Uh oh!
There was an error while loading. Please reload this page.
fix(cli): read doc tags from src/docs frontmatter, and report what it cannot read - #10976
Conversation
…it cannot read `DocSchema.tags` was declared in 17.0.0 (#4509, ADR-0049) as the enforce half of enforce-or-remove: the resolver already compared against it (`matchesInclude` in `book.zod.ts`) and the REST book-tree route already forwarded it. But `collect-docs.ts` read frontmatter with `frontmatterScalar` alone — single-line scalars — and had no case for `tags`. On the flat `src/docs/*.md` path the docs recommend, a `tags:` block was dropped without a word: every doc reached `resolveBookTree` with `tags === undefined`, so a group declaring `include: { tag: 'tutorial' }` matched nothing and rendered empty. Two halves, both needed: - `frontmatterList` reads the two ordinary YAML sequence spellings — inline `tags: [a, b]` and the block form of `- item` lines — wired through `DocItem.tags`. The block sequence ends at the next frontmatter key, so a `group:` following a `tags:` block still parses. An authored `tags: []` parses and means what it says. - A `docs/frontmatter-tags` warning fires whenever `tags:` is present in a spelling the reader cannot parse (bare scalar, unterminated inline sequence, key with nothing under it). The reader stays deliberately minimal and is not becoming a YAML engine; the warning is what keeps that minimalism honest, by turning the next unanticipated spelling from a silent drop into a visible report. The same warning fires when a locale variant declares `tags:`, since tags belong to the doc rather than to one translation and `DocTranslationItem` carries no such field. No schema change: `DocSchema.tags` already declared the key, and only the collector could not produce it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
📓 Docs Drift Check5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅ What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 59fff57c36b0a363cb16ea0d82779baa964daf57 && git checkout 59fff57c36b0a363cb16ea0d82779baa964daf57
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin af1636c760879215f28eb2d9a720c3e393027a80 51c5e2af873ecd25b4cad6c0a5de3f4c807f02fe && git checkout -B drift-repro af1636c760879215f28eb2d9a720c3e393027a80 && git merge --no-ff 51c5e2af873ecd25b4cad6c0a5de3f4c807f02fe
node scripts/docs-audit/affected-docs.mjs --json af1636c760879215f28eb2d9a720c3e393027a80 |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32546382385 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
…callout PR #10483 added an interim warn callout stating that `tags` cannot be set from a `src/docs/*.md` file. #10976 has since taught the collector to read it, so every sentence of that callout is now false and the page steers authors away from the path that works. Three sites in content/docs/ui/doc-pages.mdx, all verified against the merged collector rather than transcribed from the filing card: - the warn callout: replaced (not deleted — leaving nothing swaps one false statement for no steer at all) with the two accepted YAML spellings, the reported-not-dropped behaviour of anything else, the surfaces that raise `docs/frontmatter-tags`, the locale-variant rule, and the name-convention preference that survives as a preference; - the frontmatter table row: "not read from frontmatter" becomes frontmatter `tags:`, matching the `order:` / `group:` rows above it; - the "three scalars" sentence: `tags` is a list, not a scalar. Documentation only — no source file touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
Fixes#10486
DocSchema.tagswas declared in 17.0.0 (#4509, ADR-0049) as the enforce half of enforce-or-remove: the resolver side already compared against it (matchesIncludeinpackages/spec/src/system/book.zod.ts) and the REST book-tree route already forwarded it. Butpackages/cli/src/utils/collect-docs.tsread frontmatter withfrontmatterScalaralone — single-line scalars — and had no case fortagsat all. On the flatsrc/docs/*.mdpath the docs actually recommend, atags:block was dropped without a word.This enforces that existing ruling on the path it was declared for. No
packages/specchange — the key was already declared; only the collector could not produce it.The reproduced drop (at branch head, before any change)
Two docs, one per frontmatter spelling, run through
collectDocsFromSrcand thenresolveBookTreewithinclude: { tag: 'tutorial' }:Silent in both directions, exactly as the card describes: no build warning that
tags:was dropped, and no resolver diagnostic that anincluderule matched nothing.Half 1 — a minimal
frontmatterListReads the two ordinary YAML sequence spellings and no others, wired through
DocItem.tags:Details worth naming: the block sequence ends at the next frontmatter key, so a
group:following atags:block still parses (pinned); items are unquoted the same wayfrontmatterScalarunquotes; and an authoredtags: []parses and means what it says — no tags, and no complaint, because nothing was dropped.After the change, the same repro yields
tutorials entries: ["crm_block","crm_inline","crm_quoted"].Half 2 — the loud
DocIssue, which is the half that mattersThe reader is deliberately minimal and is not becoming a YAML engine. That minimalism is only honest if what it cannot read is reported rather than dropped, so a
tags:present in any other spelling now raises adocs/frontmatter-tagswarning naming the consequence and quoting what it actually found:Three unreadable spellings are pinned: a bare scalar, an unterminated inline sequence, and a key with nothing under it. The doc is still collected — only its tags are missing. Warnings surface through the paths that already print
DocIssues:os lint(forwarded verbatim),os validate, andos compile(printWarning).One bounded addition inside the same defect class, named here rather than left silent: the same warning fires when a locale variant — a
name.locale.mdsibling such ascrm_guide.zh.md— declarestags:.DocTranslationItemcarries only label/description/content, so a parseable tags list on a variant would have vanished exactly as unparsed ones used to — the identical silent drop, one file over. The warning points at the base file. Evidence for the shape is the sibling declaration itself (DocTranslationItem) plusDocSchema.tagsbeing doc-level; no new verification surface, same file, same rule name, same gate family. Flagging it for the reviewing seat as a deliberate call, not an oversight.Reverse-verification — both legs, one lock acquisition
Predicted RED before running (the new tests assert positively on collected tags and on exactly one warning). Reverted only the production file, kept the tests:
All 8 failures are the 8 new tests, by name — the three reading tests, the three unreadable-spelling warnings, the locale-variant warning, and the resolver-level pin. Direction was as predicted, with no inversion and no diagnostic-count surprise.
Tests
Eight new tests in
packages/cli/src/utils/collect-docs.test.ts, including a resolver-level pin that imports the realresolveBookTreefrom@objectstack/spec/systemand asserts a group'sinclude: { tag }matches docs authored in either list form — the end of the claim this card makes, not just the collector's output. A warning nothing asserts is a warning that can silently stop firing, so each unreadable spelling asserts severity, rule, path, the named consequence, and the quoted text found.Full package suite, under
scripts/pm/os-verify-lock.sh:Gate union — derived and run at the final commit
51c5e2af87node scripts/pm/dispatch-gates.mjs(no paths passed; it derived the 3-file change set from the merge base itself). All green, quoting each gate's own verdict line:check:nul-bytesOK (scanned 6323 text file(s) … no raw ASCII control bytes)check:changeset-gate-self-testscheck:objectui-changesetcheck:slot-lookupslot-lookup ratchet holds: 107 unswept site(s) … none newcheck:test-source-aliasOK — 72 packages with tests scanned; 61 registered …check:type-source-resolutionOK — 76 packages with a tsconfig.json scanned …check-adr-0087-registration.mjscheck-changeset-no-major.mjscheck-empty-changeset.mjsNo empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)check-ci-filter-parity.mjsOK: all 82 declared cross-package glob(s) (71 unique) are covered …check-plugin-teardown-shape.mjsdocs-audit/check-affected-docs.mjscheck:query-options-erasureratchet holds: 67 unswept non-test site(s) … none newcheck:engine-double-contractOK — 376 pinned, 133 in the DEBT ledger, 2 exemptcheck:where-matcherconformance holds: 275 matcher(s) … 275 answer correctly or refuse loudlycheck:type-check-coverageOK — 64/77 workspace packages type-checked …check:type-check-debt(--re-measure)OK — 33 ledger entr(ies) re-measured in 199.0s, 1908 raw tsc error(s) total, none above its recorded numberThe ratchet was run properly built (
turbo run buildover the workspace:Tasks: 70 successful, 70 total). No ledger or baseline was edited. Its standingplugin-authsurplus note (TEST_DEBT records 109, tsc now reports 97) is a pre-existing condition onmainin a package this PR does not touch, and the gate itself marks itℹ … Not an error. Worth noting the ratchet's@objectstack/clientry measures the hidden siblingtest/tree; the new test file sits undersrc, whichinclude: ["src"]already compiles — so it is covered by the greentsc --noEmitabove rather than by the ledger.Filed, not folded — #10966
Triage flagged that the interim warn callout in
content/docs/ui/doc-pages.mdx(from PR #10483) states that tags are not read from Markdown frontmatter. Once this merges that callout becomes false, so it is filed as a linked docs chore — #10966, unassigned, labelledfinding+domain:cli(labels verified as landed),Blocked-by: #10486since the callout is correct until this lands. It is kept out of this PR so the file surface stayspackages/cli/src/utils/collect-docs.ts+ its test + the changeset. It names the exact callout, the stale table row beside it, and the "three scalars" sentence further down that also needs the wordscalarscorrected.⛔ Left as draft, not enqueued — the PM seat reviews and merges.
Generated by Claude Code