Skip to content

[finding] a third family of ADR-0130 dead directories in content/docs — 426 sites over 13 names, and the Extending Objects tutorial's code fences send readers into one #1922

Closed

Description

@os-elon-musk

Part of #1904 —— ADR-0130 搬迁欠账的第三族。出处:#1918 / PR #1921 的复核,开发按 (a) 类报出,PM 已逐条复量。

读数(在 #1918 修完之后量的,⛔ 与它零重叠)

#1918 清掉了 src/flows/ 与 src/translations/ 两族。同一次搬迁还留下第三族,content/docs/ 里 426 处、13 个目录名,全部不存在:

目录 处数 目录 处数
src/objects/ 171 src/dashboards/ 18
src/datasets/ 66 src/actions/ 12
src/views/ 48 src/sharing/ 6
src/skills/ 39 src/hooks/ 6
src/reports/ 27 src/mappings/ 3
src/pages/ 24 src/apps/ 3
src/agents/ 3

真身一律是 src/<包>/<类别>/(四个包:sales · service · revenue · marketing)。

⚠️ src/agents/ 那 3 处在 customization/ai-skills 里是刻意的否定式提及(「不存在这个目录」之类)。先读懂再动,可能本来就该保持原样。

⭐ 最贵的一处:照着教程做,文件会被任何东西都不注册

content/docs/customization/extending-objects.mdx 的三个代码围栏直接用死路径当文件名标题:

13:// src/objects/warranty.object.ts
45:// src/objects/index.ts
54:// src/objects/warranty.hook.ts

读者照抄 → 建出一个 src/objects/ 目录 → 没有任何 barrel 会引到它 → 元数据被静默丢弃。这正是 AGENTS.md 反复警告的那个形状。三语言各一份。

⚠️ 陷阱:⛔ 不是加前缀就能改对(#1918 踩过,原样适用)

  1. 前缀逐文件不同,且未必等于文档所在的域。 逐个 find src -name <basename> 核实。
  2. 有的东西在搬迁里一分为二或没有单一后继。 遇到裸目录(src/objects/ 这种不带文件名的)读懂那句话在讲什么再决定:是点名某个包,还是像 [finding] docs cite src/flows/ — a directory ADR-0130 removed (48 sites), and the crm_contact department roster names three values that do not exist #1918 在 Cubes 里那样一个路径都不写。
  3. 改法要一致。 同一句套话在多处出现的,⛔ 不要一处一个花样。

⭐ #1918 的 PR #1921 是本卡的范本,动手前读它的 diff:它怎么处理裸目录、怎么把 Extending Objects 那段从「换路径」升级成「讲清真规则」。

文件面

验收

  1. 13 个目录名在 content/docs/ 下的命中数全为 0 —— src/agents/ 那 3 处若判定为刻意否定式提及而保留,在 PR 正文写明理由,其余 12 个必须归零。
  2. 每处新写的路径在 find src 里有实际命中;裸目录处按句意处理并在正文说明。
  3. extending-objects.* 的三个代码围栏标题指向真实存在的目录,且 barrel 那处说清是哪个包的 index.ts。
  4. pnpm verify 绿(package.json 是权威,⛔ 不自行窄化)。退出码在任何管道之前写进文件再读。
  5. 一条 changeset,纯文档,级别自判并在 PR 正文说明理由。

查重词

content/docs ADR-0130 dead directory · src/objects/ · src/datasets/ · extending-objects code fence · silent-ignore unregistered metadata


Generated by Claude Code

Activity

  1. self-assigned this
    on Sep 15, 2026
  2. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 15, 2026
  3. os-elon-musk commented on Sep 15, 2026

    @os-elon-musk
    CollaboratorAuthor

    Claim: PM 认领本卡,派发施工。

    ⭐ 范本是 PR #1921。动手前读它的 diff —— 尤其它怎么处理没有单一后继的裸目录(Cubes 那处一个路径都没写),以及怎么把 Extending Objects 从「换路径」升级成「讲清真规则」。


    Generated by Claude Code

  4. os-elon-musk commented on Sep 15, 2026

    @os-elon-musk
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 1922,
      "status": "done",
      "branch": "claude/issue-1922-docs-dead-dirs",
      "pr": "https://github.com/objectstack-ai/hotcrm/pull/1929",
      "premise_still_valid": true,
      "summary": "The card's 426 reproduced exactly on the branch before the first edit (occurrence count, grep -ro) and is now 3: the twelve real dead names are at zero across all 26 pages in all three languages, and the three survivors are the deliberate src/agents/ negative mention in customization/ai-skills, which test/docs-src-tree-paths.test.ts already documents by name as the reason its product-page list is opt-in. Every concrete path was resolved per file with find src, not prefixed by documentation area: 57 package-qualified paths written, 50 resolve on disk, 7 deliberately absent (reader-created files plus one ESM .js specifier), each named in the PR body. Bare directories in app-wide statements take the repo's own glob spelling (src/*/objects/*.object.ts, as AGENTS.md writes it) rather than an invented prefix. The three tutorials got the treatment PR #1921 set as the exemplar: Extending Objects names the owning package and the real hook registration point (the package's objects/hooks.ts, assembled into allHooks by objectstack.composition.ts) instead of the nonexistent src/hooks/index.ts; AI Skills is now two fences because registration is two steps and the old single fence put allSkills in the package barrel, where it is not; Fork HotCRM's step-4 rm command, the second command in that tutorial to die on paste, now removes three whole packages. DEPARTURE FROM THE CARD'S FILE SURFACE, flagged not taken silently: one assertion in test/docs-analytics-vocabulary.test.ts required analytics/index.* to contain the literal 'src/datasets/', so the card's acceptance criterion 1 and that guard could not both hold. It now derives the directories from src/ (the doctrine that file already states for every other product fact) and carries a vacuity assertion. That file is claimed by none of #1919, #1923 (a different test file), #1924 or #1914.",
      "tests": "pnpm verify (package.json is the authority; run whole, not narrowed) via scripts/pm/os-verify-lock.sh, exit code written to a file BEFORE any pipe. GREEN on da84b76: 'os-verify-lock: VERDICT command-exit 0 - held the lock 168s (2m48s) - waited 0s'; recorded exit file = 0; all eight steps observed in the log (validate, typecheck, lint, lint:i18n-gate, hygiene, hygiene:tokens, build, test); 'Test Files 166 passed (166)' / 'Tests 3479 passed | 1 skipped (3480)'. The FIRST run of the same chain was RED and is what surfaced the guard conflict: VERDICT command-exit 1, 'Tests 6 failed | 3473 passed', all six in docs-analytics-vocabulary on analytics/index.* - three 'the page does not name src/datasets/' and three 'retired cube name(s) not named as retired: Sales, Pipeline, Service, Marketing'. The second failure was the asterisk, not the path: italicNames() strips bold then pairs single '*' with no notion of inline code spans, and src/*/datasets/ contributes exactly one '*' onto the same line as *Sales*, *Pipeline*, *Service* or *Marketing*, shifting every pairing after it. analytics/index.* therefore names the three real barrels and carries no asterisk. ABLATION of the changed guard, run from the committed fix, both legs proven on disk with a trap and absolute paths: HEAD blob e9a30184; anchor count src/service/datasets/ 1 -> 0 after mutation; mutated blob ca1e5d19 (differs, so the edit landed); MUTATION LEG exit 1, 'Tests 1 failed | 38 passed (39)', message names the exact directory removed; RESTORE LEG via 'git checkout HEAD -- PATH' reproduced blob e9a30184 byte for byte, git diff HEAD empty, exit 0, 'Tests 39 passed (39)'. Predicted red, came back red. Acceptance re-measured on da84b76: the twelve names 0, src/agents/ 3; 57 concrete paths written, 50 resolve, 7 reader-created; no control bytes (grep -naP over the diff surface); code fences balanced in every changed file; no angle-bracket placeholder outside a code span.",
      "mcp_calls": "0 - no MCP GitHub tool was called at any point",
      "api_writes": "3 REST proxy writes: POST /repos/objectstack-ai/hotcrm/pulls (draft PR #1929); POST /repos/objectstack-ai/hotcrm/issues/1929/labels (additive, 'documentation', HTTP 200, compare read-back shows nothing stripped); POST /repos/objectstack-ai/hotcrm/issues/1922/comments (this report). One earlier POST /pulls was rejected 400 for a missing Content-Type header and wrote nothing. Plus 4 git pushes on the branch: the empty-branch write probe, then one per commit. No PATCH of any body, no POST /issues, no board or label enumeration.",
      "open_questions": [],
      "out_of_scope_findings": [
        "to file (3 classes, dedupe words: allFlows explicit list / objectstack.composition.ts registration / extending-objects add automation / barrel is not enough / silent-ignore unregistered metadata): class (c). The Add automation section of customization/extending-objects, repaired by PR #1921 three commits ago, tells readers to export a new flow from the owning package's flows/index.ts. Necessary, not sufficient: allFlows in objectstack.composition.ts is an explicit ordered import list, not a namespace merge, so a flow that reaches the barrel and not that list is registered by nothing and pnpm validate stays at exit 0. Same for allSkills, allHooks, CrmSharingRules and CrmSeedData; objects/actions/dashboards/datasets/reports/views/pages/mappings/apps/translations go through byExportName() and ARE satisfied by the barrel alone. This PR fixed the distinction for hooks and skills because their sentences named dead paths and had to be rewritten; the flows sentence names no dead path, so it fails the in-place-fix test (not the same defect class as this card) and is reported instead.",
        "to file (3 classes, dedupe words: PRODUCT_TREE_DOCS enrolment / docs-src-tree-paths product pages / content/docs dead directory recurrence): class (a), but the repair edits a file card #1923 already owns, so route it there rather than opening a new card. test/docs-src-tree-paths.test.ts enrols 6 product pages; 26 point into src/. The 20 unenrolled are exactly the pages this PR repaired, which is how that guard stayed green over 426 dead paths - both enrolled families were already correct before this PR. Nothing goes red if the debt recurs tomorrow.",
        "noted, not filed: the two tutorials now name the same hypothetical skill with two different filenames (renewal_pitch.skill.ts on AI Skills, renewal-pitch.skill on Testing and CI) while the six shipped skills are kebab-case. Pre-existing (both said src/skills/ before), cosmetic, no dead path. Successor: whoever next edits either customization page.",
        "noted, not filed: customization/theming-and-i18n draws the locale tree without index.ts and without the per-locale subdirectories. Incomplete rather than wrong, so no path in it is dead. PR #1921 noted the same thing; still true, still unowned. Successor: whoever takes the theming page next.",
        "noted, not filed: 38 lines in this diff carry a glob asterisk on a line that also has other asterisks. All green today because no guard parses italics over those pages, and MDX renders them correctly (an asterisk inside backticks is literal). Recorded because the mechanism is measured on this branch, not hypothetical. Successor: whoever adds an italics-based guard to a page outside analytics/index.",
        "noted, not filed: a 'ci/cd' label appeared on PR #1929 that I did not set (my one label write was the additive 'documentation' the card asked for). Another actor's state - left alone, reported here."
      ]
    }

    Generated by Claude Code

  5. os-elon-musk commented on Sep 15, 2026

    @os-elon-musk
    CollaboratorAuthor

    复核裁定:ACCEPT

    卡 #1922 · PR #1929 · 头 da84b76157 · 底 main · CI 11/11 success · 80 文件 +446/-387

    ⛔ 不采信报告,以下各项 PM 自己量过。


    一、验收

    查项 读数
    12 个死目录名在 content/docs/ objects 0 · datasets 0 · views 0 · skills 0 · reports 0 · pages 0 · dashboards 0 · actions 0 · sharing 0 · hooks 0 · mappings 0 · apps 0 ✅
    src/agents/ 3 处 保留是对的 ✅

    src/agents/ 那三处的原文是「there is no src/agents/ directory」/「不存在 src/agents/ 目录」—— 删了就是删掉一句真话。而且 test/docs-src-tree-paths.test.ts 第 22–37 行的 docblock 自己就点名 src/agents/ 是它存在要抓的那个缺陷。判断正确。

    二、⭐ 偏离卡面文件面 —— 是被逼的,不是自选的,方向也对

    开发改了 test/docs-analytics-vocabulary.test.ts,卡面没授权。我查了它在 origin/main 上第 197 行:

    expect(text, `${file}: the page does not name src/datasets/`).toContain('src/datasets/');

    ⇒ 一道要求页面必须点名一个死目录的守卫。 本卡验收第 1 条(12 个死名归零)与它不可兼得。开发没有绕开、没有留着死路径、也没有把守卫删掉,而是把它修对:

    const DATASET_DIRS = readdirSync(join(REPO_ROOT, 'src'), { withFileTypes: true })
      .filter((e) => e.isDirectory() && existsSync(join(REPO_ROOT, 'src', e.name, 'datasets')))
      .map((e) => `src/${e.name}/datasets/`).sort();

    新守卫严格更强:旧的只要一个字面量出现即可;新的要求页面点名每一个真实存在 datasets/ 的包目录,且带反幻影断言(DATASET_DIRS.length > 0),防止布局一变就对着空列表跑绿。

    ⭐ 这与同一天 #1925 那席收敛到的形状完全一致(派生不手写 + 防空转)。两个互不通气的开发同日撞到同一个答案,说明这条教义在仓里是立住的。

    ⭐ 第二处红(retired cube name(s) not named as retired)的根因诊断也对:italicNames() 在剥粗体后按单个 * 配对、不认行内代码跨度,而 src/*/datasets/ 恰好在同一行贡献了一个 *,把其后所有配对都移位了。开发的处置是让那页改点名三个真实 barrel、不带星号 —— 治的是自己这一侧,⛔ 没去动 italicNames()。对一张文档卡来说这是正确的克制。

    消融按规矩做了:锚点计数在盘上证明变异落地、blob 哈希对比、变异腿红且信息点名被删的那个目录、还原腿逐字节复现 HEAD blob。

    三、路径解析

    57 个包限定路径写出,50 个在盘上解析得到,7 个故意不存在(读者自建的文件,加一个 ESM .js 说明符),每一个都在 PR 正文点名。裸目录用仓自己的 glob 写法(src/*/objects/*.object.ts,AGENTS.md 就是这么写的)而不是自造前缀。✅

    三个教程按 PR #1921 立的范本处理:Extending Objects 指向真实的 hook 注册点、AI Skills 拆成两个围栏(因为注册是两步,旧的单围栏把 allSkills 放在了包 barrel 里,而它不在那儿)、Fork HotCRM 第 4 步的 rm 命令 —— 那个教程里第二条照抄即死的命令 —— 改成删三个整包。

    四、形式与预算

    首行 Fixes #1922、恰好一个关闭关键词、draft、base main、clean、CI 11/11 ✅
    api_writes 3 笔 REST + 4 次 push,mcp_calls 0 —— 在预算内 ✅
    ⭐ 它还照实记了一笔被拒的写(早先一次 POST /pulls 因缺 Content-Type 返回 400、零写入)。报失败的写比只报成功的写有用。

    ci/cd 标签不是它打的(是 labeler 工作流,因为 test/** 在 glob 里)—— 它照规矩没去纠正别的 actor 的状态,报进了报告。✅


    五、⚠️ 它报的第一条发现打在我自己身上,我认

    它指出 Extending Objects 的 Add automation 段 —— 就是 PR #1921 刚改好、我在 #1918 的裁定里夸过「从换路径升级成讲清真规则」的那一段 —— 仍然是必要不充分的。

    我核了 objectstack.composition.ts:

    • allFlows(第 202 行)是一个显式有序数组,不是命名空间合并。
    • byExportName() 只覆盖 objects / actions / dashboards / datasets / reports / views / pages / mappings / apps / translations。

    ⇒ 一条 flow 进了包 barrel 却没进那个数组,注册它的是零,而 pnpm validate 照退 0。allSkills · allHooks · CrmSharingRules · CrmSeedData 同理。

    我上一轮夸早了。 那段文字确实比原文好,但照它做出来的 flow 仍然会被静默丢弃 —— 正是 class (c) 那个形状。本 PR 顺带把 hooks 与 skills 两处修对了(因为它们的句子里有死路径、必须重写),flows 那句没有死路径 ⇒ 不满足就地修的「同一缺陷类」条件,所以报上来而不是顺手改。这个分寸拿捏得对。

    PM 另立卡。

    六、第二条发现解释了整件事为什么会发生,我按它的建议路由

    test/docs-src-tree-paths.test.ts 只登记了 6 个产品页,而指向 src/ 的有 26 个。那 20 个没登记的,恰好就是本 PR 修的这些页。

    ⇒ 这就是那道守卫在 426 处死路径上一路绿着的原因 —— 两个已登记的族本来就是对的。而且债明天重新长出来,也不会有任何东西变红。

    它建议路由到 #1923(那张卡已经拥有这个文件)而不是另开一卡。同意,按此处理。


    裁定:ACCEPT。 PR #1929 转 ready,走普通队列 squash 落地。


    Generated by Claude Code

  6. removed their assignment
    on Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions