维护者速读
os generate object <名字> 以前对名字不做任何检查。你给它 foo.bar,它报成功,然后写出两个编译不了的文件。这个流血已经止住了——PR #16724(卡 #16541)让它在写盘前把要写的代码交给 TypeScript 编译器,不通过就报错退出、一个字节都不写。
但止血没有回答一个问题:这个命令到底应该接受哪些名字?
今天三扇门三个答案:os create 卡 npm 的字符集;os generate 什么都不卡(现在只卡"写出来的东西编译不过");os init 在上游先把名字洗过一遍。同一个产品里,给对象起名这件事有三套规矩。
三个选项:
- A ── 只加闸:声明一个字符集,不合规的名字当场拒绝,把"能不能编译"留作兜底。名字不合规时用户会看到明确的拒绝。
- B ── 只加改写:像
os create 那样,把你给的名字自动改成一个合法标识符。用户不用改名字,但他给的名字和最终生成的代码里的名字会不一样,而且不告诉他。
- C ── 两个都做:先用闸拦掉一批,剩下的再改写。
本席推荐 A,并且建议直接复用 spec 已经给对象名声明的那套字符集(^[a-z_][a-z0-9_]*$),而不是照搬 npm 的——os generate object 的名字不是 npm 包名,两者本来就不是一回事。
⚠️ 一句必须讲清楚的:B 是三个里最危险的一个。它让"你写的名字"和"系统里真实存在的名字"悄悄分叉,而这正是 AI 批量写元数据时最容易踩、也最难发现的那类坑。
请选:A、B,还是 C?
Provenance
Filed by the domain:cli execution PM seat (#6024), session session_015QE8qk46e5CHJxyQEUjbf8, R71.
This question was surfaced by the delivery of #16541 (PR #16724). Triage had written on #16541 that it would route the acceptance surface into the decision inbox if the taker stopped and reported — verbatim: 「若承接者判断连这条拒绝都绕不开字符集裁定,⛔ 停下来在卡上报,我把接受面问题…路由进决策箱」. The taker correctly did not stop: it found the parseability refusal buildable without settling acceptance, so that condition never fired and the question would otherwise have been left unrouted. ⇒ Filed here rather than dropped.
⛔ domain:* and type deliberately left unset — those are triage's to grade.
Dedup: one semantic repo-scoped search returned 0 for the acceptance question. ⭐ Positive control on the same index returned 6 (#15892, #15816, #16373, #14705 and #16541 itself), so the zero is a reading, ⛔ not a silent index miss. #15892 (closed) ruled os create's identifier under maintainer option 2; #15816 (closed) is os create's npm-charset acceptance; #13838 (open) is os build's acceptance, deliberately untouched. None covers os generate.
Governing text: the four-axis decision framework in .claude/skills/pm-dispatch/SKILL.md (「声明即强制」, 「响亮拒绝优于静默容忍」) and the startup-stage ruling of 2026-08-04.
What already landed, and what it deliberately did not decide
PR #16724 makes os generate refuse to exit 0 when the TypeScript it would write does not parse. It asks the compiler about the actual emitted bytes — ⛔ not a charset opinion — and it ⛔ does not port sanitizeIdentifier from #15892. So today's accepted set moved only by the names whose emission was already broken.
⚠️ Attribution: the measurements below are the delivering dev's, reported on #16541 and in PR #16724's body; this seat verified the PR's file surface, form and rulings but did ⛔ not independently re-drive these commands.
| driven |
verdict |
os g object class |
exit 1 — const class: is not a declaration |
os g view class |
exit 0 — that generator emits const classViews:, which parses |
os g object order-line |
exit 0, unchanged |
⭐ That middle row is the decision in miniature: os generate view class is accepted today and writes a barrel line binding the reserved word class as a module export alias. It parses, so the landed refusal does not touch it — and it is exactly the case an acceptance gate would rule on.
四棱
① 实际业务需求. ⚠️ No measured pull for any particular charset. The harm that was real — a command reporting success while writing broken files — is already fixed. What remains is a consistency and predictability question, not an outage. ⇒ This axis does not, on its own, justify building a new vocabulary; it does justify not leaving three doors with three answers.
② 项目长远合理性. Contract-first says a scaffolder has one declared accepted set, stated once. Today os create gates on npm's charset, os generate gates on nothing, os init sanitises upstream (sanitizeNamespace, ^[a-z][a-z0-9_]{1,19}$ — the dev's reading). Three doors, three answers is the divergence class this repo keeps paying to burn down. ⇒ Points at A: one declared gate. B entrenches the divergence and adds a silent transform on top of it.
③ 防 AI 写元数据犯错 — the axis that decides this one. 「声明即强制,绝不让 AI 声明一个运行时不兑现的能力」 and 「契约收紧优于消费端宽容 —— 宽容恰是 AI 批量犯错被掩盖的温床」. B is consumer-side tolerance in its purest form: the author writes foo.bar, the system silently stores fooBar, and every later reference the author writes by hand is wrong in a way nothing announces. For an agent generating metadata in bulk, that divergence multiplies silently. A refuses loudly at the door. ⇒ Strongly A.
④ 创业阶段不扩散需求. 「能力扩张默认从紧」. ⛔ Do not mint a third charset. The spec already constrains an object name to ^[a-z_][a-z0-9_]*$ (the dev's reading of the schema), so A can be built by pointing at a constraint that already exists — zero new declared surface. Adopting npm's charset instead would import a rule from a domain this name does not live in. ⇒ A, reusing the spec's own charset.
四棱同向 (A). ① is neutral-to-weak, ②③④ all point at A, and the long-term axis (②) leads the recommendation as the weighting requires. ⛔ This is still escalated rather than self-adjudicated: it sets a published command's accepted set, which is the manual floor (contract/acceptance change), ⛔ not the auto-adjudication lane.
Options
⚠️ Whichever is chosen, it can be added on top of the landed refusal without moving it — PR #16724 was built so this stays open.
维护者速读
os generate object <名字>以前对名字不做任何检查。你给它foo.bar,它报成功,然后写出两个编译不了的文件。这个流血已经止住了——PR #16724(卡 #16541)让它在写盘前把要写的代码交给 TypeScript 编译器,不通过就报错退出、一个字节都不写。但止血没有回答一个问题:这个命令到底应该接受哪些名字?
今天三扇门三个答案:
os create卡 npm 的字符集;os generate什么都不卡(现在只卡"写出来的东西编译不过");os init在上游先把名字洗过一遍。同一个产品里,给对象起名这件事有三套规矩。三个选项:
os create那样,把你给的名字自动改成一个合法标识符。用户不用改名字,但他给的名字和最终生成的代码里的名字会不一样,而且不告诉他。本席推荐 A,并且建议直接复用 spec 已经给对象名声明的那套字符集(
^[a-z_][a-z0-9_]*$),而不是照搬 npm 的——os generate object的名字不是 npm 包名,两者本来就不是一回事。请选:A、B,还是 C?
Provenance
Filed by the
domain:cliexecution PM seat (#6024), sessionsession_015QE8qk46e5CHJxyQEUjbf8, R71.This question was surfaced by the delivery of #16541 (PR #16724). Triage had written on #16541 that it would route the acceptance surface into the decision inbox if the taker stopped and reported — verbatim: 「若承接者判断连这条拒绝都绕不开字符集裁定,⛔ 停下来在卡上报,我把接受面问题…路由进决策箱」. The taker correctly did not stop: it found the parseability refusal buildable without settling acceptance, so that condition never fired and the question would otherwise have been left unrouted. ⇒ Filed here rather than dropped.
⛔
domain:*andtypedeliberately left unset — those are triage's to grade.Dedup: one semantic repo-scoped search returned 0 for the acceptance question. ⭐ Positive control on the same index returned 6 (#15892, #15816, #16373, #14705 and #16541 itself), so the zero is a reading, ⛔ not a silent index miss. #15892 (closed) ruled
os create's identifier under maintainer option 2; #15816 (closed) isos create's npm-charset acceptance; #13838 (open) isos build's acceptance, deliberately untouched. None coversos generate.Governing text:the four-axis decision framework in.claude/skills/pm-dispatch/SKILL.md(「声明即强制」, 「响亮拒绝优于静默容忍」) and the startup-stage ruling of 2026-08-04.What already landed, and what it deliberately did not decide
PR #16724 makes
os generaterefuse to exit 0 when the TypeScript it would write does not parse. It asks the compiler about the actual emitted bytes — ⛔ not a charset opinion — and it ⛔ does not portsanitizeIdentifierfrom #15892. So today's accepted set moved only by the names whose emission was already broken.os g object classconst class:is not a declarationos g view classconst classViews:, which parsesos g object order-line⭐ That middle row is the decision in miniature:
os generate view classis accepted today and writes a barrel line binding the reserved wordclassas a module export alias. It parses, so the landed refusal does not touch it — and it is exactly the case an acceptance gate would rule on.四棱
① 实际业务需求.⚠️ No measured pull for any particular charset. The harm that was real — a command reporting success while writing broken files — is already fixed. What remains is a consistency and predictability question, not an outage. ⇒ This axis does not, on its own, justify building a new vocabulary; it does justify not leaving three doors with three answers.
② 项目长远合理性. Contract-first says a scaffolder has one declared accepted set, stated once. Today
os creategates on npm's charset,os generategates on nothing,os initsanitises upstream (sanitizeNamespace,^[a-z][a-z0-9_]{1,19}$— the dev's reading). Three doors, three answers is the divergence class this repo keeps paying to burn down. ⇒ Points at A: one declared gate. B entrenches the divergence and adds a silent transform on top of it.③ 防 AI 写元数据犯错 — the axis that decides this one. 「声明即强制,绝不让 AI 声明一个运行时不兑现的能力」 and 「契约收紧优于消费端宽容 —— 宽容恰是 AI 批量犯错被掩盖的温床」. B is consumer-side tolerance in its purest form: the author writes
foo.bar, the system silently storesfooBar, and every later reference the author writes by hand is wrong in a way nothing announces. For an agent generating metadata in bulk, that divergence multiplies silently. A refuses loudly at the door. ⇒ Strongly A.④ 创业阶段不扩散需求. 「能力扩张默认从紧」. ⛔ Do not mint a third charset. The spec already constrains an object
nameto^[a-z_][a-z0-9_]*$(the dev's reading of the schema), so A can be built by pointing at a constraint that already exists — zero new declared surface. Adopting npm's charset instead would import a rule from a domain this name does not live in. ⇒ A, reusing the spec's own charset.四棱同向 (A). ① is neutral-to-weak, ②③④ all point at A, and the long-term axis (②) leads the recommendation as the weighting requires. ⛔ This is still escalated rather than self-adjudicated: it sets a published command's accepted set, which is the manual floor (contract/acceptance change), ⛔ not the auto-adjudication lane.
Options
^[a-z_][a-z0-9_]*$rather than minting a rule.os createhas sinceos create plugin foo.baremitsexport const foo.barPlugin— an npm-legal name that renders un-parseable TypeScript #15892, keeping today's wide acceptance.