Repository navigation
未知键的「你是不是想写」兜底对 camelCase 键系统性偏弱:输入被小写化、候选没有 —— 每个大写字母白扣一次编辑距离 #4990
Description
Activity
Blocked-by: #4971
排程记录(会话
session_01FTszibd6C8sUCCZnM4VcrL,C 包 #5163 接收方,PM 循环第 1 轮):本单与 #4971 严格串行,已挂pm:blocked,#4971 的 PR 合入 main 后解锁派发。已知雷区,派发时写进派发词:- 同文件冲突:formatZodError 把 union 分支的拒绝信息压成 "Invalid input" —— #4001 策展的散文在 CLI 路径上到不了作者 #4971 正在翻转
packages/spec/src/automation/state-machine.test.ts的 CONTROL pin;本单的建议算法改动会波及断言建议文案的 pin(webhook.test.ts/etl.test.ts/control-flow系列),两单同时在飞会在错误散文断言上互踩。 - 正文自陈的纪律:横切改动,独立成 PR,跑全 spec 套件;不搭任何收紧批次。
- 必加不变量测试(issue 实质):同一键的全小写形式不得比正确大小写形式得到更好建议。
- 顺带排查
data/object.zod.ts的suggestKey是否同病;ui/responsive.zod.ts里批 13 的hideOn显式 alias 是逐例绕行,修复落地后评估是否移除(若移除,注意其注释里记的测量值要保进测试)。 - 触 zod 文件,已在 C 包 program card:协议工具链/门禁车道(
domain:spec-tooling)—— 整包移交待接收 #5163 卡上向 spec 车道做过协调声明(2026-08-04),无需重复留言。
Generated by Claude Code
- 同文件冲突:formatZodError 把 union 分支的拒绝信息压成 "Invalid input" —— #4001 策展的散文在 CLI 路径上到不了作者 #4971 正在翻转
解锁:
pm:blocked已摘除,Blocked-by: #4971条件满足会话
session_01FTszibd6C8sUCCZnM4VcrL。#4971 的 PR #5342 已于刚才经合并队列合入 main(546ab3c49),两单在packages/spec/src/automation/state-machine.test.ts上的冲突面解除,本单恢复可派。排期:本单是横切改动(会动 #4001 各批已落地的错误信息文案,
webhook.test.ts/etl.test.ts/control-flow系列等断言 message 内容的 pin 都要跟改),属 build-heavy;当前容量已满(#4912 的 os-regen 同步 + #5269 + #5063 三单在飞,其中同步单同样 build-heavy)。不与另一个 build-heavy 单并发,等空槽即派 —— 这是资源纪律,不是优先级下调:本单是 C 包升级体验 4 单里的第 2 单,#5163 卡片给的顺序就是它。派发时写进派发词的要点(此前记录的雷区依然有效):
- 正文自陈的纪律:横切改动、独立成 PR、跑全 spec 套件,不搭任何收紧批次。
- 必加不变量测试(本单实质):同一键的全小写形式不得比正确大小写形式得到更好建议。
- 顺带排查
data/object.zod.ts的suggestKey是否同病(同一套长度相对上界的另一处实现)。 ui/responsive.zod.ts里批 13 为hideOn→hiddenOn写的显式 alias 是逐例绕行;修复落地后评估是否移除,若移除,注意其注释里记的测量值要保进测试。⚠️ 新增读数陷阱(本会话今日两次踩到,AREA_REQUIRED_PERMISSIONS_RETIRED 的处方在 #4722 之后过时:仍写着「the server does not walkareas」 #4749 / protocol-17 的 migration rationale 仍写着「the server does not walkareas」,并投影进生成的升级指南 #5337 各一次):packages/spec的处方与建议文案一律是跨行拼接的字符串字面量,行式git grep看不见跨行短语,零命中不等于不存在。核验一律用邻近词反查,或直接对运行期组装后的字符串断言。- 触 zod 文件,已在 C 包 program card:协议工具链/门禁车道(
domain:spec-tooling)—— 整包移交待接收 #5163 卡上向 spec 车道做过协调声明,无需重复留言。
Generated by Claude Code
认领:PM 循环第 2 轮(C 包 #5163 升级体验第 2 单)
会话:session_01FTszibd6C8sUCCZnM4VcrL
分支:claude/issue-4990-camelcase-suggestions
Worktree:objectstack-issue-4990
域:domain:spec-tooling
文件面:packages/spec/src/shared/suggestions.zod.ts(打分归一化)、packages/spec/src/data/object.zod.ts(suggestKey同病排查)、因建议文案变化而变红的 pin 测试(webhook.test.ts/etl.test.ts/control-flow系列等)、.changeset/*。⛔ 不碰shared/error-map.zod.ts(#4971 刚落地)、⛔ 不碰ui/app.zod.ts(#4749 刚落地)、⛔ 不碰packages/spec/scripts/**(#4912 在飞)。Blocked-by: #4971已解除(PR #5342 于546ab3c49合入 main)。容量已腾出(#4912 的 os-regen 同步收工),按 #5163 卡片顺序派发。
Generated by Claude Code
PM 复核(第 2 轮):ACCEPT → PR #5363
会话
session_01FTszibd6C8sUCCZnM4VcrL。⚠️ 先记一条:我在派发词里点名要求的那条测试是空转的,dev 证伪了它我把 issue 的原话当成硬要求写进了派发词 ——「同一个键的全小写形式不应比正确大小写形式得到更好的建议」,并要求「写成在今天的代码上会失败」。dev 照做后发现它在旧代码上不可能失败。PM 独立核实:
$ git show origin/main:packages/spec/src/shared/suggestions.zod.ts | grep -n toLowerCase 70: const normalized = input.toLowerCase().replace(/[-\s]/g, '_');旧实现第一步就把输入小写化,于是
suggest(T)与suggest(T.toLowerCase())坍缩成同一次调用,462 个探针上「全部通过」,而缺陷完好无损。真正的不对称不在输入的两种拼法之间,而在声明键的两种拼法之间:同一个笔误,对着
hiddenOn判一个结果、对着hiddenon判另一个,因为只有候选保留了大写、每个大写都向作者收费 —— 这才是标题里「对 camelCase 键系统性偏弱」的机制。dev 把不变量改成钉这个,旧代码上 462 例破坏 55 例,且双向破坏(bordeRradius对着驼峰borderRadius能解析、对着扁平borderradius反而不能,大写恰好帮了忙)。这条更正比原测试有价值得多,记在这里免得下一个读本 issue 的人照抄那句空转的措辞。
零 pin 变红 —— 而它没有把这当成通过
我在派发词里的硬要求是「⛔ 不许把变红的 pin 批量改成新输出,每条都要判断更好还是更差」。实际零条 pin 变红。dev 没有就此宣布安全,而是把这件事当成可疑并另造了量化证据:在全部 325 组真实候选集(递归采集
src/index每个 ZodObject 的 shape)上生成 16734 个 camelCase 笔误探针逐个新旧对比:数量 结果不变 16374 新增建议 329(正确 328 / 错误 1) 失去建议 0 改变选中项 31(更准 30 / 变差 1) 两处代价如实点名,且论证为折叠后的真实平局而非排序退化(
yxAis两项指标双双打平由声明序决定;mxaRows → minRows是「原本没有任何建议」处新增的一个错提示)。「零 pin 变红」在一个横切文案改动上本该令人不安,把它转成 16734 次实测,是比我要求的更强的形式。顺带修掉一个 issue 未记录的同源缺陷
distance > 0过滤器的本意是「别把作者写过的字符串原样回显」,但因为输入被小写化,maxLength这种写对了的键与候选距离是 1 而不是 0,过滤器根本没拦住 —— 旧实现会把作者写对的键原样回显成「你是不是想写maxLength」。这是反向验证时才浮出来的。反向验证:预测 2 红实得 4 红,如实记录偏差
dev 事先声明方向,实测多出两条:一条是上述未记录的回显缺陷,另一条是它自己第一版那个空转的不变量。把预测落空写出来而不是事后改预测,这一点是对的。
两处「同病」排查按要求做了
data/object.zod.ts的suggestKey—— 不同病,PM 核实1816行本就editDistance(unknown.toLowerCase(), key.toLowerCase()),两侧都折叠。已补测试锁定该性质,防两个 suggester 日后分叉。- 批 13 的逐例 alias
hideOn: 'hiddenOn'—— 已退役(现靠距离本身即可解析),且按我的要求把注释里的实测值转为断言存进responsive.test.ts而非删掉;hidden: 'hiddenOn'保留,理由正确:它是另一个词,任何编辑距离都够不着。
消费半径
按 PR #5046 的教训外扩:
packages/lint三个 write-validator(57 files / 1194 tests 全绿)、service-automation断言 spec 报错文案(149 tests 全绿)。spec 全套 308 files / 7903 tests。生成物污染
authorable-surface.base.json被pnpm build的gen:schema改写,dev 提交前逐文件读 git status 发现并还原,未混入本 PR;且没有另开单,而是到 #5358 补了独立复现数据 —— 并补充了两点:它经pnpm build触发(不止 #5358 记的--check路径),且因为「构建 spec 是跑任何下游包测试的前置」,污染发生在一个正常且被鼓励的工作流步骤里。查重纪律与信息增量都做对了。CI 全绿后入队。这是本会话的最后一单。
Generated by Claude Code
已落地 —— PR #5363 经合并队列合入 main(
b4ad98435)会话
session_01FTszibd6C8sUCCZnM4VcrL。落地判据复核:$ git show origin/main:packages/spec/src/shared/suggestions.zod.ts | sed -n '68p' const foldForScoring = (value: string): string => value.toLowerCase().replace(/[-\s]/g, '_'); # 两侧折叠,回显仍用候选原始拼写 $ git grep -c "hideOn: 'hiddenOn'" origin/main -- packages/spec/src/ui/responsive.zod.ts 0 # 批 13 的逐例 alias 已退役 $ git grep -c "hideOn\|hiddenon" origin/main -- packages/spec/src/ui/responsive.test.ts 8 # 其注释里的实测值已按要求转为断言,未删除三处都对。C 包升级体验 4 单收官(#4971 / #4749 / #4912 / #4990 全部落地)。
本单最值得留给后来者的一条,重申一遍:issue 正文那句自证测试的措辞是空转的 —— 比较
suggest(T)与suggest(T.toLowerCase())在旧代码上不可能失败,因为旧实现第一步就把输入小写化。真正的不对称在声明键的两种拼法之间,不在输入之间。照抄那句措辞会得到一个全绿而无用的测试。
Generated by Claude Code
- added a commit that references this issue
on Oct 7, 2026
从 #4001 批 13 拆出(发现,不是本批修复范围)。影响 #4001 整个战役已经落地的每一条未知键错误信息的兜底建议质量。
缺陷
packages/spec/src/shared/suggestions.zod.ts的findClosestMatches():输入被小写化,候选没有。 于是候选键里的每一个大写字母都要额外付一次替换代价。而
strictUnknownKeyError给的预算是长度相对的Math.max(2, Math.floor(key.length / 3))—— 对短键就是 2。两者一叠加,短的 camelCase 键上一个再普通不过的笔误就够不着了。按 AGENTS.md Prime Directive #3,「TS config keys → camelCase」是全仓约定,所以受影响的是可授权面的大多数键。
实测(
origin/mainfe83042)第二行是这个缺陷的完整刻画:把键写成全小写的作者,拿到的建议比写对了大小写、只错一两个字母的作者更好。
暴露面是「真实距离 + 候选里的大写字母数 超过预算」的那一类,集中在较短的 camelCase 键上。
hideOn→hiddenOn就是标准样本:一个语义完全正确、只是少了个词尾的键,得到的信息里没有任何指向正确拼法的内容 —— 而这一战役的整个卖点,恰恰是「不只是大声,而是可修」。建议修法
在同一处对候选做同样的归一化,并且只用于打分、回显仍用候选的原始拼写:
注意两点:
data/object.zod.ts的suggestKey是同一套长度相对上界的另一处实现,要一起检查是否同病。webhook.test.ts/etl.test.ts/control-flow系列)。属于横切改动,应独立成 PR 并跑全 spec 套件,不适合搭在某一批收紧上。现状缓解
#4001 批 13 在
ui/responsive.zod.ts里为它自己遇到的那一例写了显式 alias(hideOn: 'hiddenOn'),并在注释里记了测量值。那是逐例绕行,不是修复 —— 每个 schema 都要为兜底够不着的短 camelCase 键手写 alias,正是strictObject引入时想消掉的那种重复。相关
ActionParamSchema,这套错误工厂的模板)packages/spec/src/shared/suggestions.zod.ts、packages/spec/src/data/object.zod.ts按 AGENTS.md Prime Directive #10 归档,未指派。