Skip to content

R26 集成批 2:文案 locale 独占门禁 + 两段式压缩落盘 + MCP M0/M1/M1b + 插件错误路径中和 + .spill 排除快照 - #26

Merged
mycyg merged 36 commits into
mainfrom
r26/integration-5
Sep 5, 2026
Merged

mycyg merged 36 commits into
mainfrom
r26/integration-5

Conversation

@mycyg

@mycyg mycyg commented Sep 5, 2026

Copy link
Copy Markdown
Owner

R26 集成批 2:文案 locale 独占门禁(B5)、两段式压缩与落盘(B10)、MCP 客户端 M0/M1/M1b、插件错误路径中和、.spill/ 排除快照。基线为 PR #25 的头(含真机修复)。

一、B5 文案 locale 独占 + AST 门禁(r26/u-ui-i18n-gate,7 commits)

  • 新门 pnpm audit:ui-i18nscripts/dev/check-ui-i18n.ts,TypeScript 编译器 API):含汉字的字符串/模板字面量出现在非词典文件里即违规;允许文件 i18n*.ts / locale*.ts / locales/** / *-copy.ts;两条失效保险丝(MINIMUM_SOURCES / MINIMUM_LOCALE_OWNER_SOURCES);行内 ui-i18n-allow 豁免(模型可见文本等)。
  • 棘轮基线 scripts/dev/ui-i18n-baseline.json(按文件 + 归一化片段记,不按行号):基线内不报、新增即红、已消失提示删除。真实存量 5732 处(立项写的 261 处只数了一种写法),本批迁了 36%:桌面端 2086→662(-68%),route-components.ts 892→526,19 份 per-module locales.ts(中文 key 集 + 英文 satisfies)。渲染文案一字未改,全靠既有测试与 golden 兜底。
  • 禁词门 check-copy-terms.ts 从手写 5 个文件扩到 258 个文案文件(与新门共用发现逻辑,判据升级为 AST 字面量),25 处存量进 copy-terms-baseline.json
  • 接线:audit:ui-i18n 进 lint 链;test:scripts 进 workspace job;两个文案门 pre-commit 只扫暂存文件(整条链 2.8–5.1 秒)。

二、B10 两段式压缩 + 落盘(r26/w-two-stage-compaction,6 commits)

  • 压缩触发时先免费剪枝:保留窗口(ceil(总条数 × 0.16))之外、超单条预算(2000 字符)的 tool_result 剪成头 75% + 双语标记 + 尾 15%,重算压力够了就不发摘要请求;配对守卫结构性成立(只改槽位字符串)。
  • 超预算结果先写进 <workdir>/.spill/<四位步号>-<工具名>.txt(单 run 16 MiB 上限),截断文本末尾附定位提示,模型可用 read_file 取回;无 workdir/写失败退回纯截断话术。
  • 两套引擎单实现,equivalence 用例逐条 deepEqual;agent_run.compacting 事件 summary_kind"pruned"
  • 既有 expected 一字节未变,新增 6 份 context-pruning.*;mutation 验证两处均能红。

三、.spill/ 排除出快照(07d1084f)

  • SnapshotTakeInput / RevertSnapshotInputexcludeDirs(顶层目录名精确匹配,含分隔符的条目直接拒绝);listFiles 顶层跳过,takeFileSnapshothashWorkdir 共用(.spill 内容变化不改 contentSha256,CORE-04 去重仍生效);revert 整树换入后把当前的排除目录原样搬回。
  • agent run 快照钩子与审计还原路由传 [SPILL_DIR_NAME];audit 与 api 两侧各一条测试钉住。

四、MCP 客户端 M0 / M1 / M1b

  • M0(r26/m0-mcp-contracts,3 commits):packages/contracts/src/domain/mcp.ts(transport 阶段 0 只认 stdio、状态枚举、trust_level 管理员断言列默认 external_effect、体检项 8 条、McpServerVm / 只读投影结构性不含命令与密钥引用);迁移 0073_mcp_servers(journal idx 73)+ Drizzle 表 + 仓储 8 个原语全部工作区围栏 + 测试。
  • M1(r26/m1-mcp-client-pure,5 commits):新包 packages/mcp-client(零 IO、零依赖):命名不变式(mcp__<server>__<tool>,有损时挂 12 位指纹,不提供反解)、结果 → 模型可见文本(中和 + 上限)、逐字段映射与读写分级真值表(管理员断言 AND 服务器自述,服务器只能在上限内降风险,缺省取最高风险)、env 白名单 + 引用式密钥(必须指向 WORKHUB_MCP_SECRET_ 前缀,否则就是绕过白名单读任意变量的原语)、静态体检(含 npx/pnpm dlx/uvx 类现下现跑启动器一律拦)。101 测试。
  • M1b(r26/m1b-shared-sanitize,3 commits):packages/tools 新增 sanitizeModelFacingText(围栏中和 + 上限 + 控制字符清理);插件工具 description 中和改为薄封装(行为逐字不变,gen:expected 零 diff);插件工具结果路径接中和 + 32KB 上限(设计稿 5.2 两处同源缺口)。
  • 插件错误路径同样中和封顶(plugin-host-client.tscallTool catch,fddccbeb):插件 execute() 抛出的 message 跨 RPC 边界后不再原样拼进 errorToolResult;测试证明字面 </outputs> 被中和、40000 字符被封顶。
  • M0 契约体检枚举补 secret_ref_scope 与 M1 的 8 条对齐。
  • MCP 客户端不引 SDK:工人全程离线安装,stdio 面就是换行分隔的 JSON-RPC 2.0,插件宿主已有同形状实现,M2 自建。

expected 基线 diff 摘要(pnpm gen:expected 幂等,git status 为空)

  • B10:只新增 6 份 packages/agent/expected/context-pruning.*,既有 expected 零改动。
  • 其余各线模型可见文本零变化(M1b / 插件错误路径 / B5 / M0 / M1 均以 gen:expected 零 diff 为硬门)。

验证

  • pnpm -r typecheck 全 Done;audit 7 / agent 313 / api 2113 / tools 71 / mcp-client 101 / plugin-host 48 / db 510 / contracts 194 / ui 261 / web 168 / desktop-webview 1698 / cuu 50 / scripts 12 全过。
  • audit:ui-i18n / copy-terms / agent-notes / migrations / third-party-notices / portable-config 通过;qa:plugin-smoke 通过;cargo fmt / clippy 绿。

🤖 Generated with Claude Code

mycyg and others added 30 commits September 5, 2026 22:54
新增 scripts/dev/check-ui-i18n.ts:用 TypeScript 编译器 API 遍历 apps/*/src 与
packages/*/src,把「含汉字的字符串/模板字面量出现在非词典文件里」判为违规。词典白名单
只认 i18n*.ts / locale*.ts / locales/ 目录 / *-copy.ts,并设 MINIMUM_SOURCES=480 与
MINIMUM_LOCALE_OWNER_SOURCES=8 两条失效保险丝,防扫描范围塌成「零违规通过」。

存量 5732 处一次迁不完,故上棘轮基线 ui-i18n-baseline.json:按「文件 + 归一化片段 →
次数」记录(不按行号,重排代码不假红),基线内不报错、新增即 exit 1、基线里已消失的条目
提示删除;--write-baseline 可整体重录,--files 供 pre-commit 只扫暂存文件。真的不是界面
文案的中文串(模型提示词等)用行内 ui-i18n-allow 豁免,与既有 term-allow 同一套约定。

check-copy-terms.ts 从手写的 5 个词典文件扩到 260 个:共用 check-ui-i18n 的文件发现逻辑
(全部词典文件 + 基线里仍含文案的文件),判据也从「整行含中文」升级成 AST 字面量,注释与
正则里的中文不再误伤。扩覆盖暴露的 25 处存量命中同样进棘轮基线,改词属于产品决策不在本批。

形状借鉴 deepseek-harness(MIT)的 scripts/verify-client-ui-i18n.ts,来源与本地改造记在文件头。

接线:根 package.json 加 audit:ui-i18n(并入 lint 链)与 test:scripts(scripts 不是
workspace 包,pnpm -r 覆盖不到,故自带 tsconfig 与 node --test);lefthook pre-commit 加
staged-only 一步;verify.yml workspace job 加 pnpm test:scripts 一步。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
禁词门扩到 260 个文件后 pre-commit 要跑 5.45 秒,超出 commit 时预算。给
check-copy-terms.ts 补 --files 模式(与 check-ui-i18n.ts 同形,免掉全量 glob:
暂存文件里只有词典或两份基线里记着的文件才需要扫),lefthook 两个文案门都改成
staged-only,全量仍由 pnpm lint 与 CI 兜底。顺手把 relativizeToRoot 提成共用导出。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
把 apps/desktop-webview/src/workbench 全树的 `zh ? "中文" : "English"` 内联三元搬进
每个模块自己的 locales.ts(workbench/、army/、chat/、drive/、editor/、files/、inbox/、
kanban/、proposal/、schedule/、settings/、timeline/ 各一份)。形状照 deepseek-harness
的 per-package locales.ts:中文对象是 key 集事实源,英文对象 `satisfies
Record<keyof typeof zh, string>` 做编译期对齐——少键多键都编译不过。

渲染出来的文案一个字没改:搬家是纯表达式替换(三元换成词典查表),1698 个桌面端测试与
typecheck 全绿。词典函数第一参数收 `WorkHubLocale | boolean` 是过渡口子——这一层的渲染
函数历史上大量以 `zh: boolean` 传语言,改签名是另一件事,不和搬家混在一批。

棘轮基线随之收缩:5732 → 5041 处(-691)。剩下的多是带插值的模板字面量三元
(`zh ? \`共 ${n} 条\` : ...`),需要参数化词条,不是机械搬家能覆盖的。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
同一次机械搬家扩到 apps/desktop-webview/src/spotlight(controller、labels、ask-cuu、
14 个 view)与 src 顶层(登录、连接屏、重绑、Cuu 运行时等):新增 spotlight/locales.ts、
spotlight/views/locales.ts、src/locales.ts 三份词典,形状与工作台那批一致。

渲染文案一个字未改,1698 个桌面端测试 + typecheck 全绿。桌面端存量 2086 → 706(-66%);
全仓棘轮 5041 → 4352。禁词门基线里 3 条随文案搬进新词典,同步重录(文案本身没动)。

剩下的 706 处不是「三元」形状,机械搬家覆盖不到:
  - command-palette.ts(96)用 `{ "zh-CN": …, en: … }` 记录式双语表;
  - labels.ts / schedule 状态表用 `{ zh, en }` 映射;
  - cuu-preferences / pet-surface / cuu-qa-scenarios 是无条件中文串;
  - 带插值的模板三元(`zh ? \`共 ${n} 条\` : …`)要参数化词条。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
第一段(免费剪枝):压缩触发时先把历史里保留窗口之外、超过单条预算的 tool_result
剪成 head + 中英双语标记 + tail,重算上下文压力;够了就不发那次摘要请求(摘要走
独立计费的 context_compact 路由)。剪的是内容不是消息,tool_use/tool_result 配对
结构一个字都不动。

第二段(spill):超过单条上下文预算的结果先写进 <workdir>/.spill/<步号>-<工具>.txt,
模型看到的截断文本末尾附中英各一句定位提示,可以自己用 read_file 把原文读回来。
没有 workdir 或落盘失败时退回既有的纯截断话术。

行为变更:有 workdir 时超大工具结果的截断话术改成「截断正文 + .spill 定位提示」;
剪枝排在压缩次数耗尽的升级判定之前——能靠不花钱的剪枝撑过去就不叫醒人。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
新增 packages/contracts/src/domain/mcp.ts:transport 单值 literal(阶段 0 只
stdio)、status 三值枚举、trust_level 管理员断言上限(read_only/external_effect,
默认 external_effect,指挥者拍板与阶段 0 合并交付)、7 项静态体检 id 枚举与报告
结构、管理员/桌面完整 VM 与 web 只读投影(结构性不含 command/args/env/
secret_refs/cwd)。挂 barrel 导出并配 9 条契约测试。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
新增迁移 packages/db/migrations/0073_mcp_servers.sql:workspace 围栏、
server_name 工作区内唯一(构成模型可见工具名)、transport 结构性只允许
stdio、status/trust_level/timeout/tool_count 四条 CHECK、阶段 1 的
url/auth_header_* 四列现在建但被 CHECK 锁死成建了不能用;journal 收于
idx:73(when 严格递增)。

schema/core.ts 补 mcpServers 表定义并入 workHubTables(图计数 81→82);
新增 repositories/mcp-servers.ts(全部原语 workspace 围栏;setEnabled(true)
不冒充已验证连接,落 connect_failed 等真实试连接回填)。

schema.test.ts 更新 journal 尾部断言、新增 0073 迁移/schema 同步测试块;
新增 mcp-servers-repository.test.ts(query-recorder 风格,10 条)。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
记录 R26 M0 的决定与取舍:为何不复用 plugins 表、trust_level 合并进阶段 0
交付的拍板依据、transport 阶段 0 结构性只认 stdio 且 HTTP 相关列现在建但
锁死、setEnabled(true) 不冒充已验证连接等设计取舍,供 M1-M3 后续工包引用。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
同一次机械搬家扩到 packages/ui 与 apps/web:
  - packages/ui/src/gold-path/locales.ts(354 条)——route-components.ts 366 处 +
    render.ts 13 处,是全仓最大的一块内联文案;
  - packages/ui/src/locales.ts、packages/ui/src/workitem/locales.ts;
  - apps/web/src/locales.ts(234 条)——browser.ts 181 处 + routes.ts 19 处 + 9 个模块。

新词典与既有的 gold-path/i18n.ts 并存而不是往那 652 行里继续堆:新代码走 per-package 小
locales.ts(中文 key 集事实源 + 英文 satisfies 对齐),这是 B5 立项时定的方向。

渲染文案一个字未改。验证:pnpm -r typecheck 全绿;@workhub/ui 261、@workhub/web 168、
@workhub/cuu 50、@workhub/desktop-webview 1698、@workhub/api 2105(2 跳过)全通过。
棘轮 4352 → 3692。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
labels.ts 的 9 张 `["中文", "English"]` 元组对照表搬进 spotlight/locales.ts,拆成 zh/en
两张表:元组读不出「哪个是中文」,也没法让编译器盯两侧对称;拆开后英文表 satisfies
`{ [Group]: Record<keyof zh[Group], string> }`,少一个枚举取值或多一个都编译不过。

labels.ts 只剩「查不到怎么办」的决定(多数原样渲染裸值,agentStepPhaseLabel 兜底成「步骤」,
与迁移前逐一对应)与各枚举的具名入口。文案逐字未改,1698 个桌面端测试与 typecheck 全绿。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
剪枝的计划、判定与全部模型可见文本只有一份实现(loop/context-pruning.ts),
两套引擎各写一层薄投影:wire 的 tool_result 块 / pi 的 toolResult 文本块。
投影口径逐条对齐(纯字符串内容、text/thinking 正文、工具名加参数 JSON 长度),
否则同一段历史会算出不同压力、在「剪枝够不够」上分叉。

落盘与截断在 loop2 侧放在 execute 返回给 pi 的那一刻——与 loop.ts 把结果写进
messages 的那一刻是同一个「首次进入模型可见历史」的时点,wire 文本因此逐字节相同。
完整 ToolResult 仍挂在 details 上,step.toolResults / trace 不受影响。

剪枝在两边都排在「压缩次数耗尽」判定之后:loop2 的耗尽判定在 shouldStopAfterTurn,
那里拿不到即将发出的历史,双引擎同步优先于多救一次。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
packages/tools 新增 sanitizeModelFacingText:围栏标签中和(覆盖 loop.ts
FENCE_TAG_NAMES 的快照拷贝 + candidate_N)、tail/head-tail 两种截断风格、
可选控制字符清理。为 R25 M-MCP 客户端设计 4.2「共享一小块」铺路——
packages/plugin-host 与后续 packages/mcp-client(M1)共用同一份判断,
不再各包各写一份、口径慢慢漂移。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
同源缺口(R25 M-MCP 客户端设计 5.2):插件工具描述符文案早有 sanitizePluginText
中和+上限,但插件工具*执行结果*(translate.ts 的 renderToolContent)此前完全没有
——不像描述符,结果常被工人原样抄进 outputs/ 与自述,会被 packages/agent 的
fenced()/collectOutputExcerpts 装进围栏,一段字面 </outputs> 就能提前闭合围栏冒充指令;
且没有长度上限,插件想返回多少字节就进多少。

- to-tool-spec.ts 的 sanitizePluginText 改成调用 packages/tools 的
  sanitizeModelFacingText(tail 截断、不开围栏中和),行为逐字不变:43 条既有
  plugin-host 测试与 pnpm gen:expected 均无变化。
- translate.ts 的 renderToolContent 拆成原始拼接(renderToolContentRaw)+ 唯一导出、
  唯一返回口的外壳,在这一处统一做围栏标签中和 + 32KB 上限(PLUGIN_RESULT_MAX_CHARS,
  对齐设计文档给 MCP content[] 定的档位),render 抛错/非 text 占位/循环引用回退等
  既有分支同样受保护。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
记录 sanitizeModelFacingText 的落点选择、围栏标签清单单一事实源的已知负债
(packages/tools 不能依赖 packages/agent,本工包文件白名单也不含 packages/agent,
只能维护一份快照拷贝并双向注释指向对方)、M1(packages/mcp-client)后续应该
怎么接这个共享点,以及发现但按设计范围未修的相邻缺口
(plugin-host-client.ts 里插件 execute() 抛错时的 message 不经 renderToolContent)。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MCP 客户端接入的纯翻译包,与 packages/plugin-host 平级:MCP 服务器本来就是独立进程,
第三方代码从不进我们的模块图,不需要再套一层自己的宿主。

names.ts 只管一条不变式:两台服务器的两个不同工具,公开名永不坍缩。跨服务器重名是常态
(调查里 1470 台服务器出现 775 个重名工具),一旦坍缩,模型以为在调 A 的 search、
实际打到 B 上。做法是 `mcp__<服务器>__<工具>`,非法字符压下划线;**只有改名有损时**
(字符被压过或超了 64 字符)才追加 12 位 SHA-256 指纹,干净且够短的名字原样保留。
指纹喂的是原始输入而不是压缩后的串,否则压完同形的两个名字会得到同一个指纹。

反方向刻意不提供:原始工具名只在 tools/call 上用,公开名从不反解——反解需要「压缩可逆」,
而这件事本来就不可逆,留一个假装能反解的函数早晚会有人拿去拼一次真实调用。

另附两件给上层用的纯函数:名字预算(设置页实时预览,把人推向短名)与工具 id 分词
(人工保留门按词判高风险类,而服务器名也参与分词——一台叫 finance 的服务器,
它的每个工具都会被归到财务类,这件事得在添加服务器的界面上先说明白)。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
三条纪律:非 text 块留占位不静默丢(模型得知道有东西没带过来,否则会对着半份结果
编一个完整结论);过围栏中和;32KB 上限带截断标记。

中和这一层的理由要说准:工具结果本身进的是 tool_result 消息、不在围栏里,但它常被工人
原样抄进 outputs/ 与自述,而那两条确实要进评审围栏。一台服务器只要在结果里回一行字面的
`</outputs>`,就能在第二段提前闭合围栏、把后文送到围栏外冒充指令。中和成本 O(len),
堵的是这条二段式逃逸。

截断有两个不是讲究、是必须的细节:给标记预留位置,让「结果 ≤ 上限」这句话成立;
不切开代理对,孤立代理对在 JSON 序列化时会变成替换字符、个别 HTTP 客户端直接报编码错,
把一次本来成功的调用变成失败。

围栏标签表是从 packages/agent 的 loop.ts 复制来的(本包不依赖 agent),因此配了一条
对着 loop.ts 源码核对的漂移守卫:那边加了新标签而这边没跟上,测试会红。
这份复制与 plugin-host 的 sanitizePluginText 一起,是 M1b 的合并点。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
翻出来的 ToolSpec 走既有注册表通道,于是自动继承 canUse 双检、快照门、人工保留拦截
与审批链——服务器只提供能力实现,授权判断全在 API 进程这一侧。

读写分级按拍板落成一张真值表:最终 sideEffect = 管理员断言 AND 服务器自述
(readOnlyHint 为真且 destructiveHint 不为真才算只读),任何缺省都取最高风险。
两条道理要一起说才成立:不分级的话,external_effect 会让人工保留门对每一次调用都开升级,
而 MCP 生态里占大头的恰恰是只读检索;敢分级则是因为 MCP 规范自己写明 annotations 是
不可信提示,所以服务器只能在管理员划的线以内降风险,永远不能自己抬权限。

其余映射:description 去控制字符 + 4000 上限,缺省回落成一句说得清来源的话;
inputSchema 直通(删 $schema、type 缺省补 object),序列化超 32KB 或含取不到的 $ref
则丢弃该工具——截断一份 JSON Schema 只会产出无效 schema,让整轮请求 400;
minScope 拼成 mcp:<服务器>:<read|external_effect>,服务器名先压过一遍,
否则一个含 `*` 或 `:` 的名字能伪造出 mcp:*:read 绕开封禁规则。

整份清单级别的问题(raw 名重复、公开名坍缩)让整次翻译失败,不留半套。

qa/fixtures/ 是给 M5/M6 复用的常量夹具:两个工具正好把真值表跑成两端各一条,
外加一份把每条拒绝规则各命中一次的边界夹具。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
env:白名单不是黑名单。基座是插件宿主那六个键,其余一律不给。参考实现用的是黑名单
(过滤掉像凭据的键名、其余父环境全透传),这条已经被否决过——漏一个 MY_COMPANY_PAT
就全给出去了,测试里专门钉了这一条。

凭据不落库:服务器配置里的 env 过凭据形状黑名单(复用 plugin-host 那一份,不留副本),
真要给凭据就填指针不是值,API 进程 spawn 时从自己的 process.env 取。被引用的服务端变量
必须带 WORKHUB_MCP_SECRET_ 前缀——这条不立,引用式密钥就成了一个绕过整个白名单读任意
环境变量的原语(管理员填一个指向 COOKIE_SECRET 的引用就能把它递给第三方服务器);
也不能改用凭据形状黑名单来兜,因为 WORKHUB_MCP_SECRET_GITHUB 自己就命中 SECRET。
引用不存在时 fail-closed,不拿空串起进程——起来了只会在第一次调用时报一个远端 401,更难查。

precheck:不执行任何东西,命令存在性由调用方查好传进来。npx / pnpm dlx / yarn dlx /
bunx / uvx 一类「现下现跑」的启动器按拍板拦掉,错误文案教用户先在本机装好、再填装好之后
的路径——放行会让仓库的两条既有红线(安装期脚本、sandbox 的 npm exec 禁令)对同一类风险
给出相反答案。

体检项是 8 条不是设计稿表里的 7 条:多出来的 secret_ref_scope 就是上面那条前缀规则。
另附 mcpPrecheckErrorCode,把每条 block 映射到稳定错误码,省得展示层去解析英文诊断。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
真值表覆盖:谁被剪(保留窗口之外且超预算)、保留窗口至少 1 条、幂等(靠标记识别
而不是长度,剪完的文本天然略超预算)、配对结构不动(消息条数/角色/tool_use_id/
is_error 全查)、压力重算口径、落盘文件名与总字节上限、无 workdir 分支。

计费可观测两条:剪枝够用 → 摘要请求数为 0 且不占压缩配额;剪枝不够 → 恰好一次。

golden 新增 6 份(既有 expected 一个字节未变):两种截断话术、spill 定位提示、
两种剪枝正文、以及一段固定历史的剪枝真值表 JSON。gen:expected 二次运行无变化。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
记下四件在代码里看不出来的事:为什么判据是「含汉字的字面量」而不是上游那套文案属性识别、
为什么上棘轮基线而不是全量豁免、为什么禁词门的 25 处存量记账而不是就地改词,以及立项时
说的「53 个 key 不对称」经逐文件核实并不存在(四份词典早就是编译期强制对称)。

附剩余 3648 条按形状的普查表与「后来者怎么继续搬」的操作步骤。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
index.ts 只导出纯模块——照 plugin-host 刻意不 re-export ./host.js 的先例,把 SDK 关在
apps/api 侧那一个文件里:SDK 还在快速迭代,破坏性改版就只砸在那一个文件上,本包契约不动。

Note 里写清三条在设计稿之外收紧的判断(密钥引用前缀、$ref 只放行同文档片段、
minScope 里的服务器名要压过),以及 M1b 的两处合并点与给 M0/M3/M7 的硬信息。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts:
#	.github/workflows/verify.yml
三条场景走 B6 留下的 runBoth harness:落盘后的截断文本与定位提示逐字相同、
剪枝够用时两边都零摘要请求且剪掉的字符数相同、剪枝不够时两边都恰好一次。
剪枝事件补 context_chars 字段并纳入事件投影——两侧上下文投影口径一旦漂移,
等价性检查立刻红(已用两次 mutation 验证:截断预算差 1、otherChars 差 1 都被抓到)。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
collectOutputExcerpts / 交付物清单 / 变动行数统计都只走 workdir/outputs,
.spill/ 在 workdir 根下因此天然不被采集;这条用例把「天然」变成有门禁的事实。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mycyg and others added 6 commits September 5, 2026 23:29
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…快照与内容哈希,revert 也不删它

- SnapshotTakeInput / RevertSnapshotInput 增 excludeDirs(顶层目录名精确匹配,含分隔符的条目直接拒绝)
- listFiles 顶层跳过,takeFileSnapshot 与 hashWorkdir 共用;revert 整树换入后把当前的排除目录原样搬回
- agent-run-snapshots 与审计路由的 revert 传 [SPILL_DIR_NAME],不重复字面量
- 测试:.spill 不进快照目录、.spill 内容变化不改 contentSha256(CORE-04 去重仍生效)、revert 保留 .spill、不带参数行为不变

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mycyg
mycyg merged commit 812732b into main Sep 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant