Files
InfoRSI/src/submissions/checks.ts
T
iomgaa 09c44031a0 feat: 任务书 10,submissions 模块与 materials 修改
- 新增 src/submissions:先程序检查(14 条跨字段规则、不许改的文件、代码存不了、指标编号占用、提案数上限),全过再问 Jev;退回原因列出该写的全部编号;回应/统一拼冻结协议主体只在这一处;核验、消融核验通过没有。
- materials:挑刺单写"现在交什么";退回按阶段、作者、种类、对象找(ledger 退回记录加 target,表结构版本 4);核验材料去掉 Jev 预扫;信念本写合法化;删"本该关着";消融核验不通过可重做,不跑的格从 cellNotRun 事件读原因。
- 用户 2026-10-03 定:冻结协议去掉 replies、replyBody,第二次预测的答复从已公开的回应或统一读;takenMetricIds 加两个保留键;解释一律交 cells;Jev 退回只列判"是"的几问、用英文问题名。
- jev 判重材料带 keySettings;读代码工具删 offset;三处旧说法改正。
- 文档:结构.md 第四、七、八部分,WORK_LOG。

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

194 lines
11 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 type { ChecklistOf, Range, Submission } from "../vocab/index.ts";
import type { AblationCode, ForwardCode, Incoming, ProtocolBody } from "./types.ts";
// 程序检查:vocab 字段定义管不到的跨字段规则(WORK_LOG"留给 submissions 的跨字段规则"14 条,
// 加任务书 10 第四部分的不许改的文件、代码存不了、指标编号重名、提案条数)。
// 每条规则交回零到几行问题,一份提交的问题一次列全。编号写错时列出对号清单里该写的全部编号:
// 只说"不对",模型拿不到可对照的写法(参考项目真实运行 15-tight-2 第 3 轮,提交 378fe68)。
/** 一份提交的全部程序检查。回应、统一查拼好的协议(protocol),其余种类 protocol 为 null。 */
export function programProblems(inc: Incoming, protocol: ProtocolBody | null): string[] {
switch (inc.kind) {
case "entry":
case "discussion":
case "wrapUp":
return [];
case "claim":
return claimProblems(inc.content, inc.checklist);
case "proposal":
return [
...(inc.known.accepted + 1 > inc.checklist.maxProposals
? [`本阶段你已经收下 ${inc.known.accepted} 条提案,每人最多 ${inc.checklist.maxProposals} 条:要改哪一条,就在 replaces 里写它的提交编号整份替换,不要另交`]
: []),
...factorProblems(inc.content, inc.checklist),
...increasing("authorEstimate", inc.content.authorEstimate),
];
case "firstPrediction":
case "secondPrediction":
return increasing("range", inc.content.range);
case "critique":
return within("target", [inc.content.target], ["whole", ...inc.checklist.factors]);
case "response":
return [...covers("replies 的挑刺编号", inc.content.replies.map((r) => r.critique), inc.checklist.critiques), ...factorProblems(protocol!, inc.checklist)];
case "unification":
return [
...covers("replies 的挑刺编号", inc.content.replies.map((r) => r.critique), inc.checklist.critiques),
...covers("sources 的原提案编号", inc.content.sources.map((s) => s.proposal), inc.checklist.sources),
...factorProblems(protocol!, inc.checklist),
];
case "implementation":
case "repair":
return [...locationProblems(inc.content, inc.checklist), ...codeProblems(inc.known.code)];
case "verification":
return verificationProblems(inc.content, inc.checklist);
case "explanation":
return explanationProblems(inc.content, inc.checklist);
case "ablationImplementation":
return ablationProblems(inc.content, inc.checklist, inc.known.code);
case "ablationVerification":
return covers("factors 的因子", inc.content.factors.map((f) => f.factor), inc.checklist.factors);
}
}
// ---------------------------------------------------------------------------
// 各种提交
// ---------------------------------------------------------------------------
/** 规则 7:志愿至少 min(N, 入口总数) 个、不重复、编号都在入口清单里(方法.md 4.2)。 */
function claimProblems(c: Submission<"claim">, k: ChecklistOf<"claim">): string[] {
return [
...(c.choices.length < k.minChoices ? [`choices 至少要排 ${k.minChoices} 个入口,你排了 ${c.choices.length} 个`] : []),
...within("choices", c.choices, k.entries),
...repeated(c.choices).map((x) => `choices 里 ${x} 写了不止一次`),
];
}
/**
* 提案、回应、统一共用(方法.md 第五部分"延续""指标"、4.3 ②⑥):
* 规则 2 因子数上限;3 两个因子不延续同一个、延续的编号看得到;4 保障短名;10 指标的引用与新登记。
*/
function factorProblems(p: Pick<ProtocolBody, "factors" | "safeguards" | "metrics">, k: ChecklistOf<"proposal" | "response" | "unification">): string[] {
const continued = p.factors.flatMap((f) => (f.continues === null ? [] : [f.continues]));
const defined = p.metrics.defined.map((m) => m.id);
return [
...(p.factors.length > k.factorLimit
? [`factors 有 ${p.factors.length} 个因子,超过上限 ${k.factorLimit} 个:多出来的留到下一轮,不要几个捆成一个`]
: []),
...repeated(continued).map((x) => `有两个以上因子的 continues 都写了 ${x}:同一份提案里两个因子不能延续同一个因子`),
...within("continues", unique(continued), k.continuable).map((x) => `${x};新因子写 null`),
...unique(p.safeguards)
.filter((s) => s === "main" || /^p[1-9][0-9]*$/.test(s))
.map((s) => `保障短名 ${s} 不能用:不能叫 main,也不能写成 p1、p2 这种因子编号的样子`),
...repeated(p.safeguards).map((s) => `safeguards 里 ${s} 写了不止一次`),
...within("metrics.reused", p.metrics.reused, k.reusableMetrics),
...repeated(defined).map((x) => `metrics.defined 里编号 ${x} 写了不止一次`),
...unique(defined)
.filter((x) => k.takenMetricIds.includes(x))
.map((x) => `metrics.defined 的编号 ${x} 已被占用(已占用的是 ${k.takenMetricIds.join("、")}):换一个编号;要用别人登记过的就写进 metrics.reused`),
];
}
/** 规则 11:位置表覆盖每个因子和每条保障;代码位置起始行不大于结束行(方法.md 4.3 ⑨)。 */
function locationProblems(c: Submission<"implementation">, k: ChecklistOf<"implementation" | "repair">): string[] {
const all = [...c.factorLocations.flatMap((f) => f.locations), ...c.safeguardLocations.flatMap((s) => s.locations), ...c.unassigned.map((u) => u.location)];
return [
...covers("factorLocations 的因子", c.factorLocations.map((f) => f.factor), k.factors),
...covers("safeguardLocations 的保障", c.safeguardLocations.map((s) => s.safeguard), k.safeguards),
...unique(all)
.filter((loc) => {
const m = /:([0-9]+)-([0-9]+)$/.exec(loc);
return m !== null && Number(m[1]) > Number(m[2]);
})
.map((loc) => `代码位置 ${loc} 的起始行大于结束行`),
];
}
/** 规则 12:核验的条款、因子、保障逐项全覆盖;没有归属的改动与材料列出的逐条对应(方法.md 4.3 ⑨)。 */
function verificationProblems(c: Submission<"verification">, k: ChecklistOf<"verification">): string[] {
return [
...covers("clauses 的实现条款", c.clauses.map((x) => x.clause), k.clauses),
...covers("factors 的因子", c.factors.map((x) => x.factor), k.factors),
...covers("safeguards 的保障", c.safeguards.map((x) => x.safeguard), k.safeguards),
...covers("unassigned 的改动位置", c.unassigned.map((x) => x.location), k.unassigned),
];
}
/**
* 规则 5、6、1:解释最多 M 条;每条都交 cells、恰好覆盖材料列出的格,三个数严格递增(方法.md 4.3 ⑭)。
* 用户 2026-10-03 定:只按完整流程全开实现,一律要求交 cells;关掉消融的情形以后做开关时再补。
*/
function explanationProblems(c: Submission<"explanation">, k: ChecklistOf<"explanation">): string[] {
return [
...(c.explanations.length > k.maxExplanations ? [`交了 ${c.explanations.length} 条解释,最多 ${k.maxExplanations} 条`] : []),
...c.explanations.flatMap((e, i) =>
e.cells === undefined
? [`第 ${i + 1} 条解释没交 cells:每条解释都要对 ${k.cells.join("、")} 每一格给三个数`]
: [
...covers(`第 ${i + 1} 条解释 cells 的格`, e.cells.map((x) => x.factor), k.cells),
...e.cells.flatMap((x) => increasing(`第 ${i + 1} 条解释 ${x.factor} 格的 range`, x.range)),
],
),
];
}
/**
* 规则 13:写了 cannotRun 时 alsoOff 为空;alsoOff 不含本格要关的因子、都在因子清单里、不重复(方法.md 4.3 ⑯)。
* 跑得起来的另查代码:运行的规矩和正向运行相同,改了不许改的文件同样退回。
*/
function ablationProblems(c: Submission<"ablationImplementation">, k: ChecklistOf<"ablationImplementation">, code: AblationCode | null): string[] {
return [
...(c.cannotRun !== null && c.alsoOff.length > 0 ? ["写了 cannotRun(这一格跑不起来)时 alsoOff 要交空数组:两种做法只能选一种"] : []),
...(c.alsoOff.includes(k.off) ? [`alsoOff 不能写本格要关的因子 ${k.off},只写还要一起关的别的因子`] : []),
...within("alsoOff", c.alsoOff.filter((x) => x !== k.off), k.factors.filter((x) => x !== k.off)),
...repeated(c.alsoOff).map((x) => `alsoOff 里 ${x} 写了不止一次`),
...(c.cannotRun === null ? codeProblems(code!) : []),
];
}
/** 代码存不了、改了不许改的文件(方法.md 4.3 ⑨)。 */
function codeProblems(code: ForwardCode | AblationCode): string[] {
if (!code.saved) return [`代码存不成版本:${code.path}(${code.reason})。改掉它再交`];
return code.protectedFiles.length === 0 ? [] : [`改了不许改的文件:${code.protectedFiles.join("、")}。把它们改回本批基线的样子再交`];
}
// ---------------------------------------------------------------------------
// 共用
// ---------------------------------------------------------------------------
/** 规则 1:三个数严格递增(方法.md 第五部分"三个数")。 */
function increasing(field: string, r: Range): string[] {
return r.q1 < r.q5 && r.q5 < r.q9 ? [] : [`${field} 的三个数要严格递增 q1 < q5 < q9,你写的是 ${r.q1} / ${r.q5} / ${r.q9}`];
}
/** 一组编号必须恰好覆盖 expected:不多、不少、不重复。只写出现了的几段。 */
function covers(field: string, actual: readonly string[], expected: readonly string[]): string[] {
const unknown = unique(actual.filter((x) => !expected.includes(x)));
const missing = expected.filter((x) => !actual.includes(x));
const twice = repeated(actual);
if (unknown.length + missing.length + twice.length === 0) return [];
return [
[
`${field}应当是 ${expected.length === 0 ? "没有(交空数组)" : expected.join("、")}`,
...(unknown.length > 0 ? [`你写的 ${unknown.join("、")} 不在里面`] : []),
...(missing.length > 0 ? [`还缺 ${missing.join("、")}`] : []),
...(twice.length > 0 ? [`${twice.join("、")} 写了不止一次`] : []),
].join(";"),
];
}
/** 一组编号都要在 allowed 里(可以只写其中几个)。 */
function within(field: string, actual: readonly string[], allowed: readonly string[]): string[] {
const unknown = unique(actual.filter((x) => !allowed.includes(x)));
if (unknown.length === 0) return [];
return [`${field} 只能写 ${allowed.length === 0 ? "(现在没有可写的编号)" : allowed.join("、")};你写的 ${unknown.join("、")} 不在里面`];
}
function unique(xs: readonly string[]): string[] {
return [...new Set(xs)];
}
/** 出现了不止一次的值,各列一次。 */
function repeated(xs: readonly string[]): string[] {
return unique(xs.filter((x, i) => xs.indexOf(x) !== i));
}