feat: 配置 config 模块(任务书 04)
- 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>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import type { TSchema } from "typebox";
|
||||
import { Settings } from "typebox/system";
|
||||
import { Value } from "typebox/value";
|
||||
import { ResearchConfig } from "./schema.ts";
|
||||
|
||||
// research.json 是用户写的配置,属于系统边界(CLAUDE.md"罕见情况怎么办"),所以一次列出全部错误,
|
||||
// 每条写明字段路径和错在哪里。
|
||||
|
||||
type SchemaError = ReturnType<typeof Value.Errors>[number];
|
||||
|
||||
const TYPE_TEXT: Readonly<Record<string, string>> = {
|
||||
string: "字符串",
|
||||
number: "数字",
|
||||
integer: "整数",
|
||||
boolean: "true 或 false",
|
||||
object: "对象",
|
||||
array: "数组",
|
||||
null: "null",
|
||||
};
|
||||
|
||||
function reasonOf(e: SchemaError): string {
|
||||
const p = e.params as Record<string, unknown>;
|
||||
switch (e.keyword) {
|
||||
case "type":
|
||||
return `应为${TYPE_TEXT[String(p.type)] ?? String(p.type)}`;
|
||||
case "enum":
|
||||
return `只能是 ${(p.allowedValues as unknown[]).map((v) => JSON.stringify(v)).join("、")} 之一`;
|
||||
case "const":
|
||||
return `只能是 ${JSON.stringify(p.allowedValue)}`;
|
||||
case "minimum":
|
||||
return `不能小于 ${String(p.limit)}`;
|
||||
case "maximum":
|
||||
return `不能大于 ${String(p.limit)}`;
|
||||
case "exclusiveMinimum":
|
||||
return `必须大于 ${String(p.limit)}`;
|
||||
case "exclusiveMaximum":
|
||||
return `必须小于 ${String(p.limit)}`;
|
||||
case "minItems":
|
||||
return `至少要有 ${String(p.limit)} 项`;
|
||||
case "pattern":
|
||||
return p.pattern === "\\S" ? "不能为空,也不能只有空白" : `格式不对,应符合 ${String(p.pattern)}`;
|
||||
default:
|
||||
return e.message;
|
||||
}
|
||||
}
|
||||
|
||||
/** 按 JSON 指针("#/properties/flow/anyOf/0"、"/flow/ablation")往下走。 */
|
||||
function walk(root: unknown, pointer: string): unknown {
|
||||
let node = root;
|
||||
for (const key of pointer.split("/").filter((k) => k !== "" && k !== "#")) {
|
||||
node = typeof node === "object" && node !== null ? (node as Record<string, unknown>)[key] : undefined;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 几选一(Union)报错时,typebox 会把每个分支的错误都报一遍。这里按区分字段(每个分支都有、取值是常量的那个字段,
|
||||
* 如 preset、enabled)认出配置想写的是哪一支,只报那一支的错误;一支都对不上,就报区分字段该写什么。
|
||||
*/
|
||||
function unionErrors(u: SchemaError, errors: SchemaError[], value: unknown): string[] {
|
||||
const branches = (walk(ResearchConfig, u.schemaPath) as { anyOf: TSchema[] }).anyOf;
|
||||
const props = branches.map((b) => (b as { properties: Record<string, { const?: unknown }> }).properties);
|
||||
const key = Object.keys(props[0]!).find((k) => props.every((p) => p[k] !== undefined && "const" in p[k]))!;
|
||||
const written = walk(value, u.instancePath);
|
||||
if (typeof written !== "object" || written === null || Array.isArray(written)) return [`${u.instancePath}:应为对象`];
|
||||
const k = props.findIndex((p) => p[key]!.const === (written as Record<string, unknown>)[key]);
|
||||
if (k < 0) {
|
||||
const allowed = props.map((p) => JSON.stringify(p[key]!.const)).join("、");
|
||||
return [`${u.instancePath}/${key}:缺少或取值不对,只能是 ${allowed} 之一`];
|
||||
}
|
||||
const prefix = `${u.schemaPath}/anyOf/${k}`;
|
||||
const inside = errors.filter((e) => e.schemaPath === prefix || e.schemaPath.startsWith(`${prefix}/`));
|
||||
return errorLines(inside, prefix, value);
|
||||
}
|
||||
|
||||
/** scope 以内的错误逐条写成"字段路径:原因";更深一层分支里的错误交给那个 Union 去挑。 */
|
||||
function errorLines(errors: SchemaError[], scope: string, value: unknown): string[] {
|
||||
const lines: string[] = [];
|
||||
for (const e of errors) {
|
||||
if (e.schemaPath.slice(scope.length).includes("/anyOf/")) continue;
|
||||
if (e.keyword === "anyOf") {
|
||||
lines.push(...unionErrors(e, errors, value));
|
||||
} else if (e.keyword === "required") {
|
||||
for (const name of e.params.requiredProperties as string[]) {
|
||||
lines.push(`${e.instancePath}/${name}:缺少这一项(每个字段都必填,不补默认值)`);
|
||||
}
|
||||
} else if (e.keyword === "additionalProperties") {
|
||||
for (const name of e.params.additionalProperties as string[]) {
|
||||
lines.push(`${e.instancePath}/${name}:不认识的字段`);
|
||||
}
|
||||
} else {
|
||||
lines.push(`${e.instancePath || "/"}:${reasonOf(e)}`);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
/** 校验一份配置(读 research.json 后、从账本读回冻结的配置时都用它);不合格就报错,列出全部错误。 */
|
||||
export function checkConfig(value: unknown): ResearchConfig {
|
||||
if (Value.Check(ResearchConfig, value)) return value;
|
||||
// typebox 默认最多收 8 条错误,Union 的每个分支又各报一遍,不放开就会漏报;放开只在这一次调用里。
|
||||
const cap = Settings.Get().maxErrors;
|
||||
Settings.Set({ maxErrors: Infinity });
|
||||
let errors: SchemaError[];
|
||||
try {
|
||||
errors = [...Value.Errors(ResearchConfig, value)];
|
||||
} finally {
|
||||
Settings.Set({ maxErrors: cap });
|
||||
}
|
||||
const lines = errorLines(errors, "#", value);
|
||||
throw new Error(`研究配置不合格,共 ${lines.length} 处:\n${lines.join("\n")}`);
|
||||
}
|
||||
|
||||
/** 读取并校验 research.json。 */
|
||||
export function loadConfig(path: string): ResearchConfig {
|
||||
const text = readFileSync(path, "utf8");
|
||||
let value: unknown;
|
||||
try {
|
||||
value = JSON.parse(text);
|
||||
} catch (error) {
|
||||
throw new Error(`${path} 不是合法的 JSON:${(error as Error).message}`);
|
||||
}
|
||||
return checkConfig(value);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// config:research.json 的字段和校验(每个字段必填,不补默认值),以及把配置翻译成步骤清单(docs/结构.md 第二、三部分)。
|
||||
export { checkConfig, loadConfig } from "./check.ts";
|
||||
export { FIELD_CATEGORIES, ResearchConfig, THINKING_LEVELS, type FieldCategory, type ThinkingLevel } from "./schema.ts";
|
||||
export {
|
||||
ROUND_STEPS,
|
||||
STUDY_STEPS,
|
||||
stepList,
|
||||
type RoundStepId,
|
||||
type RuleRef,
|
||||
type Step,
|
||||
type StepList,
|
||||
type StudyStepId,
|
||||
} from "./steps.ts";
|
||||
@@ -0,0 +1,179 @@
|
||||
import { Type, type Static, type TSchema } from "typebox";
|
||||
import { BATCH_RULES, CELL_RULES, type BatchRuleName, type CellRuleName } from "../rules/index.ts";
|
||||
import { Id } from "../vocab/index.ts";
|
||||
|
||||
// research.json 的字段定义(docs/结构.md 第二部分、第五部分"方法参数""步骤开关")。
|
||||
// 每个字段都必填,不写默认值;不认识的字段报错。可以没有的值写 null(目前没有这样的字段)。
|
||||
// 每个字段带两样给配置对话读的东西:description 是中文说明,category 标明它是"用户配置"(配置对话逐项问)
|
||||
// 还是"方法参数"(配置对话按方法.md 6.11 的现行值写进文件,给用户看过,不逐项问)。
|
||||
// 任务本身的事实(最终指标、方向、ε、已知最好、时限、核数、镜像)不在这里,归 tasks、放在任务目录里。
|
||||
|
||||
/** 字段属于哪一类,写在每个字段定义的 category 属性里。 */
|
||||
export const FIELD_CATEGORIES = ["用户配置", "方法参数"] as const;
|
||||
export type FieldCategory = (typeof FIELD_CATEGORIES)[number];
|
||||
|
||||
const CLOSED = { additionalProperties: false } as const;
|
||||
/** 文字字段:不能为空,也不能只有空白。 */
|
||||
const TEXT = { pattern: "\\S" };
|
||||
|
||||
function user<T extends TSchema>(schema: T, description: string): T {
|
||||
return { ...schema, description, category: "用户配置" };
|
||||
}
|
||||
function method<T extends TSchema>(schema: T, description: string): T {
|
||||
return { ...schema, description, category: "方法参数" };
|
||||
}
|
||||
|
||||
/**
|
||||
* Pi 接受的思考档位。config 不能引用 Pi(只有 agents、plugin 能),所以在这里照抄一份,出处是本机 Pi 0.85.1:
|
||||
* - @earendil-works/pi-agent-core dist/types.d.ts 第 261 行 `type ThinkingLevel`;
|
||||
* - @earendil-works/pi-coding-agent dist/cli/args.js 第 6 行 `VALID_THINKING_LEVELS`。
|
||||
* 两处都是 off、minimal、low、medium、high、xhigh、max。xhigh、max 只有部分模型支持(同一处注释)。
|
||||
*/
|
||||
export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
||||
export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
||||
|
||||
/** 规则名从 rules 的登记表取,不另抄名单。 */
|
||||
const BATCH_RULE_NAMES = Object.keys(BATCH_RULES) as BatchRuleName[];
|
||||
const CELL_RULE_NAMES = Object.keys(CELL_RULES) as CellRuleName[];
|
||||
|
||||
/** 一个角色用的模型:provider、model 与 Pi 的模型服务配置里的写法相同。 */
|
||||
const Model = Type.Object(
|
||||
{
|
||||
provider: user(Type.String(TEXT), "模型服务的名字,与 Pi 的模型服务配置里的写法相同"),
|
||||
model: user(Type.String(TEXT), "模型名,与 Pi 的模型服务配置里的写法相同,例如 glm-5.3"),
|
||||
thinking: user(
|
||||
Type.Enum(THINKING_LEVELS),
|
||||
`思考档位,必须写明,不能写 null 去用 Pi 自己的默认:${THINKING_LEVELS.join("、")} 之一;xhigh、max 只有部分模型支持`,
|
||||
),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
/** 第零轮(方法.md 4.2):关,或开并写讨论次数。 */
|
||||
const RoundZero = Type.Union([
|
||||
Type.Object({ enabled: method(Type.Literal(false), "第零轮关") }, CLOSED),
|
||||
Type.Object(
|
||||
{
|
||||
enabled: method(Type.Literal(true), "第零轮开"),
|
||||
discussions: method(Type.Enum([1, 2]), "第零轮讨论几次,1 或 2;现行值 2(方法.md 4.2)"),
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
]);
|
||||
|
||||
/** 消融(方法.md 第十一部分"步骤开关"):关,或开并写选格规则。 */
|
||||
const Ablation = Type.Union([
|
||||
Type.Object(
|
||||
{ enabled: method(Type.Literal(false), "消融关:不列消融格;解释照样写文字,但不给每格的三个数") },
|
||||
CLOSED,
|
||||
),
|
||||
Type.Object(
|
||||
{
|
||||
enabled: method(Type.Literal(true), "消融开"),
|
||||
cellRule: method(
|
||||
Type.Enum(CELL_RULE_NAMES),
|
||||
`选格规则名,取自 rules 的选格登记表:${CELL_RULE_NAMES.join("、")};现行值 valueMinusCost(信息价值减成本)`,
|
||||
),
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
]);
|
||||
|
||||
/**
|
||||
* 两种流程(方法.md 第十一部分)。完整流程写开关、规则和它才用得到的方法参数;
|
||||
* 朴素循环整套照搬,不能写任何开关、规则,也不写它用不到的 λ、u、衰减(它不预测、不消融)。
|
||||
*/
|
||||
const FullFlow = Type.Object(
|
||||
{
|
||||
preset: method(Type.Literal("full"), "完整流程"),
|
||||
roundZero: method(RoundZero, "第零轮:写入口、讨论、认领、分配(方法.md 4.2)"),
|
||||
firstPrediction: method(Type.Boolean(), "第一次预测(看原始提案)开不开(方法.md 4.3 ④);现行值 true"),
|
||||
critique: method(
|
||||
Type.Boolean(),
|
||||
"挑刺与回应开不开(方法.md 4.3 ⑤ ⑥);关掉时不挑刺、不回应,协议照样冻结;现行值 true",
|
||||
),
|
||||
ablation: method(Ablation, "消融(方法.md 4.3 ⑬–⑰)"),
|
||||
batchRule: method(
|
||||
Type.Enum(BATCH_RULE_NAMES),
|
||||
`选批规则名,取自 rules 的选批登记表:${BATCH_RULE_NAMES.join("、")};现行值 bestExpected(批内最好期望)`,
|
||||
),
|
||||
openReserve: method(
|
||||
Type.Number({ exclusiveMinimum: 0, exclusiveMaximum: 1 }),
|
||||
"开放预留 λ:合并时给\"大家都没想到的结果\"留的份额,大于 0、小于 1;现行值 0.10(方法.md 6.3、6.11)",
|
||||
),
|
||||
knowledgeUse: method(
|
||||
Type.Number({ minimum: 0, maximum: 1 }),
|
||||
"u:测出的知识会被用上的概率,0 到 1;现行值 0.5(方法.md 6.7、6.11)",
|
||||
),
|
||||
decay: method(
|
||||
Type.Number({ minimum: 0, maximum: 1 }),
|
||||
"衰减 r:知识以后每多用一次打的折扣,0 到 1;现行值 0.5(方法.md 6.7、6.11)",
|
||||
),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
const NaiveFlow = Type.Object(
|
||||
{
|
||||
preset: method(
|
||||
Type.Literal("naive"),
|
||||
"朴素循环:方法.md 第十一部分的对照流程整套照搬,不挑刺、不回应、不预测、均匀随机选批、不消融",
|
||||
),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
const PositiveInteger = Type.Integer({ minimum: 1 });
|
||||
|
||||
export const ResearchConfig = Type.Object(
|
||||
{
|
||||
studyId: user(Id, "研究编号:决定的种子由它和轮次、版本算出;只许字母、数字和 ._-,首字符是字母或数字"),
|
||||
task: user(Type.String(TEXT), "用哪个任务:任务目录的路径。指标、方向、ε、时限等任务事实写在任务目录里"),
|
||||
models: user(
|
||||
Type.Object(
|
||||
{
|
||||
researchers: user(
|
||||
Type.Array(Model, { minItems: 1 }),
|
||||
"研究者:每一项是一位研究者用的模型,列表长度就是研究者人数 N",
|
||||
),
|
||||
experimenter: user(Model, "实验员用的模型"),
|
||||
verifier: user(Model, "核验员用的模型"),
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
"各角色用的模型",
|
||||
),
|
||||
proposalLimit: user(PositiveInteger, "每人每轮提案上限 M(方法.md 4.3 ②)"),
|
||||
batchLimit: user(PositiveInteger, "每批上限:一轮正向最多跑几个候选,本轮批量 = min(每批上限, 余额)(方法.md 第七部分)"),
|
||||
runs: user(PositiveInteger, "正式运行次数:唯一的预算,基线运行也计入,用完研究结束(方法.md 第七部分)"),
|
||||
factorLimit: method(PositiveInteger, "因子数上限:一条提案最多几个因子;现行值 3(方法.md 6.11)"),
|
||||
maxRepairs: method(
|
||||
Type.Integer({ minimum: 0 }),
|
||||
"修复次数上限:核验不通过时最多退回实验员修几次(方法.md 4.3 ⑨)",
|
||||
),
|
||||
lightChecks: method(
|
||||
Type.Object(
|
||||
{
|
||||
maxCount: method(
|
||||
Type.Integer({ minimum: 0 }),
|
||||
"实验员在一个实现任务里最多做几次轻量检查(方法.md 第七部分)",
|
||||
),
|
||||
seconds: method(
|
||||
Type.Number({ exclusiveMinimum: 0 }),
|
||||
"一次轻量检查最长几秒(方法.md 第七部分)",
|
||||
),
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
"轻量检查的次数与时长上限:只用来检查语法、接口和极小的样例,不能借它跑正式规模的求解",
|
||||
),
|
||||
promptDir: method(Type.String(TEXT), "提示词目录:各角色的角色提示词和各类任务消息放在这里"),
|
||||
devEntry: method(
|
||||
Type.Boolean(),
|
||||
"开发入口(方法.md 第九部分):正式研究写 false;开发调试时才写 true,打开过的研究不能当验收证据",
|
||||
),
|
||||
flow: method(Type.Union([FullFlow, NaiveFlow]), "流程:完整流程或朴素循环(方法.md 第十一部分)"),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
export type ResearchConfig = Static<typeof ResearchConfig>;
|
||||
@@ -0,0 +1,136 @@
|
||||
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) })),
|
||||
};
|
||||
}
|
||||
+4
-4
@@ -128,8 +128,8 @@ export const KINDS = {
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
/** 3 私人笔记的每一版:每次修改追加一版,以版本号最大的为准。 */
|
||||
note: Type.Object({ author: Id, version: Version, body: Text }, CLOSED),
|
||||
/** 3 私人笔记:不属于任何阶段,每次修改追加一条,以最后写入的一条(序号最大)为准。 */
|
||||
note: Type.Object({ author: Id, body: Text }, CLOSED),
|
||||
|
||||
/** 4 冻结协议:每个候选一份。内容由 flow 合成,先存 Json。 */
|
||||
protocol: Type.Object({ candidate: Id, protocol: AnyJson }, CLOSED),
|
||||
@@ -176,8 +176,8 @@ export const KINDS = {
|
||||
CLOSED,
|
||||
),
|
||||
|
||||
/** 9 结算:一次消融一条,各格结局、配合关系、解释的确认或否定等先存 Json。 */
|
||||
settlement: Type.Object({ ablation: Id, result: AnyJson }, CLOSED),
|
||||
/** 9 结算:每轮最多采纳一个改法,所以最多一次消融,结算按轮次一轮一条。各格结局、配合关系、解释的确认或否定等先存 Json。 */
|
||||
settlement: Type.Object({ round: Round, result: AnyJson }, CLOSED),
|
||||
|
||||
/** 10 模型调用开始:先登记再调用(方法.md 第九部分)。附这次任务的全部材料,用来重现模型当时看到了什么。一次调用只有一条。 */
|
||||
callStarted: Type.Object({ call: Id, actor: Id, stageId: OptionalId, model: Text, materials: AnyJson }, CLOSED),
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { DatabaseSync } from "node:sqlite";
|
||||
// 账本的表结构(docs/结构.md 第四部分"全部只追加"):只有一张表,每一行是序号、种类、内容、内容的哈希。
|
||||
// 表结构一改就把版本号加 1;旧文件版本号对不上时打开报错,不迁移,也不逐字核对建表语句。
|
||||
|
||||
export const SCHEMA_VERSION = 1;
|
||||
export const SCHEMA_VERSION = 2;
|
||||
|
||||
/** 新文件要执行的建表语句,由 Ledger 在一个事务里执行,最后写入版本号。 */
|
||||
export const CREATE_SCHEMA = [
|
||||
@@ -31,7 +31,8 @@ export const CREATE_SCHEMA = [
|
||||
`CREATE UNIQUE INDEX run_result_once ON records (json_extract(content, '$.run')) WHERE kind = 'runResult'`,
|
||||
`CREATE UNIQUE INDEX call_started_once ON records (json_extract(content, '$.call')) WHERE kind = 'callStarted'`,
|
||||
`CREATE UNIQUE INDEX call_ended_once ON records (json_extract(content, '$.call')) WHERE kind = 'callEnded'`,
|
||||
`CREATE UNIQUE INDEX settlement_once ON records (json_extract(content, '$.ablation')) WHERE kind = 'settlement'`,
|
||||
// 结算以轮次为编号:每轮最多采纳一个改法,所以最多一次消融。
|
||||
`CREATE UNIQUE INDEX settlement_once ON records (json_extract(content, '$.round')) WHERE kind = 'settlement'`,
|
||||
`PRAGMA user_version=${SCHEMA_VERSION}`,
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user