diff --git a/WORK_LOG.md b/WORK_LOG.md index 2eb65d8..a2aeac9 100644 --- a/WORK_LOG.md +++ b/WORK_LOG.md @@ -38,6 +38,7 @@ - 三个数改为严格递增,去掉尖峰(bfb96ba)。用户原话:"凡是为'三个数相等''宽度为 0''开放预留缩成一个点'写的专门处理,全部删掉"。 - 答案路径、保障动手次数只记读到的原始内容或读不到的原因;在不在位置表里,由以后拿得到位置表的模块现算,不存。用户原话:"按'能现算的不存',tasks 只记读到的原始内容……对表由以后有位置表的模块现算,把这条记进 WORK_LOG。" - tasks 与 runner(任务书 05):依赖跟着任务走,一份研究不能自己改依赖,镜像在研究开始时建一次、编号冻结进账本;runner、tasks 行数按结构.md 第七部分;两个模块合成一份任务书。用户原话:"都同意,写任务书 05 吧"。监督者另定并告知用户:凡是执行任务代码的事(正式运行、判分、轻量检查)都在任务镜像里跑;资源用量在容器里读,不建要 root 清理的 cgroup 分组;容器断网;同一份研究所有运行用同一个种子;不做"取消"函数;`VERDICTS` 删掉"判不了"。 +- workspace(任务书 06)用 git:一份研究一个裸仓库,版本号是目录树编号;存版本时自己列文件、用 hash-object 存,不用 `git add`(实测 `.gitignore` 会让它悄悄漏存);git 子进程不读用户配置;实验员目录里除了 task.json 的"不存"清单都算候选,符号链接等特殊文件退回实验员;预期 200–300 行。先派全新上下文子 Agent 独立分析过。用户原话:"都同意,写任务书 06 吧"。 ## 未完成 @@ -82,4 +83,4 @@ ## 下一步 -任务书 05 已核对通过(cf6bc4c)。下一个模块待和用户商量。 +任务书 06(workspace,`任务书/06-workspace.md`)已写好,等用户另开新 Agent 执行;完成后监督者核对。 diff --git a/docs/结构.md b/docs/结构.md index 158775e..45ac4b3 100644 --- a/docs/结构.md +++ b/docs/结构.md @@ -8,6 +8,7 @@ - 用户 2026-10-02 同意:"全部只追加":账本用一张只追加的表,全部记录都不能改。 - 用户 2026-10-02 指出可见性、截止序号、通用唯一键都是过度设计:"每次给到派的材料有哪些"和"一开始灌进去还是让它去查"是分开的两件事;Jev 本质上是一种可以代替程序的判别器。据此:谁能看什么归 materials(按任务定材料),ledger 只存;只在方法要求"只能一次"处加唯一约束;Jev 不单独成记录。ledger 预期 500–900 行。 - 用户 2026-10-02 同意:依赖跟着任务走(一份研究不能自己改依赖),凡是执行任务代码的事都由 runner 在任务镜像里跑,runner 与 tasks 的行数按第七部分。 +- 用户 2026-10-02 同意:workspace 用 git,按独立分析给出的窄用法(版本号用目录内容编号、自己列文件存、不读用户配置);实验员目录里除了任务给的"不存"清单,其余都算候选。 - 依据是两次开发调研。一次是模块切分,参考了 Parnas 1972 与 1979、Ousterhout、Cockburn 和同类项目的源码;一次是同类研究的实验设计。 --- @@ -42,7 +43,7 @@ | 外部 | agents(角色会话) | Pi 的接口:开子会话、发工具和提示词、收提交、记用量 | 无 | | 外部 | runner(运行器) | Docker 的用法:建镜像、限时、限核、断网、记资源、查一次运行的状态 | 无 | | 外部 | tasks(任务) | 任务目录里的事实和文件(指标、ε、时限、核数、镜像的 Dockerfile 与依赖清单);一个任务怎么跑、怎么从原始产物独立算分(填圆的合法化也在这里) | 无 | -| 外部 | workspace(代码版本) | git 的用法:基线快照、实验员工作目录、冻结候选、代码差异 | 无 | +| 外部 | workspace(代码版本) | git 的用法:一份研究一个裸仓库,版本号是目录内容的编号;存版本(自己列文件,不用 `git add`)、取出、比较两个版本;不读用户的 git 配置 | 无 | | 外部 | jev(Jev 判别) | 问法、阈值、校准 | 无 | | 纯计算 | rules(研究规则) | 全部公式和常数;信念本、战绩、余额的计算 | 无 | @@ -190,7 +191,7 @@ Jev 是一个判别器,和程序检查地位相同,不单独成一种记录 | jev | 300–500 | | materials | 1000–1800 | | agents | 800–1300 | -| workspace | 300–600 | +| workspace | 200–300 | | runner | 250–450 | | tasks | 公共部分与填圆合计 400–700,另有任务目录里的 Python 文件 | | export | 500–900 | diff --git a/任务书/06-workspace.md b/任务书/06-workspace.md new file mode 100644 index 0000000..cfefa89 --- /dev/null +++ b/任务书/06-workspace.md @@ -0,0 +1,147 @@ +# 任务书 06:workspace(代码版本) + +写 workspace 模块:用 git 存每一份代码版本,取出到目录,比较两个版本改了什么。顺带给 tasks 的 `task.json` 加一项"不存的东西"。 + +本任务书已由用户和监督者在 2026-10-02 一起确定。下面说的"监督者",是在参考项目目录里和用户讨论设计的另一个会话。用不用 git、怎么用,是先派一个全新上下文的子 Agent 独立分析、实测之后才定的,下面"实测过"的结论都来自那次分析,监督者复现过其中第 3 条。 + +## 开工前先读 + +- `CLAUDE.md`:全部,尤其是"罕见情况怎么办"(什么算边界、哪些不算)、"不设默认值"、"测试"里"不为了方便测试而给产品代码加参数"。 +- `docs/结构.md`: + - 第二部分:模块表里 workspace 一行; + - 第三部分:账本记录第 6 种"冻结代码"; + - 第六部分:只有 runner 和 workspace 能起子进程; + - 第八部分:tasks 一节。 +- `docs/方法.md`: + - 4.3 ⑨:实现与核验、因子位置表、没有归属的改动、不许改的文件; + - 4.3 里消融格的实现:从完整候选出发关掉因子; + - 第九部分:冻结的东西不可覆盖,恢复时只核对。 +- vocab 里的 `CodeLocation`(`src/vocab/submissions.ts`):代码位置写成"文件"、"文件:行号"或"文件:起-止"。 +- `WORK_LOG.md`。 +- 参考项目(只读,不要改): + - `/home/iomgaa/Projects/RivalBits/src/board/code-store.ts`; + - `/home/iomgaa/Projects/RivalBits/src/rounds/code-diff.ts`。 + + 只为了解它们原来要解决什么问题,第六部分说明为什么都不搬。 + +## 一、这个模块做什么 + +用一个例子走一遍: + +1. 研究开始:把任务目录里的 `baseline/` 存成第一个版本,得到一个版本号。 +2. 实验员实现候选 c1:把"本批基线"那个版本取出到一个空目录,实验员在里面改。 +3. 实验员交了:把这个目录现在的内容存成一个版本,得到 c1 的版本号。flow 把它记进账本。 +4. 核验员、研究者要看改了什么:比较本批基线和 c1 两个版本。 +5. 正式运行:把 c1 取出到一个目录,交给 tasks 准备运行目录。 +6. 采纳:c1 成为新基线。git 里什么都不做,账本记一句"现在的基线是哪个版本号"。 +7. 消融格:从 c1 取出,实验员关掉一个因子,再存成一个版本。 + +"上一版是谁"由账本记,git 里不记。 + +## 二、怎么用 git + +**一个裸仓库,版本号就是目录内容的编号。** +- 一份研究一个裸仓库(`git init --bare`),位置由调用方给。 +- 版本号用 git 的目录树编号(tree id),不造提交。同样的内容永远得到同一个编号,所以: + - 中断恢复时重新存一次,得到的号和账本里已经记的一样; + - 存过的版本没法被覆盖,因为内容变了编号就变。 +- 每存一个版本,挂一个以版本号命名的引用,例如 `refs/versions/<编号>`,防止 git 清理没有引用指着的对象。实测过:没挂引用的对象,`git gc --prune=now` 之后就没了。 + +**存版本不用 `git add`,由我们自己遍历目录、列出文件清单**: +- 每个文件用 `git hash-object -w --no-filters` 存,再用临时的索引文件(`GIT_INDEX_FILE`)加 `git update-index --index-info` 和 `git write-tree` 得到目录树编号。 +- 为什么不用 `git add`:实测过,目录里有 `.gitignore` 时,被它列中的文件 `git add` 不存,退出码是 0。子目录里有另一个 git 仓库时,里面的文件也会丢。实验员写个 `.gitignore` 是模型干活时顺手就会做的事,而漏存以后,正式运行跑的代码和实验员交的就不是同一份,谁也发现不了。 +- 记下可执行位:普通文件 100644,可执行文件 100755。 + +**git 不读用户的配置**: +- 我们启动的每个 git 子进程都设 `GIT_CONFIG_NOSYSTEM=1` 和 `GIT_CONFIG_GLOBAL=/dev/null`。用户本机的全局配置里有换行符转换、比较算法、lfs 过滤器这些设置,会悄悄改掉存进去的内容或比较输出的格式。这件事没人操作也会发生,所以要防。 +- 仓库位置(`GIT_DIR`)只写进我们自己启动的子进程的环境变量,不写进 `process.env`。插件进程的环境会被实验员的 shell 继承,模型顺手敲一句 `git checkout`,就会动到版本仓库。 + +**取出用 `git archive`**(或者效果一样的命令):写进一个空目录,可执行位要还原。取出的目录里没有 `.git`,实验员看不到别的版本。 + +## 三、对外提供什么 + +经 `src/workspace/index.ts` 导出,函数名由你定。仓库位置作为参数传入,不设默认值。 + +1. **建仓库**:在给定位置建一个空的裸仓库。 +2. **存版本**:给一个目录和一份"不存的名字"清单,交回版本号,或者交回"存不了 + 原因"。 + - 遍历目录:名字在清单里的文件或目录,在任何一层都跳过。 + - 遇到符号链接、设备文件这类既不是普通文件也不是目录的东西,交回"存不了"并写明是哪个路径。这个目录是实验员交上来的,属于系统边界(CLAUDE.md"罕见情况怎么办"),flow 会把它当作提交不合格退回给实验员。不要替它猜怎么存。 + - 基线也用这个函数存。 +3. **取出**:把一个版本的文件写进一个空目录。 +4. **比较两个版本**:给"从哪个版本"和"到哪个版本",交回: + - 每个改动过的文件:路径,新增、删除还是修改,是不是只改了可执行位,是不是二进制; + - 每个文件里变了哪几块,**新旧两边的行号范围都给**。正向核验对照的是新版本的行号(位置表写的是候选代码的行号);消融时位置表写的是完整候选的行号,也就是比较里"从"那一边。只删不加的块在新版本里没有行,用 `git diff -U0` 那种"第几行之后"的写法表示,在类型里写清楚; + - 给模型看的差异文本:带上下文的 `git diff` 原样输出,不加颜色。 + + 列文件用 `git diff-tree -r` 这类给程序读的命令,它的输出格式 git 保证不变。二进制文件不拆块,当作整个文件改了。 + +**不归 workspace 管的**: +- 哪些改动没归到任何因子或保障:由拿得到位置表的模块,用这里给的行号范围现算; +- 哪些改动属于不许改的文件:用这里的文件清单,交给 tasks 现成的函数判断; +- 给研究者看的只读目录:以后要用时,调用方用"取出"。 + +## 四、顺带改 tasks + +- `task.json` 加一项"不存的名字",字段名你定,必填。填圆写: + - `__pycache__`:实验员跑 Python 时会自动生成; + - `.git`:模型可能在目录里 `git init`; + - `solution.json`、`metrics.json`:运行输出,不是代码。 +- tasks 读任务时一并交回这份清单,flow 存版本时传给 workspace。workspace 不引用 tasks。 +- `protectedFiles` 里的 `solution.json` 删掉:它现在在"不存"清单里,harness 开跑前也会删掉旧的。 +- 相应的测试和 `docs/结构.md` 第八部分 tasks 一节跟着改。 + +## 五、测试 + +workspace 的测试用真实 git,测试仓库建在测试自己的临时目录里。 + +- **同内容同号**:同一个目录存两次;把目录复制一份、改了文件时间再存,都得到同一个版本号。 +- **取出再存回来,号不变**:内容、可执行位都还原。 +- **`.gitignore` 不起作用**:目录里有 `.gitignore` 写了 `*.json`,`cfg.json` 照样存进去。 +- **不读用户配置**:测试里给 git 一份会改行为的全局配置(例如 `core.autocrlf=true`,或者改比较前缀的 `diff.noprefix=true`),结果不受影响。这份配置用测试自己的环境变量(例如 `HOME` 指向临时目录)造出来,不给产品代码加参数。 +- **"不存"清单**:清单里的名字在顶层和子目录里都跳过。 +- **存不了**:目录里有符号链接时,交回"存不了"和那个路径。 +- **引用防清理**:存完以后 `git gc --prune=now`,版本照样能取出。 +- **比较**: + - 新增、删除、修改、只改可执行位的文件各一个,以及一个二进制文件; + - 行号:手写两个小文件,算好预期的新旧行号范围再比,至少包括改一行、加几行、只删不加三种块; + - 预期值手算,不从实现里推。 +- **tasks**:`task.json` 少了新字段时报错;读任务交回这份清单。 + +workspace 关系到"冻结的东西不可覆盖"和中断恢复(CLAUDE.md"工作方式"),写完派一个全新上下文的子 Agent 审阅一轮。告诉审阅者: +- 每条意见先回答"不写会怎样"; +- 只防三类:没人操作也会发生的、模型正常工作时就会做的、出错后发现不了的; +- 模块之间传错参数、有人用 shell 故意破坏,都只列出不改; +- 下面这些分析时已经判断过不用防,除非有新证据: + - 并发存版本:每次用自己的临时索引,控制进程只有一个; + - 换行符和大文件:`--no-filters` 原样存; + - 嵌套仓库:清单是自己列的,不会出现。 + +审阅意见逐条判断,采纳或不采纳都在报告里写理由。 + +## 六、参考项目的保留与删除 + +| 处理 | 内容 | +|---|---| +| 删除 | `code-store.ts` 整个文件:复制、逐个重算哈希、改只读、强制写盘、目录已存在时逐字节比对。git 的内容编号本身就保证了这些 | +| 删除 | `code-diff.ts` 整个文件:自写的逐行比较算法,改用 git。它的理由是"换台机器结果也一样",但一份研究只在一台机器上算 | +| 删除 | `candidateContentHash` 里的可执行位处理:git 的目录树本来就记可执行位 | +| 借鉴 | 无 | + +## 七、规模与文档 + +- 预期 200–300 行,不含测试。超过 300 行就停下来检查。 +- 完成后更新: + - `docs/结构.md` 第八部分:补 workspace 对外的函数和类型,每项一句话;tasks 一节按第四部分改; + - `WORK_LOG.md`:在"已完成"补一条。 +- 不新建 md 文件。 + +## 八、验收与报告 + +做完后本地提交,然后在回复里交一份报告,监督者会对着实际文件逐项核对: + +1. 提交号,新增和修改的文件列表,每个文件的行数,workspace 的总行数。 +2. `npm run check` 和 `npm test` 的完整输出。 +3. 比较两个版本的实际输出样例:一份结构化结果,一份差异文本。 +4. 审阅意见逐条:内容、采纳或不采纳、理由。 +5. 你和这份任务书、结构.md、方法.md 不一致的地方,以及为什么。 +6. **规则不清楚的地方**:读 CLAUDE.md、结构.md、方法.md 时,哪里让你犹豫、哪里两份文件说法不一致、哪里你是靠猜的。没有就写"没有"。