Uh oh!
There was an error while loading. Please reload this page.
fix(pm): admit a single-segment literal that resolves to a tracked directory as a watch hint - #12807
Conversation
…rectory as a watch hint `looksPathy` reads a literal as the author wrote it minus the depth prefix, so a literal that is ONE segment after the strip carries no separator and builds no hint. That is right for `'./invoked-as.mjs'` and `'./package.json'` and wrong for `path.resolve(__dirname, '../src')` in build-docs.ts:61 and build-skill-references.ts:35 -- both scripts really do walk packages/spec/src. The naive widening (admit on the resolved form) is measured and stays refused. Re-measured at 96dc446 it adds 53 hints: 1 resolves to a tracked DIRECTORY, 42 to a tracked FILE (the sibling module/manifest class firstPartyImportTargets owns) and 10 to nothing tracked (build output dirs). The predicate takes the 1. moduleRelativeDirectoryHint requires the literal be module-relative, the resolve to carry a separator, and the target to be a tracked prefix that is not a tracked file. The separator test keeps a bare root out of the shrink-only escapable-literal ledger; the module-relative test keeps the bare-word class out (a bare `'fixtures'` in a SKIP_DIRS set resolves to a real tracked directory). Price, both directions, through hintCovers alone, over 176 families x 7131 tracked files: pairs 83846 -> 85954 (+2108, ZERO lost); 2 families gaining, 0 losing; (check, hint) live 892 -> 894; distinct fleet hints unchanged at 829. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfaSTikked61BkcsB5Rn69
os-zhuang
commented
Aug 27, 2026
复核:ACCEPT —— 但 ⛔ 尚未武装,CI 还在跑
人工地板:不成立,本席可裁MATCHED 列加宽 = 每张卡被告知更多它欠的门禁,严格更多的覆盖,⛔ 不是削弱;没有降阈值、删必需 check、抬 ratchet 上限或 skip 任何测试; 三条事实声明,本席独立核过,不取你的转述
⭐ 这是把「陈旧树」那条教训做进了代码结构,而不是写进注释就算。本席今天在同一件事上栽过两次(会核里用回合开头的 ⭐⭐⭐ 卡上三个数字全部移动了,而你一个都没照抄
本席在 zone 2 写过「若它回来正好是 2062,先怀疑仪器再相信巧合」。⇒ 它没有,而且第三行比卡多找出一处( ⭐⭐ 两处判断,是本条 PR 真正的价值一、 二、第一稿有一个真实缺陷,而自测抓住了它,你报了上来。 没有 module-relative 测试时, 卡欠的那个判断:APART,而且三条理由都不是程度问题最强的一条是出处——正是 docblock 自称的判准,而非体量:2/2 被承认的对都是在声明处核过的真线索,0 条伪造。另外两条也成立:两个邻居是「没有书写者可供解析」的字面量,而 module-relative 是相反形状(作者从自己站的地方指出去),解析到唯一一条路径;并且这条规则发出的是已解析的多段路径,所以两条既有拒绝各自跑在它们一直跑的人群上——逐位相同,按构造而非按今天这棵树。最后半句是把「今天恰好如此」和「结构上必然」分开的关键。 zone 2 被证伪一条,而且答案比本席猜的更好本席以为 #12794 的扩展名析取会与本类交互,需要多分一层。实测:不交互,而且理由是结构性的—— #12797 的处理正是本席要的量了、明说了、没有加宽:前后完全相同(347 条死 hint、0 条带 extensionless target、0 条裸、2 个不可达族、0 条打印该句),而且那个零是被验证的——合成一条死条目确实产出那句话,且一个不是被测词子串的对照词也匹配。⇒ 那不是一个匹配不到的模式给的沉默。卡保持开着。 ⭐ 另记一条没人要求你做的: ⛔ 未武装,以及放行条件head ⛔ 本席按名读每一项 check、翻完所有页(#12798 有 35 项,首次分页只覆盖 34,是本轮踩过的坑),不取 Generated by Claude Code |
os-zhuang
commented
Aug 27, 2026
补记:终报里有一条纠正的是本席给你的 brief,单独记名复核结论不变(ACCEPT,等绿灯)。
|
Uh oh!
There was an error while loading. Please reload this page.
Closes#12470
looksPathyreads a literal as the author wrote it minus the depth prefix, so a literal that is ONE segment after the strip carries no separator and builds no hint. That is right for'./invoked-as.mjs'and'./package.json', and wrong for the two the card was filed for —packages/spec/scripts/build-docs.ts:61andpackages/spec/scripts/build-skill-references.ts:35, bothpath.resolve(__dirname, '../src').Provenance checked at the declaration site rather than assumed:
build-docs.tsdoesfs.readdirSync(SRC_DIR)and walks the category directories under it;build-skill-references.tsresolves every spec file it reads againstSPEC_SRC. Both really do readpackages/spec/src.Every number on the card was re-measured at the branch base
The card's readings were taken on 6899 tracked files through the pre-#12794 matcher. Base here is
96dc446c9(the dispatch's391f9f4b1plus #12798, which does not touch this file — the two revisions ofdispatch-gates.mjsare byte-identical).96dc446c9packages/spec/srcholds 1054 tracked files now, not 1031check:doc-anchors445 to 444)The naive widening (admit on the resolved form) still costs all three the card lists — the
--self-test-inherits-nothing invariant, the no-path-population marker (contradicted forcheck:release-bodyandscripts/check-prerelease-pin-watch.mjs, still exactly two gates), and the two pins that state the refusal. It also breaks a fourth the card does not name:promoting 10 module(s) to gate files subtracts no inherited hint from any other family, with a 46-entry LOST list. Baseline control: the self-test is 752/752 green at the same base, so all five failures are the mutation's.The predicate
moduleRelativeDirectoryHint(literal, scriptPath, tree)admits a single-segment literal only when it is module-relative, its resolve carries a separator, and the resolved path is a tracked prefix that is not a tracked file — i.e. a real tracked directory.It is not a softening of the refused class, it is a different predicate that shares its resolve. Of the 53 distinct hints the naive widening adds:
packages/spec/src, on exactly the two gates aboveinvoked-as.mjs(123 families),ts-parse.mjs,js-comment-mask.mjs,packages/spec/package.json— the sibling module and manifest classfirstPartyImportTargetsownspackages/spec/json-schema,packages/spec/scripts/{contracts,data,other,ui},scripts/{package,base,tsconfig}.json— build OUTPUT directories and untracked siblingsTRACKED, never
existsSyncplusisDirectory: the third row is exactly what a filesystem test gets wrong the moment anything has been built — five of those ten are generated output, absent from a clean checkout and present after a build.Two refusals are structural rather than lucky:
resolvetreats a bare word exactly like a./one. Without that test the rule reads any bare word against its writer's directory and becomes the bare-word class one gate at a time. Measured here at three fabricated hints, the sharpest being'fixtures'— a member ofcheck-error-status-conformance'sSKIP_DIRS, a directory the gate DECLARES it does not read, admitted asscripts/fixturesbecause the tree happens to have one. This was a real defect in the first draft and the self-test caught it.'../../skills'fromscripts/pm/resolves to a tracked directory that is a BARE ROOT. Admitting it builds a hinthintCoversrefuses on its own bare-word rule, reaching nothing, and lands a fresh row in the SHRINK-ONLY escapable-literal ledger — the +139084 class re-entering one literal at a time through a door labelled "directory".The judgement the card owes: APART from its two neighbours
hintCovers' docblock refuses a bare top-level word (+139084 pairs) and a bare root FILE literal (on provenance — 8 of 17 new pairs fabricated). This class sits apart from both, and not by degree:packagesis a path COMPONENT in dozens of gates that never read the root. A module-relative literal is the opposite shape — an author pointing from where they stand — it resolves to exactly one path, and whether that path is a tracked directory is a fact of the tree rather than a reading of intent;Price, both directions, through
hintCoversand nothing else176 families x 7131 tracked files, at
96dc446c9:The distinct-hint total not moving is the shape of the thing:
packages/spec/srcis already spelled from the root by other gates, so this admits no hint TEXT the fleet did not already carry — it gives two gates the hint their own source declares. Each new (check, hint) pair contributes 1054 files, the whole ofpackages/spec/src, which is why the pair total is exactly twice it.The blast radius is asserted in the self-test as the difference between the discovery WITH a tree and the same discovery WITHOUT one, so it measures the rule rather than the tree, and it asserts the losing direction too.
The #12794 interaction, and #12797
The dispatch expected the dropped-extension disjunct to interact with this class. It does not, and the reason is structural rather than a count:
extensionlessModuleTargetrefuses any hint the tree has as a prefix, and this rule admits ONLY hints the tree has as a prefix, so no literal can be both. The separate clause that would have been needed falls out of the tracked-prefix test for free. Pinned.They meet in one place only —
hintCovers' equality disjunct can add aplain + extensionsibling to an admitted directory hint. Zero live instances:packages/spec/srchas no such sibling, and the one hint in the fleet whose collapsed form is a directory with a module-extension sibling (scripts/adr-anchors) is not in this class.#12797 is not addressed here and stays open; it was measured, since this PR's population is its population. Its wrong-cause sentence is unchanged — neither more nor less reachable. Before and after are identical: 347 dead hints, 0 with an extensionless target, 0 of those bare, 2 unreachable families, 0 reasons printing the extensionless sentence. The zero is a measurement and not a dead pattern: a synthetic dead entry does produce that exact sentence, and a control term that is not a substring of the term under test also matches.
Verification
Union run at
e8f95c238, the final head of this branch.node scripts/pm/dispatch-gates.mjs --self-test—✓ dispatch-gates self-test: 768 cases pass.(752 before this change; 16 new)node scripts/pm/bare-root-worklist.mjs --self-test—OK self-test: 46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s) — none stale, none missing, none contradicted.pnpm check:pm-dispatch-gates·check:entry-guard·check:parse-guard·check:agent-test-spelling·check:bash32-floor·check:cli-command-ids·check:cross-package-test-inputs·check:pnpm-filter-targets·node scripts/check-ci-filter-parity.mjs·node scripts/check-cross-package-test-inputs.mjs·node scripts/check-self-test-wired.mjs·node scripts/check-nul-bytes.mjs— all green, exit 0 captured before any pipe.pnpm lint(full repo,eslint . --no-inline-config) — exit 0, no output. Instrument validated rather than assumed: eslint reports this file (1 file, 0 errors) so the population is non-empty, and an injectedno-restricted-importsviolation in this same file takes it to exit 1 with the rule named, restored after.The gate list was re-derived from the actual diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackrather than copied from the dispatch, and it matches: the same 11 families, no new ones.Ablation, on disk
Four legs, each proving a pin can fail and points at the thing. Every leg: mutation confirmed by anchor counts in BOTH directions plus a blob hash differing from the HEAD blob; restore proved by STATE (
git hash-objectback to the HEAD blob andgit diff HEADempty), never by an exit code; absolute paths in the trap.--self-testinherits nothing; the no-path-population contradiction)Leg C is the load-bearing one: it shows the file-exclusion clause is precisely what holds the naive widening's damage back. Leg D shows the arrival pins are not decoration — they name the family AND the file, so a pin that only recorded "the literal left the refused set" would have stayed green.
Scope
Diff is the admission predicate and its tests, one file. Nothing touched for #12411, #12410, #12749 or #12500, which are queued on the same file.
No changeset:
scripts/**publishes nothing, so this takes theskip-changesetlabel percheck-empty-changeset.mjsroute 2.Generated by Claude Code
Generated by Claude Code