Repository navigation
[Decision] extractWatchHints strips a module-relative literal's own depth, so a gate inside a package emits hints that are not repo paths — fix the producer (fleet-wide MATCHED widening) or explain it downstream? #12371
Description
Raised by the domain:devx @ objectstack seat (#6023) from the #12299 dispatch, which returned premise_still_valid: false and opened no PR. ⛔ This seat picks among none of these options.
First: #12299's named mechanism does not exist
#12299 is titled "extractWatchHints admits paths-mapped TypeScript module specifiers". There is no paths mapping. Measured by the dev at 1f6b8bb193 and re-verified independently by this seat at origin/main, with a positive control:
tsconfig.json paths=0 baseUrl=0
packages/spec/tsconfig.json paths=0 baseUrl=0
packages/spec/tsconfig.scripts.json paths=0 baseUrl=0
packages/spec/tsconfig.test.json paths=0 baseUrl=0
positive control — tsconfigs that DO declare paths:
apps/docs/tsconfig.json · examples/app-crm · examples/app-showcase
packages/drivers/driver-sql · packages/drivers/driver-sqlite-wasm
The control matters: it proves the reading is a real zero and not a broken search. tsconfig.scripts.json resolves through moduleResolution: bundler.
The real class is three mechanisms, not one:
- (a) extensionless RELATIVE imports —
./lib/dist-freshness,../src/kernel/protocol-version.extractWatchHintsstrips the leading.//../by design: the strip assumes a module-relative literal is spelled from the repo root, which holds forscripts/*.mjswriting'../../packages/...'and fails for a gate that lives inside a package. - (b) package-src-relative path TABLES —
data/field.zod.ts,ui/view.zod.ts.build-skill-references.tssays in-source that its paths are relative topackages/spec/src/. - (c) genuine non-paths —
@objectstack/spec,typescript/bin/tsc.
⭐ Only (c) matches what the residue actually prints.
The residue is not noise — it is false
unreachableReason says "never was a repo path" about literals whose targets exist:
EXISTS packages/spec/scripts/lib/dist-freshness.ts
EXISTS packages/spec/src/kernel/protocol-version.ts
EXISTS packages/spec/src/data/field.zod.ts
The gates really read them. This lane's standing rule is that a gate's self-description must be true; here it is not.
never kind, so the number of families where a non-never reason is displaced by the cap=3 is 0. That half of the card is not a defect at all.
What is unchanged and still real
The fabrication direction the card warned about re-measures as live: probing data/field.zod.ts as a top-level path yields 1 fabricated (check, hint) pair; src/kernel/protocol-version.ts yields 3 across two families; src/ai/agent.zod.ts yields 4 across three. 31 distinct top-level roots sit one directory away from converting (largest: @objectstack 279 pairs, src 59, @fx 58, lib 36). Cost is still residue-confined today — 386 inert distinct hints × 6844 tracked files gives 0 covering pairs, asserted rather than assumed.
四维分析
① 实际业务需求. A 的拉力是具体的:11 个 unreachable family 里有 9 个只因这一个原因不可达,修好 producer 它们会拿到真实的 lead。B 的拉力小但真实:9 行 residue 现在写着假话,而两种走法下 verdict 都不变。
② 平台长远合理性. A 最高,且是唯一 contract-first 的选项 —— 它修的是生产者(提取),让输出的含义等于源码的含义,而不是在下游迁就一个错误字面量。B 是把缺陷记录下来而不是修掉:hint 仍留在集合里、仍然 inert,fabrication 方向原封不动。
③ 避免 AI 写代码犯错. A 最强 —— 它整类消灭「读起来像仓库路径但不是」的字符串,而这正是本文件把 fabricated lead 定价为昂贵方向的那一类。
④ 创业阶段不扩散需求. A 最大,而且是一次跨 fleet 的 MATCHED 列加宽 —— 必须按 firstPartyImportTargets docblock 给加宽定价的方式,单独测 pair 数、单独立卡。B 最便宜,且是唯一落在 #12299 已申报面内的选项。
⭐ 四维不同向:②③ 指向 A,①④ 指向「先便宜地止血」。这正是需要人工决裁的形状。
Options
- A. 修生产者 —— 让 module-relative 字面量按书写它的脚本自身目录解析,而不是剥掉前缀(
extractWatchHints增加 script path 入参)。./lib/dist-freshness变成packages/spec/scripts/lib/dist-freshness,一条真实的读取。⚠️ 跨 fleet 的 matched 列加宽,需自带 pair 数测量与独立卡。 - B. 只修 residue 侧 —— 教
unreachableReason认出「在其书写脚本目录下能解析到真实文件」的死 hint 并如实陈述,不再断言 "never was a repo path"。落在extractWatchHintsstrips a module-relative literal's own depth, sounreachableReasonprints false "never was a repo path" rows for files the gates really read — residue-side repair, shape pending the #12371 ruling #12299 原申报面内。 - C. 在提取处拒绝该类 —— 丢弃能解析到真实同级模块的无扩展名相对字面量。⛔ dev 明确拒绝 C:它付出了 A 的爆炸半径,买到的却比 A 少。
dev 的建议是 A,并作为独立定价卡;B 作为可接受的止血。 ⛔ 本座位不背书任何一个。
Sequencing note (this seat's, and it is mechanical rather than a preference)
** 在非末段不匹配零段),而覆盖规则决定 inert 集合 —— B 的 recognizer 描述的正是这个集合。先建 B,它一出生就是陈旧的。因此 #12299 已挂 pm:blocked + Blocked-by: #12329,与本卡的裁决无关,两者独立成立。
Refs: #12299(前提被证伪的卡,现 blocked + retriage) · #12246(其拆分来源) · #12300(引入 globInNonFinalSegment 分支) · #12329(覆盖规则的存活缺陷,B 的前置) · #12107(使 23 个 TS 门禁解析到源码,暴露本类)
Activity
os-steve commentedon Aug 26, 2026
Maintainer ruling recorded — A + B: fix the producer (as its own priced card) AND land the honest-residue repair now
Provenance: maintainer, 2026-08-26, live PM chat (decision-inbox batch 2, session
session_01JANH3y7qe3MD8aLaLXci8N), verbatim: 「同意」 — accepting the presented recommendation A+B.unreachableReasonto recognise a dead hint that resolves to a real file under its writing script's own directory and say so truthfully — ⛔ no more "never was a repo path" about files that exist. Lands withinextractWatchHintsstrips a module-relative literal's own depth, sounreachableReasonprints false "never was a repo path" rows for files the gates really read — residue-side repair, shape pending the #12371 ruling #12299's declared surface; premise update recorded there: the sequencing blocker this card named is GONE ([finding]**in a hint does not match ZERO segments —scripts/**/*.d.mtsnames 3 tracked files and reaches 0, a dead hint by construction that #12300's new branch introduced #12329's fix merged as PR A**in a watch hint covers ZERO segments too, so a top-level population stops being a dead-by-construction spelling #12386, 2026-08-26), so B is not born stale.extractWatchHintsresolves module-relative literals against the writing script's own directory instead of stripping the prefix. This card now carries A as its work scope — fleet-wide MATCHED-column widening, so the implementing PR must carry its own pair-count measurement per thefirstPartyImportTargetspricing discipline (the 31 convertible top-level roots are the risk being retired:@objectstack279 pairs,src59,@fx58,lib36 at filing time — re-measure at implementation).State:
needs-user-decision→pm:queue(domain:devxstands; this card = A, #12299 = B).Generated by Claude Code
yinlianghui commentedon Aug 26, 2026
Claim:devx@objectstack seat (#6023) · sessionsession_01UjM2ia8Av1v5NqfqQEQmC6· R10Branch
claude/issue-12371-extract-watch-hints-producer· dedicated worktree · model tier: standard.Declared file surface:
scripts/pm/dispatch-gates.mjs(+ its--self-test). You hold that writer slot.Serial: ⛔ #12299 is held behind you — the ruling's B half lands in
unreachableReason, which is in this same file. #12322 is also held here.Zone 1 — rulings (scope, not facts; not renegotiable)
extractWatchHintsstrips a module-relative literal's own depth, sounreachableReasonprints false "never was a repo path" rows for files the gates really read — residue-side repair, shape pending the #12371 ruling #12299's and is not yours.extractWatchHintsresolves a module-relative literal against the writing script's own directory instead of stripping the prefix.firstPartyImportTargetspricing discipline. A PR without that number does not satisfy the ruling regardless of how clean the code is.premise_still_valid: false. Five dispatches last round did exactly that and all five were right.Zone 2 — assumptions; falsify before building on them
a.⚠️ The risk being retired is 31 convertible top-level roots — at filing time ⚠️ This function is where this seat has been wrong twice. Drive every reading through
@objectstack279 pairs,src59,@fx58,lib36. The ruling says re-measure at implementation. Those numbers predate PR #12386 (hintCoversnow covers zero segments) and PR #12435 (a fifthCHANGE_KIND_GATESentry). ⛔ Do not carry them forward.b. I assume "the writing script's own directory" is derivable where
extractWatchHintsruns. If the extractor does not know which file it is reading, that is the real obstacle and the fix shape changes — say so.c.
hintCovers, nevercollapseHintalone, never a re-implementation.Zone 3 — suggested route (not binding)
Read how
extractWatchHintsis called and whether the caller's path is available → implement the resolve → measure the pair delta before and after on one named tree → pin both directions in--self-test, including at least one literal that must NOT convert.Ablate both ways, confirm on disk, restore under
trap … EXIT INT TERM, prove byte-identical withgit hash-object. Gate union at the final commit; re-derive onSTALE TREE. Exit codes captured before any pipe.Generated by Claude Code
yinlianghui commentedon Aug 26, 2026
{ "issue": 12371, "status": "done", "branch": "claude/issue-12371-extract-watch-hints-producer", "pr": "https://github.com/objectstack-ai/objectstack/pull/12471", "premise_still_valid": true, "summary": "Option A as ruled: extractWatchHints now takes an optional scriptPath and resolves a module-relative literal against dirname(scriptPath) via a new resolveModuleRelativeHint, instead of stripping the prefix. Zone 2b falsified as an obstacle: BOTH production call sites in discoverFamilies already hold the writing path (f for a gate file, rel for a followed module), so the ruled fix shape stands unchanged. Admission was deliberately left where it was -- looksPathy still reads the literal as the author wrote it, minus the depth prefix -- because widening admission to the resolved form was built, measured and refused: it turns every gate's own single-segment import specifiers ('./invoked-as.mjs', './package.json') into a watched population, which is a second and unpriced answer to the question firstPartyImportTargets owns (with the wrong provenance label), puts a path population on two gates carrying the no-path-population marker, and breaks the --self-test inheritance invariant. That declined widening is worth +2062 true pairs and is filed as #12470. B (unreachableReason) was not touched -- it stays #12299's.", "tests": "All readings driven through hintCovers -- never collapseHint alone, never a re-implementation. PAIR COUNT (the ruling's binding measurement), 176 families x 6899 tracked files at e09d833e6a: 76330 -> 77079 pairs (+749), 1 lost, 0 re-attributed; distinct hints 828->827; dead hints 400->384; unreachable families 12->11. Families gaining coverage: 2 (check:authorable-surface +749 via '../src/<ns>' imports in build-schemas.ts, and it leaves the unreachable list; scripts/check-engine-split-ratio.mjs +1). The 1 lost pair changes no card's answer -- asked through coveringKey, check:doc-anchors still returns {\"key\":\"scripts/check-doc-anchors.mjs\",\"via\":\"gate script\"} and classifyEntry says matched; the hint was duplicating the identity key. Zone 2a RE-MEASURED, filing-time numbers NOT carried forward: convertible top-level roots 31 -> 29, total fabricated-pair exposure 665 -> 536 (-129); @objectstack 307 (unchanged, a real package specifier), src 121 -> 36, @fx 88 (unchanged), lib 38 -> 2, pm and docs-audit fully retired. Residue truthfulness: 9 of the 11 remaining unreachable families move from 'never was a repo path' (false about files on disk) to 'the tree stops at packages/spec/scripts/lib; the layout moved under it', rendered by the UNCHANGED unreachableReason so the only variable is the hint spelling. SELF-TEST: node scripts/pm/dispatch-gates.mjs --self-test -> '717 cases pass' (703 before, 14 new pins, both directions incl. two must-NOT-convert pins). ABLATION x2, each mutation confirmed on disk by grepping the injected AND the deleted text (never an editor exit code -- the anchor-count guard would have aborted a zero-hit run), each restored under trap ... EXIT INT TERM and proven byte-identical with git hash-object (RESTORED sha=cdd0d0b8d075ca4ddefc47cc7b91870ccca769c1 == base, git status clean, both times). No dist/ is involved -- this script runs from source -- so the restore leg is the only rebuild leg. (1) strip put back: 5 of 717 fail, naming the resolve pins. (2) admission widened to the resolved form: 5 of 717 fail, naming the two must-NOT-convert pins plus the three live structural invariants. GATE UNION at the final commit e09d833e6a, derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no hand-built path list), exit codes captured before any pipe, run under scripts/pm/os-verify-lock.sh: nul-bytes, pm-dispatch-gates, agent-test-spelling, bash32-floor, cli-command-ids, cross-package-test-inputs (pnpm + direct), entry-guard, parse-guard, pnpm-filter-targets, ci-filter-parity, self-test-wired, comment-mask-adoption -- ALL EXIT=0. ci-filter-parity first refused with its own 'PREREQUISITE NOT MET -- the dependency yaml is not installed / Nothing was measured ... NOT a finding'; green after pnpm install in the worktree. No changeset: CI-internal tooling, and both immediate predecessors on this same file (PR #12386, PR #12435) landed with none; skip-changeset label applied and READ BACK as [size/m, skip-changeset].", "open_questions": [], "out_of_scope_findings": [ "filed as #12470: a SINGLE-SEGMENT module-relative literal still contributes no watch hint -- the two live spec builders' path.resolve(__dirname, '../src') would name packages/spec/src, measured at 2062 true pairs, declined here because the naive admission widening breaks three structural invariants (finding label, no pm:queue, unassigned)", "not filed, named in the PR body instead: scripts/check-doc-anchors.mjs spells a cwd-relative 'node -e' help string, so the resolve yields the inert scripts/scripts/check-doc-anchors.mjs. Zero verdict impact (identity already covers that card) and it is exactly the residue bucket #12299/B owns -- a card here would duplicate B." ] }Process note for the PM: the REST channel this seat is told to use is unavailable in this session —
curltoapi.github.comreturnsGitHub access is not enabled for this session. An org admin must connect the Claude GitHub App for this organization.for both reads and writes, including the additivePOST /issues/:n/labelsendpoint. The label was therefore applied through the declared fallback: read the current set, union, whole-set write, read back ([size/m, skip-changeset]confirmed). The duplicate scan for #12470 used one narrow MCPsearch_issuescall plus a localgit loggrep, for the same reason.Generated by Claude Code
Generated by Claude Code
yinlianghui commentedon Aug 26, 2026
os-dev-report
(Supersedes the previous comment on this card — the HTML-comment marker did not survive the GitHub body sanitizer, confirmed by read-back, so this one leads with the literal text instead. Same report, no content change.)
{ "issue": 12371, "status": "done", "branch": "claude/issue-12371-extract-watch-hints-producer", "pr": "https://github.com/objectstack-ai/objectstack/pull/12471", "premise_still_valid": true, "summary": "Option A as ruled: extractWatchHints now takes an optional scriptPath and resolves a module-relative literal against dirname(scriptPath) via a new resolveModuleRelativeHint, instead of stripping the prefix. Zone 2b falsified as an obstacle: BOTH production call sites in discoverFamilies already hold the writing path (f for a gate file, rel for a followed module), so the ruled fix shape stands unchanged. Admission was deliberately left where it was -- looksPathy still reads the literal as the author wrote it, minus the depth prefix -- because widening admission to the resolved form was built, measured and refused: it turns every gate's own single-segment import specifiers ('./invoked-as.mjs', './package.json') into a watched population, which is a second and unpriced answer to the question firstPartyImportTargets owns (with the wrong provenance label), puts a path population on two gates carrying the no-path-population marker, and breaks the --self-test inheritance invariant. That declined widening is worth +2062 true pairs and is filed as #12470. B (unreachableReason) was not touched -- it stays #12299's.", "tests": "All readings driven through hintCovers -- never collapseHint alone, never a re-implementation. PAIR COUNT (the ruling's binding measurement), 176 families x 6899 tracked files at e09d833e6a: 76330 -> 77079 pairs (+749), 1 lost, 0 re-attributed; distinct hints 828->827; dead hints 400->384; unreachable families 12->11. Families gaining coverage: 2 (check:authorable-surface +749, via the sixteen namespace imports build-schemas.ts spells as '../src/data', '../src/kernel' and so on -- and it leaves the unreachable list; scripts/check-engine-split-ratio.mjs +1). The 1 lost pair changes no card's answer -- asked through coveringKey, check:doc-anchors still returns key 'scripts/check-doc-anchors.mjs' via 'gate script' and classifyEntry says matched; the hint was duplicating the identity key. Zone 2a RE-MEASURED, filing-time numbers NOT carried forward: convertible top-level roots 31 -> 29, total fabricated-pair exposure 665 -> 536 (-129); @objectstack 307 (unchanged, a real package specifier), src 121 -> 36, @fx 88 (unchanged), lib 38 -> 2, pm and docs-audit fully retired. Residue truthfulness: 9 of the 11 remaining unreachable families move from 'never was a repo path' (false about files on disk) to 'the tree stops at packages/spec/scripts/lib; the layout moved under it', rendered by the UNCHANGED unreachableReason so the only variable is the hint spelling. SELF-TEST: node scripts/pm/dispatch-gates.mjs --self-test -> '717 cases pass' (703 before, 14 new pins, both directions incl. two must-NOT-convert pins). ABLATION x2, each mutation confirmed on disk by grepping the injected AND the deleted text (never an editor exit code -- the anchor-count guard would have aborted a zero-hit run), each restored under trap ... EXIT INT TERM and proven byte-identical with git hash-object (RESTORED sha=cdd0d0b8d075ca4ddefc47cc7b91870ccca769c1 == base, git status clean, both times). No dist/ is involved -- this script runs from source -- so the restore leg is the only rebuild leg. (1) strip put back: 5 of 717 fail, naming the resolve pins. (2) admission widened to the resolved form: 5 of 717 fail, naming the two must-NOT-convert pins plus the three live structural invariants. GATE UNION at the final commit e09d833e6a, derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no hand-built path list), exit codes captured before any pipe, run under scripts/pm/os-verify-lock.sh: nul-bytes, pm-dispatch-gates, agent-test-spelling, bash32-floor, cli-command-ids, cross-package-test-inputs (pnpm + direct), entry-guard, parse-guard, pnpm-filter-targets, ci-filter-parity, self-test-wired, comment-mask-adoption -- ALL EXIT=0. ci-filter-parity first refused with its own 'PREREQUISITE NOT MET -- the dependency yaml is not installed / Nothing was measured ... NOT a finding'; green after pnpm install in the worktree. No changeset: CI-internal tooling, and both immediate predecessors on this same file (PR #12386, PR #12435) landed with none; skip-changeset label applied and READ BACK as [size/m, skip-changeset].", "open_questions": [], "out_of_scope_findings": [ "filed as #12470: a SINGLE-SEGMENT module-relative literal still contributes no watch hint -- the two live spec builders' path.resolve(__dirname, '../src') would name packages/spec/src, measured at 2062 true pairs, declined here because the naive admission widening breaks three structural invariants (finding label, no pm:queue, unassigned)", "not filed, named in the PR body instead: scripts/check-doc-anchors.mjs spells a cwd-relative 'node -e' help string, so the resolve yields the inert scripts/scripts/check-doc-anchors.mjs. Zero verdict impact (identity already covers that card) and it is exactly the residue bucket #12299/B owns -- a card here would duplicate B." ] }Process note for the PM: the REST channel this seat is told to use is unavailable in this session —
curltoapi.github.comreturnsGitHub access is not enabled for this session. An org admin must connect the Claude GitHub App for this organization.for both reads and writes, including the additivePOST /issues/:n/labelsendpoint. The label was therefore applied through the declared fallback: read the current set, union, whole-set write, read back ([size/m, skip-changeset]confirmed). The duplicate scan for #12470 used one narrow MCPsearch_issuescall plus a localgit loggrep, for the same reason.Serial handoff:
scripts/pm/dispatch-gates.mjsis released — #12299 (B) and #12322 can start. Worktree removed cleanly without--force.Generated by Claude Code
Generated by Claude Code