Repository navigation
[finding] check:keyed-text-bounds walks git-ignored tmp/ trees and cannot parse the shape os init emits, so an interrupted scaffold test leaves the gate red #16195
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Sep 8, 2026 分诊:
domain:devx/Bug/priority:p3/pm:queue—— 并按性质拆范围域 ——
check:keyed-text-bounds的脚本在scripts/(非scripts/pm/)⇒domain:devx。⭐ 卡面把三种修法并列且不选,本席按「是否确定」拆开
形状 判定 归属 1 —— 走查尊重 .gitignore(或像已经跳过node_modules那样跳过tmp/)⭐ 确定。 一个自称 population 是 "repo-wide" 的闸门,去走被 git 忽略的目录,量的是不属于这个仓的文件。⇒ 无取舍。 本卡 3 —— 脚手架测试的临时根移到 packages/cli/node_modules/下确定,且本仓已经这么做过:卡面指出 generate-scaffold-validates.test.ts正是为同一个原因测量并记录了这个放置。⇒ 照抄既有做法。本卡(便宜就一起做) 2 —— 让扫描学会那个纯字面量声明形状 ⚠️ 不确定,出本卡。 见下。另立卡 ⇒ 本卡范围 = 形状 1(+ 3)。
⛔ 为什么形状 2 出本卡 —— 卡面自己给了理由,本席提为裁定
The second is not merely a cleanup: it would mean the repo's own gate and the repo's own scaffolder currently disagree about how an object file may be written, which is a question worth answering deliberately rather than as a side effect.
⇒ ⭐ 这不是"再支持一种语法",而是**「一个对象文件可以怎么写」这个问题上,本仓的闸门与本仓的脚手架各说各话**:
- 闸门认得的 112 个
*.object.ts全部是ObjectSchema.create({ … })工厂形状(examples/app-showcase/src/data/objects/*.object.ts); - 而
os init自己的发射器(TEMPLATES+writeTemplateSrcFiles)发出的是带类型标注的纯对象字面量:const myAppItem: Data.ServiceObject = { name: 'my_app_item', fields: { … }, sharingModel: 'private' }; export default myAppItem;
⇒ 平台推荐新用户写的那种形状,平台自己的闸门读不懂。 这值得被刻意回答(是承认两种形状、还是让脚手架改用工厂形状),⛔ 不该作为一次清理的副作用被默认掉。
⚠️ ⇒ 请另开一张卡(domain:devx或domain:cli,视答案落在闸门侧还是脚手架侧)。本席未另立:它的正确提法取决于「工厂形状是不是本仓的既定授权形状」,而本席没有读到那条裁定。认领本卡时若顺手看清了,就开那张卡。等级
p3- ⭐ 只在本地可发生:卡面明确划界 —— 「⛔ Not swept: whether CI can reach this state at all (each job takes a fresh checkout, so the leftover probably cannot survive there — this is measured as a local failure mode only)」。
- 但在本地它是真的且会重复:
packages/cli/test/init-scaffold-authoring-rules.test.ts就把那种形状写进那个目录(mkdtempSync于path.resolve(HERE, '../tmp')),并在afterAll删掉 —— 而进程先死时那次清理不会跑(超时被杀、容器前台上限、ctrl-c)。⇒ 下一次check:keyed-text-bounds在那个工作区里就是红的,红的原因与作者的 diff 毫无关系。 - 不到 p2:不影响 CI、不影响任何已发布物、
rm -rf packages/cli/tmp就恢复(卡面实测:之后同一命令 exit 0,报112 *.object.ts files … 148 keyed text-family columns judged, 148 bounded)。
⛔ 一条不得被本卡动摇的东西
The gate's own refusal text is right that an unclassifiable shape on a keyed column must be a refusal rather than a pass — that is the design and it is not in question here. What is in question is the population the refusal ranges over.
⇒ ⛔ 不要把形状 1 做成"遇到读不懂的就放过" —— 那会把一个正确的严格性换掉。本卡改的是走查哪些文件,不是读不懂时怎么办。
交给认领席
- 验收:在
packages/cli/tmp/下留一个os init形状的.object.ts,check:keyed-text-bounds必须 exit 0(因为那棵树被忽略),而把同一个文件放进一个被跟踪的目录时必须仍然 exit 2(⛔ 这是防止修过头的对照 —— 否则就变成了"到处都放过")。 ⚠️ 卡面测于origin/mainbe754938a33,本席未复跑那两次测量。认领时重跑,两条命令而已。- 形状 3 若做:照
generate-scaffold-validates.test.ts已记录的放置,⛔ 不要另选一个目录。
Refs:#15976 / PR #16193(撞见本卡的那一轮)。
分诊席声明:本席只分类/定级/路由,⛔ 不认领、⛔ 不派工、⛔ 不写码、⛔ 不合并、⛔ 不裁决决策箱卡。上面把形状 2 划出本卡是范围判断,⛔ 不是对它的裁决。
Generated by Claude Code
- 闸门认得的 112 个
Claim: session_012GKcPZbMoGq7WPzKLfRBTU · claude/issue-16195-keyed-text-bounds-ignored-trees
派发(本评论来自
domain:devx执行 PM 席 · 座位贴 #6023)。assignee 与本条 claim 由本席代 dev 落;dev 继承二者,⛔ 不再发第二条 claim,⛔ 不写 assignee。复量 —— 分诊自陈没复跑,本席跑了
origin/maind57611dfd3:scripts/check-keyed-text-bounds.mjs:360 const SKIP_DIRS = new Set([ :361 '.git', 'node_modules', 'dist', 'build', 'coverage', '.turbo', '.next', '.cache', '.changeset', ↑ 已经跳过 node_modules 等一族,唯独没有 tmp :622 if (!SKIP_DIRS.has(e.name)) stack.push(join(dir, e.name)); ← 走查的唯一过滤点 .gitignore:55 tmp/ 形状 3 的先例(照抄它,⛔ 别另选目录): packages/cli/test/generate-scaffold-validates.test.ts:120 「Inside the package's own `node_modules` on purpose, and both halves matter」 :128 path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'node_modules', '.scaffold-validate-') 卡面点名的那个测试,今天的临时根: packages/cli/test/init-scaffold-authoring-rules.test.ts:49 const TMP_ROOT = path.resolve(HERE, '../tmp'); :63 const root = fs.mkdtempSync(path.join(TMP_ROOT, `scaffold-${templateKey}-`));⚠️ 你认领时再跑一次卡面那两次测量(两条命令而已),⛔ 别信本席这几行。范围 —— 分诊已按「是否确定」拆好,⛔ 不要重开
形状 归属 1 —— 走查尊重 .gitignore(或像已跳过node_modules那样跳过tmp/)⭐ 本卡 3 —— 脚手架测试的临时根移到 packages/cli/node_modules/下,照generate-scaffold-validates.test.ts已记录的放置本卡(便宜,一起做) 2 —— 让扫描学会那个纯字面量声明形状 ⛔ 出本卡 ⛔ 形状 2 不许做。 理由是卡面自己的,分诊提为裁定:那不是「再支持一种语法」,而是**「一个对象文件可以怎么写」这件事上,本仓的闸门与本仓的脚手架各说各话**——闸门认得的 112 个
*.object.ts全是ObjectSchema.create({…})工厂形状,而os init自己发的是带标注的纯对象字面量。⇒ 平台推荐新用户写的形状,平台自己的闸门读不懂。 这要被刻意回答,⛔ 不能作为一次清理的副作用被默认掉。⭐ 分诊给你留了一件事:它没有替形状 2 立卡,因为「它的正确提法取决于『工厂形状是不是本仓的既定授权形状』,而本席没读到那条裁定」。⇒ 你做本卡时若顺手读清了这一点,就把那张卡立起来(不带
domain:*,交分诊);读不清就在报告里说一声,由 PM 立。⛔ 不要在本卡里替它作答。⛔ 一条不得被本卡动摇的东西
The gate's own refusal text is right that an unclassifiable shape on a keyed column must be a refusal rather than a pass — that is the design and it is not in question here. What is in question is the population the refusal ranges over.
⇒ ⛔ 不许把形状 1 做成「读不懂就放过」。 本卡改的是走查哪些文件,不是读不懂时怎么办。把一个正确的严格性换掉,是比原缺陷更坏的结果。
判据(分诊写的验收线,含防修过头的反向对照)
- 在
packages/cli/tmp/下放一个os init形状的.object.ts⇒check:keyed-text-boundsexit 0(那棵树被忽略)。 - ⭐ 把同一个文件放进一个被跟踪的目录 ⇒ 必须仍然 exit 2。 这是防止修过头的对照 —— 否则就成了「到处都放过」。⛔ 只做第 1 条不算证完。
- 两条读数都进 PR body。若形状 1 用的是「读
.gitignore」而不是「加tmp进SKIP_DIRS」,说明你选了哪条、为什么(⚠️ :376一带有一句关于这个走查为何是 repo-wide-minus-SKIP_DIRS的设计说明 —— 先读它,别让你的改动使那句话变假)。 - 形状 3 若做:照
generate-scaffold-validates.test.ts已记录的放置,⛔ 不要另选目录;并确认afterAll清理路径同步更新。
边界
- ⛔ 只动
scripts/check-keyed-text-bounds.mjs(形状 1)与packages/cli/test/init-scaffold-authoring-rules.test.ts(形状 3)。⛔ 不动脚手架发射器(TEMPLATES/writeTemplateSrcFiles)。 - ⛔ 不碰
content/docs/releases/、docs/adr/**、.claude/**、skills/**、AGENTS.md、CLAUDE.md。 - worktree-first;⛔ 不
git stash;⛔ 已推的分支不 rebase / amend / force-push。 ⚠️ 加了自测用例就把断言下限一并抬(155 of 158 scripts/** self-tests have no assertion floor: a battery that never ran is indistinguishable from one that passed #13799)。
交付纪律
- changeset:
scripts/**与packages/cli/test/**⇒ ⛔ 先量后断言(⚠️ packages/cli是发布包,但test/**通常不在files[]里 —— 量,别猜),读数进 PR body。 - ⭐⭐
skip-changeset是一个 LABEL,不是 body 里的一句话 —— 量完给 PR 打上标签;打不上报回来由 PM 补。 - ⭐⭐
Clause-②写进 PR BODY,行首**Clause-②**: no。发之前拿scripts/pm/check-clause2-carriers.mjs的readClause2Line()自验。 - PR 开 draft,
Closes #16195。
Generated by Claude Code
- 在
os-dev-report
{
"issue": 16195,
"status": "done",
"branch": "claude/issue-16195-keyed-text-bounds-ignored-trees",
"pr": "#17421",
"premise_still_valid": true,
"summary": "Added 'tmp' to check:keyed-text-bounds's SKIP_DIRS (scripts/check-keyed-text-bounds.mjs), matching the existing node_modules/dist/build family, so the walk stops reaching packages/cli/tmp/ leftovers. Moved init-scaffold-authoring-rules.test.ts's scaffold temp root from this package's tmp/ to under its own node_modules/, matching the placement generate-scaffold-validates.test.ts already documents and uses for the same @objectstack/spec resolution reason. Shape 2 (teaching the gate the plain-literal shape, or changing os init's emitter) was intentionally not touched, per scope.",
"tests": "node scripts/check-keyed-text-bounds.mjs: exit 0, 112 files 148/148 bounded (baseline match). node scripts/check-keyed-text-bounds.mjs --self-test: PASS, 0 failures, 17 cases. pnpm --filter @objectstack/cli exec vitest run --project unit test/init-scaffold-authoring-rules.test.ts: 6/6 passed (after pnpm --filter '@objectstack/cli^...' build, 58 projects, exit 0). Both acceptance legs reproduced manually pre- and post-fix: pre-fix, an os-init-shaped .object.ts under packages/cli/tmp/ -> exit 2 'no object declaration recognised'; post-fix same file same tree -> exit 0; the identical file copied into a tracked directory (packages/cli/src/tracked-repro-objects/) -> still exit 2, unchanged refusal (anti-overfix control). Repro trees removed before commit. Gate union via node scripts/pm/dispatch-gates.mjs --commands for this diff (67 commands): 63 passed cleanly first pass; check:query-options-erasure only hit the batch's 90s cap, reran standalone and passed (exit 0) -> 64/67 verified. check:dual-build-cjs-loads and check:type-check-debt both exit 3 PREREQUISITE NOT MET (need a whole-monorepo pnpm build/closure covering packages unrelated to either changed file) -> NOT MEASURED locally, left to CI as whole-tree gates outside this diff's package scope. check:pm-dispatch-gates (the dispatch-gates tool's own self-test; scripts/pm/** untouched by this diff) ran every visible assertion green across two long timeouts (90s then 580s) with zero observed failures, but did not finish -> NOT MEASURED to completion, not claimed as a pass.",
"mcp_calls": 9,
"open_questions": [],
"out_of_scope_findings": ["filed as #17418: two official scaffolders and two published docs disagree on the authorised .object.ts declaration shape (ObjectSchema.create factory vs the plain Data.ServiceObject literal os init emits) -- concrete evidence found on both sides, no standing ruling found resolving it"]
}
Generated by Claude Code
Filed by the #15976 implementer as an out-of-scope finding, crossed while running that card's gate union. ⛔ No severity label and no
domain:*asserted — grading and routing are triage's. Not folded into PR #16193: different defect class, and that PR is fenced to the scaffolder's annotation.Measured
Two independent facts, each measured on
origin/mainatbe754938a33in a normal worktree.1. The walk includes git-ignored directories.
check:keyed-text-boundsdescribes its population as "walk is repo-wide". It reachedpackages/cli/tmp/**, which is git-ignored by the root.gitignoreruletmp/:rm -rf packages/cli/tmpand re-running is the whole fix — the same command then exits 0 and reports112 *.object.ts files … 148 keyed text-family columns judged, 148 bounded.2. The scan does not recognise the declaration shape
os initemits. The three files above were emitted by the scaffolder's own emitter (TEMPLATES+writeTemplateSrcFiles). The shape is a plain annotated object literal:The 112⚠️ Measured with the repaired annotation, so this is not an effect of #15976's
*.object.tsfiles the gate does parse are all theObjectSchema.create({ … })factory shape (examples/app-showcase/src/data/objects/*.object.ts).Data.Objectdefect — the literal shape is unrecognised either way.Why the two together matter
packages/cli/test/init-scaffold-authoring-rules.test.tswrites exactly that shape into exactly that directory —mkdtempSyncunderpath.resolve(HERE, '../tmp')— and removes it inafterAll. That cleanup does not run when the process dies first: a test run killed by a timeout, a container foreground cap, orctrl-cleaves the files behind, and the nextcheck:keyed-text-boundsin that workspace is red for a reason that has nothing to do with the author's diff.The gate's own refusal text is right that an unclassifiable shape on a keyed column must be a refusal rather than a pass — that is the design and it is not in question here. What is in question is the population the refusal ranges over.
Not claimed
⛔ Not swept: whether CI can reach this state at all (each job takes a fresh checkout, so the leftover probably cannot survive there — this is measured as a local failure mode only). ⛔ No repair proposed, and the choice is a real one with at least three shapes, none of them obviously right:
.gitignore(or skipstmp/the way it already skipsnode_modules);--self-testcase, so a scaffolded object file is judged rather than refused;packages/cli/node_modules/, the placementgenerate-scaffold-validates.test.tsalready measured and documented for the same reason.The second is not merely a cleanup: it would mean the repo's own gate and the repo's own scaffolder currently disagree about how an object file may be written, which is a question worth answering deliberately rather than as a side effect.
Refs: #15976 / PR #16193 (the run that crossed this).