You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Third of the three toolchain audits. #566 (§4 docsite P3s) and #572 (docsite P2: Rspress performance.buildCache written every CI run) are the baseline; nothing they list is restated here. Same format as #572 / #576.
Versions installed → latest:@rspress/core + plugin-{llms,sitemap,twoslash,typedoc} 2.0.21 → 2.0.21 · @rsbuild/core 2.2.1 via lockfile (main: 2.2.3) → 2.2.3 · typedoc 0.28.20 → 0.28.20 and typedoc-plugin-markdown 4.12.0 → 4.13.0, both pinned exactly by @rspress/plugin-typedoc (upstream rspressmain still pins 4.12.0, so a lockfile refresh cannot move them) · typescript 6.0.3 → 7.0.2, blocked by two peers (typedoc5.0–6.0.x, @rspress/plugin-twoslash^6.0.3; the pin itself is Rstack skills audit: build compiler, package builds, tests, docsite #566 §4) · react/react-dom 19.2.8 → 19.2.8 · shiki 4.4.3 → 4.4.3 · Node 22.19.0 and pnpm 11.23.0 in docs.yml (builds here ran on Node 22.23). Actions: checkout@v7, pnpm/setup@v2, upload-pages-artifact@v5, configure-pages@v6, deploy-pages@v5 — all current majors.
Built and measured: full rspress build ×3 (cold ×2, warm ×1) plus an Rsdoctor run and three A/B builds (ssg.experimentalWorker: true; twoslash removed; TypeDoc router: 'module'), each under /usr/bin/time -v with a sampler that recorded whether any other build was running on the machine; pnpm docs:site:build gate; rspress preview of the built site crawled with headless Chromium at 1440×900 (114 pages: all 74 authored + 40 /api/**), 8 search-timing trials, language-switcher/dark-mode/404 matrices, an rspress dev sample, and curl -sI against the live Pages site to separate preview-server artefacts from production; a 25-sentence accuracy sample against packages/agent-bundle/src; a full sweep of 33 authored pages/locale and 916 TypeDoc pages/locale; dependency currency and the last 80 docs.yml runs.
Skills applied:rspress-best-practices, rspress-v2-upgrade, rspress-custom-theme, rspress-docs-generator, rspress-description-generator, rsbuild-best-practices (for builderConfig), rsdoctor-analysis. Every option cited below was checked against the installed @rspress/shared@2.0.21 typings (<shared> = node_modules/@rspress/shared/dist/index.d.ts) and @rspress/core@2.0.21 runtime (<core> = node_modules/@rspress/core/dist), not memory. cfg = website/rspress.config.ts.
Findings
P1 — user-visible breakage or a wrong statement on a reference page
P1 · docs/diagnostics.md:821 · rspress-docs-generator "a source-backed reference must render what the source says" · The AB4834 row has four cells (Code | Severity | Trigger | Recovery) under a three-column header (:785, Code | Severity | Trigger). GFM drops the extra cell, so the recovery hint (Add ".agent-bundle/routes.d.ts" to tsconfig.json include …) is silently absent from both generated reference/diagnostics pages and from GitHub. It is the only ragged row in 29 tables / 240 rows. Change: fold the recovery text into the Trigger cell, as the neighbouring AB48xx rows do. Risk: none.
P1 · docs/diagnostics.md vs website/docs/en/index.mdx:230, guide/distribution/validation.mdx:29,195-201, guide/authoring/mcp.mdx:735, reference/cli.mdx:99, index.mdx:175 · rspress-docs-generator "every user-facing item has a reference entry" · The home page promises "Every diagnostic has a stable AB code documented in the diagnostics reference", but four codes the guides explain with specific semantics have no row: AB4204, AB6005, AB8003, AB8004 (covered only by a one-line family catch-all). Breadth: packages/agent-bundle/src emits 400 distinct ABnnnn literals; docs/diagnostics.md names 213 explicitly (237 with ranges); 169 emitted codes have no explicit or ranged row and 93 of those only a family row — including all 69 AB80xx dev-server codes. Every code does fall under some family row, and the 6 documented codes with no src literal are legitimate (AB8206, AB8233–8235 live in the workbench/example; AB4807, AB4816 are marked retired). Change: add the four rows now; add a check (website check script or pnpm lint) that fails when an AB\d{4} cited in website/docs/**/*.mdx is absent from docs/diagnostics.md explicitly or in-range — it fails on exactly these four today. Risk: low; an "every src literal must be explicit" variant would fail on 169 codes and needs a ratchet.
P1 · website/docs/en/reference/cli.mdx:288 (zh :267) · AGENTS.md "reference prose must match the source" · "2 | An argument-parsing failure from the command-line parser." Five option validators throw a plain TypeError, not InvalidArgumentError: port, trialCount, installHost, installMode, installScope (src/cli.ts:217-246). Commander rethrows anything that is not commander.invalidArgument, and runCli maps only CommanderError to 2 — everything else is one AB5000 diagnostic on stderr and return 1 (src/cli.ts:1156-1161). Only --profile, --allow and doctor --host (cli.ts:251,260,268) exit 2. Change: "2 — a parser-level failure: unknown option, missing argument, or an invalid --profile, --allow, or doctor --host value. An invalid install <host>, --scope, --mode, --port, or --trials value is reported as one AB5000 diagnostic and exits 1." (Or make the five validators throw InvalidArgumentError; the mixed usage in cli.ts:217-268 looks accidental.) Risk: scripts branching on exit 2 for bad install arguments currently see 1 plus a JSON diagnostic.
P1 · website/docs/en/guide/authoring/hooks.mdx:132 (zh :120) · AGENTS.md "reference prose must match the source" · "outcome | continue, deny, or stop." src/adapters/hook-handler.ts:58 says "No event admits outcome: 'stop'", hookResultContract (:62-71) has no stop capability, and the generated wrappers accept the literal in the enum check (hook-contract.ts:895) and then fail it for every event (:902-905, "stop only accepts continue or deny with a reason" / "cannot stop the parent flow"). Change: "outcome | continue or deny. 'stop' is not a legal result on any event: HookHandler<E> excludes it and the wrapper rejects it at run time." Risk: a reader who returns { outcome: 'stop' } gets a tsc error and a hook-time failure.
P1 · cfg:156-167, website/tsconfig.typedoc.json:6-7, .github/workflows/docs.yml:54-61 · rspress-best-practices "validate before deployment" / AGENTS.md "state what the code does" · The twoslash compilerOptions.paths maps only agent-bundle, /config, /test, /eval; the TypeDoc tsconfig maps ten more (@agent-bundle/runtime*, rsc-markdown-stream) and its comment claims twoslash "mirrors" them — it does not. packages/agent-bundle/src/test/{render,matchers,target-capabilities}.ts and eval/assertions.ts import types from @agent-bundle/runtime, whose types is ./dist/index.d.ts; packages/rsc-runtime/dist does not exist in a fresh checkout and docs.yml runs pnpm install → pnpm lint → pnpm docs:site:build with no pnpm build. Twoslash only validates the sample's diagnostics, so the unresolved module becomes anyin the published hovers: in the built HTML, 18 of 784 twoslash popups render : any — renderRoute<…>(target: any, …), invokeMcpTool<…>(tool: any, …), RenderRouteOptionsBase.input?: any, ContractRouteFixture.input?: any — on guide/development/{testing,evaluations,workbench} in both locales (5 + 3 + 1 per locale). Change (minimal): add the ten aliases to the twoslash paths (absolute, like the existing four: '@agent-bundle/runtime': [path.join(repoRoot, 'packages/rsc-runtime/src/index.ts')], /plugin, /flight/server, /lineage, /mount, /notices, /notices/inbox-route, /state, /state/sqlite, 'rsc-markdown-stream'). Better: read tsconfig.typedoc.json once with ts.readConfigFile(file, ts.sys.readFile) (it is JSONC) and feed both consumers from one map — the "never copy a helper" rule. Risk: twoslash type-checks rsc-runtime/src too — slower fences, and TS-7-only syntax there fails under the pinned TS 6.0.3 (the failure mode cfg:122-126 already documents for TypeDoc).
P1 · cfg:79-81 (search) + website/plugins/mirror-api-locale.ts · rspress-best-practices "validate before deployment" ·The first search query races the index build and shows "No matching results". The per-locale index is 1.71 MB raw / 338 KB gz (952 docs, of which 916 TypeDoc pages contribute 1.21 MB); it is prefetched, but FlexSearch construction takes 1.7–2.3 s after download, and a query typed ≤1.2 s after opening the panel returned nothing or a partial list in 7 of 8 trials (快速开始 0 → 3 on retype, 参与贡献 0 → 1, AB6005 1 → 3). Root cause is in the theme: LocalProvider.init (<core>/theme/components/Search/logic/providers/LocalProvider.js:81-85) calls addAsync for every document without awaiting, so SearchPanel.initSearch flips initStatus to 'inited' (SearchPanel.js:97-98) while indexing is still running, and NoSearchResult renders as soon as the list is empty and initStatus === 'inited' (:271) — no spinner, so the term reads as absent. tokenize: 'full' makes the cost proportional to content size, and this site's index is ~70 % API pages. Change: (a) file upstream (await Promise.all around the addAsync calls, or gate the pending query on completion); (b) site-side, shrink the window: a modifySearchIndexData hook (<shared>:815) in mirror-api-locale.ts that blanks content for /api/ pages (titles and headers stay indexed, so CapabilityStateError is still found by title) — or search: false frontmatter on the mirrored API pages (FrontMatterMeta.search, <shared>:90; honoured at createPageData.js:33) to drop them entirely. Risk: (b) trades API body-text search for a ~3× smaller index; it does not remove the race, only its window.
P1 · cfg:115 (llmsUI: { placement: 'outline' }) + cfg:170-191 (exclude: /api/) · rspress-best-practices "no broken assets" ·"Copy Markdown" / "Open in ChatGPT" / "Open in Claude" are broken on all 1,832 /api/** pages.llmsUI renders the outline rows on every page (Outline/index.js:35-39) and the hidden LlmsHint advertises routePathToMdPath(page.routePath); both pluginLlms entries exclude /api/, and include/exclude apply to mdFiles as well as llms.txt (<llms>/index.js:142-148,236-244), so no .md exists for those routes. Live: https://scriptedalchemy.github.io/agent-bundle/api/classes/api.TargetRegistry.md → 404; the ChatGPT/Claude links embed that URL; getLlmsCopyContent.js:10 copies whatever response.text() returns with no status check (the 404 page — on preview, the home-page HTML). LlmsUI has no route gating (<shared>:292-315: injectLlmsHint, viewOptions, placement only). Change (cheapest, custom-theme level 3): in website/theme/index.tsx re-export LlmsCopyRow, LlmsOpenRow, LlmsHint wrappers that return null when usePageData().page.routePath includes /api/. Alternative that also closes #566 §4's llms-full.txt gap: a third pluginLlms entry { llmsTxt: false, llmsFullTxt: false, mdFiles: { mdxToMd: false }, include: ({ page }) => page.routePath.includes('/api/') } (Options allows false for both, <llms>/index.d.ts:42-50) — at the cost of 1,832 extra SSG-MD renders per build. Risk: none for the wrapper; build time for the alternative.
P2 — real quality / perf / maintainability gain with a concrete change
P2 · cfg:127-144 (pluginTypeDoc), website/docs/{en,zh}/api/_meta.json · rspress-docs-generator "shape the API surface for readers" / rsdoctor-analysis ·@rspress/plugin-typedoc bootstraps TypeDoc with router: 'kind' (<typedoc>/index.js:41), which emits 916 pages per locale (13 modules, 151 functions, 21 classes, 499 interfaces, 186 type aliases, 45 variables). Consequences measured on the built site: every one of the 1,832 API routes renders a 921-item sidebar (the six dir groups carry no collapsed, and 2.0.21 defaults collapsed = false, SidebarGroup.js:25), items sort by filename module.Symbol while labels come from the h1, so the module grouping is invisible and two labels collide (Type Alias: AgentBundleConfig ×2, Variable: default ×2); each API HTML page averages 326 KB raw / 17.7 KB gz (authored: 54 KB) because the SSR'd <aside> embeds all 916 sidebar links — collapsed groups render their children with gridTemplateRows: '0fr', not conditionally — so 561 MB of the 602 MB of HTML (93 %) is sidebar, doc_build is 623 MB (the published Pages site sits at ~62 % of GitHub's documented 1 GB limit; the Pages artifact is 36 MB compressed per deploy); the entry chunk static/js/index.*.js (2.23 MB raw / 368 KB gz, loaded on every page) is O(pages): virtual-page-data 1,346 KB (219 KB gz — toc/frontmatter/title for all 1,906 routes) + virtual-routes 488 KB (73 KB gz) + virtual-site-data 287 KB (31 KB gz); 143 API pages have ≤2 body lines. The zh mirror (plugins/mirror-api-locale.ts) doubles all of it: 916 → 1,832 pages ≈ +300 MB HTML, ≈ +40 s single-thread SSG, +916 routes in every chunk above — while the mirrored body stays English. A/B (this audit, link checks disabled, otherwise identical):app.options.setValue('router', 'module') in the existing setup hook → 102 routes instead of 1,906, wall 90.3 s vs 186.3 s (−52 %), peak RSS 4.99 GB vs 8.52 GB (−41 %), doc_build8.4 MB vs 623 MB, index.*.js474 KB vs 2.23 MB; the largest module page (api/api.html) is 731 KB. Costs found by the A/B: 43 authored → API links per locale target member URLs (/api/classes/api.TargetRegistry) and must become module anchors; TypeDoc's slugger emits -1-suffixed anchors for duplicate names that Rspress's slugger does not, so checkAnchors fails on the generated pages until the titles are deduplicated; the tracked api/_meta.json (hand-maintained — the plugin only writes it when absent) must be flattened to the 13 module files. Change: (1) today, at zero risk: add "collapsed": true to the six dir entries in both _meta.json files (fixes the 921-item sidebar UX only — the HTML is unchanged); (2) then pick the lever — the module router as measured, an intermediate typedoc-plugin-markdown router, and/or dropping the zh mirror in favour of pointing zh API links at /api/ — and rewrite the 43 links; (3) either way, the requiredToBeDocumented warnings (~65/151 functions, ~250/499 interfaces, ~120/186 type aliases with no JSDoc prose) mean the thin pages stay thin until the source is documented. Risk: (1) none; (2) URL churn for any external links into /api/** (the sitemap advertises 1,832 of them today).
P2 · cfg (no ssg) · rsbuild-best-practices "measure the resolved config before tuning" · SSG is the largest build phase — 77–87 s of the ~186 s — because the default path (<core>/node/ssg/renderPages.js:85-91) renders all 1,906 routes in one thread (SSGConcurrency 32 is async, and rendering is CPU-bound). ssg.experimentalWorker: true (<shared>:1146-1157, default false) spawns a Tinypool of min(ceil(pages/100), cpus) threads (:46-50). Two quiet A/B runs: SSG 77.4 s → 8.7 s / 7.7 s, wall 186.3 s → 101.1 s / 110.8 s (−46 % / −41 %), peak RSS 8.52 → 9.82 / 10.35 GB; output identical (1,907 HTML files both, 7/7 sampled pages byte-identical after content-hash normalisation, HTML total 602,258,970 vs 602,258,375 B). Together with the TypeDoc surface above this is the whole "why does the docsite take three minutes" answer (#572 measured the persistent-cache lever at ~4 %; the warm run here was −3 %). Change:ssg: { experimentalWorker: true }, measured once on the hosted runner before committing to it — ubuntu-latest has 4 vCPU → 4 threads → projected SSG ≈ 20–25 s and wall ≈ −55–65 s, not −85 s. Risk: experimental API; worker-written HTML bypasses Rsbuild's asset table (the file-size table no longer lists HTML — cosmetic); +1.3–1.8 GB RSS to verify on the runner.
P2 · cfg:116-118 (editLink) · rspress-custom-theme "override at the lowest level that works" ·useEditLink builds docRepoBaseUrl + page._relativePath unconditionally (<core>/theme/components/EditLink/useEditLink.js:10-11) and the link renders in every outline. en/api/** (916), zh/api/** (916) and the 8 */reference/{hosts,events,notices,diagnostics}.md pages are gitignored (.gitignore:19-25), so 1,840 pages carry an "Edit this page / 编辑此页面" link to a GitHub 404 (verified: …/tree/main/website/docs/en/reference/hosts.md, …/en/api/index.md). 2.0.21 has no per-page switch: EditLink is { docRepoBaseUrl } only (<shared>:31-37) and FrontMatterMeta has no editLink key. Change: in website/theme/index.tsx, wrap EditLink from @rspress/core/theme-original: return null when page._relativePath matches ^(en|zh)/(api/|reference/(hosts|events|notices|diagnostics)\.md$), or — better for the reference pages — point them at their real sources (docs/diagnostics.md, packages/agent-bundle/src/adapters/capabilities/*.json) with a small map. Risk: the regex must track .gitignore.
P2 · cfg:86 (markdown), AGENTS.md "code samples wrap at roughly 90 columns" · rspress-best-practices · Measured at 1440×900 with the outline present (every authored page has one): the code-block container is 690 px, the pre 686 px, 14 px monospace at 8.4 px/char → ~81 characters fit, so the 90-column rule is ~10 columns too wide. 66 of 201 visible code blocks on en authored pages (33 %, 18 of 33 pages) need a horizontal scrollbar (39 % across both locales, 132/336 blocks on 39 pages); every block is white-space: pre with the wrap toggle off by default. Worst: /reference/cli (158-char line, +682 px, 3 of 4 blocks), /guide/authoring/package-entries (139 chars, +530 px, 9/30), /guide/authoring/mcp (124 chars, +396 px, 17/38), /guide/distribution/installation (124, +396), /guide/authoring/hooks (114, +312, 5/9), /guide/development/testing (114, +312, 4/7), /reference/diagnostics (104, +228, 2/2). At the source level 26 fences exceed 90 columns and 11 exceed 100 (reference/cli.mdx:153 = 158; guide/distribution/installation.mdx:165, guide/authoring/mcp.mdx:155 = 124). Change: either markdown: { defaultWrapCode: true } (<shared>:383-387; the copy button still copies raw text) or correct AGENTS.md to "≤ 80 columns" and re-flow the 26 fences above 90 first. Risk: wrapped shell one-liners are less scannable; none for correctness.
P2 · cfg:110-113 (languageParity), website/package.json:11 (check) · AGENTS.md "en page and its zh translation in the same PR" ·checkLanguageParity compares only the set of .md/.mdx relative paths across config.locales (checkLanguageParity.js:16-19,36-38,62-67); _meta.json, _nav.json, frontmatter, headings and code are not compared, and it runs in finally after the whole build, so a miss costs a full build. Today nothing is stale (all 33 pairs share the same last commit and commit count; h2/h3, table, link, AB-code and ::: counts match; fences are byte-identical after stripping comments — the only diffs are translated trailing #/// comments in 16 fences across 10 pages), but no gate keeps it that way. Change (minimal):website/scripts/check-locale-drift.mjs, run by checkbeforerspress build, failing when for any en/X ↔ zh/X the fence count differs, fence bodies differ after stripping comment lines and trailing comments, the AB\d{4} sets differ, table-row counts differ, or h2/h3 counts differ by >2; plus a PR-level complement that fails when git diff --name-only origin/main...HEAD touches en/X.mdx without zh/X.mdx. Risk: false positives on a deliberately zh-specific example (none exist today) or translated string literals inside code (all kept English today).
P2 · AGENTS.md:116-117 ("pnpm docs:site:build is the gate"), cfg:98-106, website/package.json:11 · rspress-best-practices "verify broken links before deployment" ·The gate does not check the link forms the site itself relies on.<core>/node/mdx/remarkPlugins/link.js:162-174 visits only mdast link/definition nodes and image.js:77,100-105 sees markdown images plus <img>only as mdxJsx nodes (i.e. in .mdx); nothing inspects frontmatter, _nav.json, or raw HTML/JSX in .md. A probe docs root with a dead hero.actions[].link, dead features[].link, missing hero.image, <a href="/dead"> in .md and .mdx, <Link href="/dead">, a dead _nav.json entry, an en page with no zh twin under a directory named capi/ (the parity exclude is a substring match on api/), and a zh page with unrelated content built with exit 0 — while the same probe root confirmed that [x](/abs), [x](./rel), [x](/x.md), definitions, #anchors (self, relative, absolute, cross-locale), markdown images, <img> in .mdx, _meta.json entries and a missing zh file all fail the build as they should. The live docs carry 28 frontmatter links (docs/{en,zh}/index.mdx:16-65) and 10 _nav.json links in exactly the unchecked forms. Second miss: with the persistent Rspack cache that a local build writes to website/node_modules/.cache/rspack/ by default, a dead anchor added to page A that targets unchanged page B passes (B's loader never runs, so the RouteService.onRouteAnchorIds consumer, route/RouteService.js:139-148, never fires) — reproduced: warm exit 0, same content cold exit 1. CI is safe only because the cache is not persisted between runs; a local pnpm docs:site:build re-run is not. Change: a ~50-line post-build script in website/ (run by check after rspress build) that walks doc_build/**/*.html, resolves every href/src under /agent-bundle/ to a file and every #id to an id= in the target — covering frontmatter, nav, sidebar, JSX, generated pages and the cache case uniformly (+2–3 s); plus RSPRESS_PERSISTENT_CACHE=false in the check script itself (extends #572's docs.yml-only proposal; the warm gain is 3 %). The locale-drift check above is the parity half of the same gap. Risk: low.
P2 · website/docs/en/reference/configuration.mdx:24,51-53 (zh :24,50) · AGENTS.md "reference prose must match the source" · (a) "targets | string[] | Adapter-selected." — exactly one adapter is registered as default (.register(portableAdapter, { default: true }), src/adapters/registry.ts:780-786) and resolveTargets returns registry.defaultTargetNames() when config omits targets and no --target was passed (src/config/normalize.ts:1114-1122); no page says so. (b) "defineConfig accepts an AgentBundleConfig … and validateSource enforces the rules on this page" while the table lists routes and evals — AgentBundleConfig (src/core/types.ts:309-328) declares neither; both ride the [key: string]: unknown index signature (types.ts:328, src/routes/graph.ts:336), config/validate.ts never reads evals, and the evals rules at :145-147 are enforced by normalizeEvalConfig (src/eval/config.ts:82-125) only when an eval or the Workbench loads the config, as EVAL_CONFIG_INVALID, not an AB diagnostic. Sibling: :38marketplace "Adapter-selected." → false (only marketplace === true is carried, normalize.ts:1301). Change: (a) "['portable'] (the only default adapter); --target overrides both." (b) add after :53: "evals and routes are not members of AgentBundleConfig; they are read through its index signature, so tsc does not check them and agent-bundle validate does not report them — the evals rules below fire when an eval runs (EVAL_CONFIG_INVALID)." Risk: a user omitting targets expects host artifacts and gets only artifact/portable; a misspelled top-level key is accepted by both tsc and validateSource.
P2 · website/docs/en/guide/authoring/hooks.mdx:146 (zh :133) · AGENTS.md "reference prose must match the source" · "agentStop … on Codex it cannot add context." The Cursor wrapper also rejects it (hook-contract.ts:906, "Cursor … has no additional-context channel") and the typed contract sets agentStop: { additionalContext: false } for every host (hook-handler.ts:66-67: "Claude carries additionalContext on SubagentStop; Codex and Cursor reject it"). Change: "…and only Claude Code carries additionalContext from it — Codex and Cursor reject it, and the typed contract excludes it everywhere." Risk: none.
P3 — polish
cfg (no head), website/docs/public/ · rspress-best-practices "first-class options first" · SSG emits og:title/og:description and en/zh hreflang alternates on 114/114 pages but no rel=canonical, og:url, og:image, og:type, twitter:card, theme-color or x-default; 25 pages have a meta description >160 chars (max 842 on api/type-aliases/*, inherited from TypeDoc bodies); /api/ is titled "agent-bundle - agent-bundle". UserConfig.head (<shared>:1079) accepts per-route functions evaluated at SSG, so canonical + og:url are one entry (withBase/withSiteOrigin/normalizeHref are exported from @rspress/core). Live /agent-bundle/robots.txt → 404 and plugin-sitemap emits none; sitemap.xml (1,906 <loc>) is therefore unadvertised — add website/docs/public/robots.txt with Sitemap: https://scriptedalchemy.github.io/agent-bundle/sitemap.xml.
cfg:83 (cleanUrls: true) · deployment · leaf pages are emitted as x.html, so /agent-bundle/guide/start/quick-start and .html are 200 on Pages while the trailing-slash form …/quick-start/ is 404 on Pages (200 under rspress preview, whose SPA fallback masks it). The site never emits that form (sitemap, hreflang and nav are extensionless), so this only bites shared links; the cheap fix is a redirect snippet in the 404 page (head script: strip a trailing slash when location.pathname is not a directory route).
rspress preview · acceptance workflow · the rsbuild static server serves index.html for every extensionless dotted route (/api/classes/api.TargetRegistry), producing React feat(routes): reference MCP App resourceUri from static config and resolve App templates route-relative (#388) #418 hydration errors and a content flash on 30/30 sampled API pages in preview only; the same pages hydrate clean as .html and on Pages. Any browser acceptance against preview therefore shows fake errors and hides the trailing-slash 404 above — preview doc_build with a plain static server (npx serve doc_build) or document it.
website/plugins/mirror-api-locale.ts · i18n · 916 zh mirror pages carry <html lang="zh"> and Chinese chrome (目录 / 上一页 / 下一页) around an English body with no notice that the API reference is English-only, and inherit the English site description. Prepend an :::info note in the mirror plugin or set lang/description frontmatter there.
cfg:137 · rspress-best-practices ·pageTitleTemplates.index: '{projectName} {version}' renders # agent-bundle — {version} is empty because includeVersion is not set; drop it or app.options.setValue('includeVersion', true). website/docs/en/_nav.json:22-26: "Type API" lands on the bare generated index (13 bullets named by entry file — index, routes/public, test/browser — no prose, 0/916 API pages have a description); the mapping to agent-bundle, agent-bundle/routes, … exists only in reference/api.mdx — point the nav there or add a "Module page" column to reference/api.mdx:12-29.
website/plugins/generated-reference.ts:18-36 · AGENTS.md "look for the helper before writing it" · re-declares loose CapabilityRow/HostCapabilityTable shapes while src/adapters/capability-state.ts:47,94,99 owns EventRouteCapabilityTableEntry, CapabilityTableRow, NoticeDeliveryCapabilityTableEntry; the package does not export them, so a schema change renders stale columns silently. Export them from a public entry and import type in the plugin (changeset + api/** regeneration).
build hygiene · (a) the entry chunk hash is non-deterministic: two builds of the same tree produced index.88eed14fa7.js and index.5960409c3a.js, same length 2,227,901 B, first difference at byte 1,837,065 = sidebar key order in virtual-site-data ("/reference" first vs "/contributing" first) — every deploy invalidates the 368 KB gz entry for every returning visitor; upstream @rspress/core auto-nav-sidebar, no repo change. (b) sitemap.xmllastmod = source-file mtime (<sitemap>/index.js:51): 66 URLs carry the checkout time and 1,836 the build time, so every deploy tells crawlers every page changed. (c) Rsbuild's default performance.printFileSize prints a 3,900-line size table per CI log, 4.6–8.9 s after ready built; builderConfig: { performance: { printFileSize: { detail: false } } } removes the noise (rsbuild-best-practices; the seconds are not fully attributed — treat as log hygiene).
cfg:92-96, cfg:79-81 · rspress-best-practices "remove unused plugins" · the docs contain 2 [!code highlight] markers and zero [!code ++/--]/[!code focus], so transformerNotationDiff() and transformerNotationFocus() run on every fence for nothing; search.codeBlocks: true restates the default (<shared>:356-359). themeConfig.footer is unset (repo is Apache-2.0).
shared snippets · the minimal defineConfig fence is byte-identical in 4 en pages (guide/authoring/index.mdx:12, guide/start/project-structure.mdx:69, guide/start/quick-start.mdx:68, reference/configuration.mdx:10) and four paragraphs are duplicated verbatim across pages (reference/security.mdx:19 ≡ guide/development/evaluations.mdx:196; reference/limitations.mdx:50-51 ≡ guide/development/testing.mdx:350-351; guide/distribution/installation.mdx:7 ≡ guide/start/quick-start.mdx:107; guide/distribution/validation.mdx:179 ≡ guide/authoring/package-entries.mdx:223) — MDX fragment candidates.
hooks.mdx:139 · "sessionStart, afterTool, and agentStart cannot deny" is true for HookHandler<E> and the Claude/Codex wrapper, but the Cursor wrapper admits a denying agentStart with a reason (hook-contract.ts:899-901, hook-handler.ts:16) — "the typed contract rejects it; Cursor's runtime tolerates one".
theme a11y / contrast (upstream defaults; styles/index.css to override) · appearance switch is a div.rp-switch-appearance with no role/name and not focusable; the GitHub social link has no accessible name; no skip link; home page has no <main>/<h1>; prev/next 12 px labels 1.85:1 light / 3.19:1 dark; active-nav brand #0d8f80 on white 3.99:1; Shiki light string token #31a94d 3.04:1 (body/table/code base ≥11:1 in both modes). Override --rp-c-text-3 and the brand token; add aria-label via themeConfig.socialLinks.
.github/workflows/docs.yml:44 ·name: Docs site (Node 22.19) is a required context on main (required_status_checks also names Host install proofs (Node 22.19), Examples check (Node 22.19), Release gates (Node 22.19)); bumping Node renames the check and blocks every PR until protection is edited — use a stable name: Docs site. package.json:24 root typecheck covers root/workbench/create-agent-bundle/rsc-markdown-stream but not website/tsconfig.json (config, plugins/**, theme/**, 1,307 lines), which only docs:site:build compiles — so AGENTS.md's "gate before pushing" never typechecks the website locally or in ci.yml Verify; append pnpm --filter @agent-bundle/docs typecheck. website/package.json:24,28:@types/react-dom and agent-bundle: workspace:* have no importer (twoslash resolves agent-bundle* via paths, TypeDoc via tsconfig.typedoc.json); react/react-dom are also unreferenced by website code but @rspress/core aliases to <cwd>/node_modules/reactif present, else its own copy — remove both or neither. rstest.integration-tests.ts:8:workspaceTestFileGlob = 'packages/**/tests/**/*.test.ts' means any website/**/*.test.ts would never be collected, and scripts/classify-docs-only.mjs:20 skips Verify for website/**-only PRs — zero tests exist for the ~900-line generated-reference.ts today, so nothing is silently skipped yet; when the first test lands, add website/**/*.test.ts to rstest.unit.config.tsand run it in docs.yml. Actions are major-tag pinned, none by SHA (dependency-review-action runs; Dependabot updates SHA pins). On main, .github/dependabot.yml:7-8: since ci: split Verify into a fast leg + 2 integration shards behind the required gate; pin the browser; retry setup; nightly pools (#576) #583pnpm/setup@v2 lives only in the composite .github/actions/setup-workspace/action.yml, which the github-actions ecosystem with directory: / does not scan — use directories: ["/", "/.github/actions/*"].
Measurements
(a) Builds (/usr/bin/time -v, website/, Node 22.23, this machine; "quiet" = the sampler saw no other build during the run)
Rspack web + node compile incl. MDX/twoslash (→ Rendering pages)
67.3
61.9
65.4 / 69.0
29.7
SSG (Pages rendered in)
77.4
87.2
8.7 / 7.7
90.7
emit + size table
6.1
8.5
0.8 / 0.5
7.1
parity + sitemap + llms
<0.1
<0.1
<0.1
<0.1
Twoslash is worth its cost: 64 opted-in fences (32 per locale; explicitTrigger defaults to true, so the other 120 ts/tsx/js fences are not type-checked), 26 output pages carry hovers (guide/authoring/mcp 51), compile −37.6 s without it (~20 % of wall). Shiki and twoslash ship no client JS (@shikijs/langs 8.6 MB source, parsedSize 0, tree-shaken); hover HTML is inlined in the route chunks (mcp.mdx route 287 KB / 30 KB gz).
Client JS per page (raw / gz):index.*.js 2,227,901 / 368,666 · search chunk (flexsearch) 226,677 / 74,372, a blocking <script> on every page · lib-react 189,213 / 59,843 · lib-router 35,321 / 12,714 · styles 79,775 / 14,677 CSS · route chunk 4.8–29.5 KB / 1–4.6 KB → ≈ 2.70 MB raw / 520 KB gz per page, identical on home, guide and API pages, plus the 1.71 MB / 338 KB gz search index fetched at requestIdleCallback on every page (SearchPanel.js:177-182), not on focus. Rsdoctor 1.6.3: no E1001/E1002 duplicates; top retained modules by gz are .rspress/virtual-page-data.js 1,345.6 KB / 218.9 KB, virtual-routes.js 487.7 / 73.2, react-dom 174.1 / 54.9, the reference/diagnostics.md routes 257 / 53 each, guide/authoring/mcp.mdx routes 268–287 / 30–34, virtual-site-data.js 286.8 / 30.8 — i.e. the bundle is dominated by the repo's own route count, not by dependencies. Rspack cache after a build: 90 MB in website/node_modules/.cache/rspack/.
Cursor maps all five matchers; Claude no agent; Codex no agent/file.read
capabilities/*.json hooks.matchers
TRUE
25
reference/runtime-environment.mdx:42
AGENT_BUNDLE_ENV_FILE delimiter / later wins / none
launch-env.ts:28-33,108
TRUE
Sweeps behind the sample: AB codes — 101 distinct on en pages, 0 without an emitter, 0 outside every family row, 4 named only at family level (the P1); CLI flags — 45 parsed, documented-not-parsed 0, parsed-not-documented 0 (after excluding 23 foreign-tool flags that are real in their own parsers); config keys — 19 + 4 host extensions documented, 17 + 4 typed, documented-but-untyped routes, evals, known-but-undocumented 0; unknown top-level keys are rejected nowhere (index signature) and no page claims otherwise.
(f) Dependency / CI currency — see preamble for versions. docs.yml last 80 runs: 62 success, 8 cancelled (PR supersession), 8 action_required (all changeset-release/main bot PRs — repo Actions-approval setting, ci.yml shows the same), 0 failures; build-job median 166 s (n = 9, 130–193 s), deploy 16 s, timeout-minutes: 25 / 10 ample; head-of-main guard and deploy-pages succeeded on every completed push run. pnpm audit: 2 moderate (qs@6.15.3), 0 of 28 paths through @rspress/typedoc/shiki/mdx; 0 open Dependabot alerts. Gate parity: rslint covers website/** in Verify, docs.yml and locally; tsc -p website/tsconfig.json, the TypeDoc + twoslash compile of the package sources under TS 6.0.3, and the Rspress dead-link/anchor/image/parity checks run only in docs.yml; website/** unit tests run nowhere (none exist).
Already compliant
Config surface:siteOrigin is a documented UserConfig key (<shared>:1052-1055) consumed by the sitemap fallback, hreflang, llms.txt URLs and absolute-link normalisation; base: '/agent-bundle/' + default outDir match docs.yml:66 and the Pages html_url; lang: 'en' + route.localeRedirect: 'never' emit no redirect script, en at /, zh at /zh/; route.cleanUrls: true (extensionless hrefs, hreflang, sitemap loc and address bar all agree); zh UI strings resolve through DEFAULT_I18N_TEXT (目录, 上一页/下一页, 搜索, 编辑此页面, 复制 Markdown, 切换代码换行) so no themeConfig.locales block is needed; local search builds one index per locale; markdown.shikilazy: true covers every fence language in use (sh 118, ts 100, text 20, tsx 16, md 2, json 2) and the extra langs only pre-warm grammars for twoslash hovers as the comment says; link.checkDeadLinks: { excludes }, checkAnchors: true, image.checkDeadImages: true match RemarkLinkOptions/RemarkImageOptions; plugin order (TypeDoc config → setup → convert → writeOutput, then mirrorApiLocale, then generatedReference) is sequential with no race in dev; theme/index.tsx uses the first-class afterFeatures slot and re-exports theme-original with no cycle; styles/index.css uses only existing --rp-* tokens and BEM classes (no dead selectors); icon/logo/hero.image are base-prefixed; no deprecated keys (themeConfig.localeRedirect, addRuntimeModules) and no v1 imports; website/tsconfig.json scope is correct; socialLinks.mode: 'link' valid.
Content architecture: nav parity (5 entries, same order, zh activeMatch carries /zh/); 10 _meta.json per locale, identical order, translated labels, dir-section-header for the four Guide groups; a missing sidebar target throws at build ("Missing page file"), so the coupling to generated pages is fail-loud; exactly one h1 per page (66/66), 0 heading skips, titles unique per locale; all frontmatter keys valid; 0 orphans (33/33 pages reachable from nav + sidebar per locale; 953 generated pages reachable); no page under 120 words; only the two deliberate cross-locale links (reference/api.mdx); all 13 blob/main/tree/main GitHub targets exist; generated reference plugin fails loudly on malformed input, translates zh headings/prose/info boxes, escapes | < > { }, keeps all 240 diagnostics rows; TypeDoc surface: 916 pages, zh mirror 916/916 byte-identical, 3,149 internal links with 0 broken, 43 authored → API links per locale all resolve, <>{} escaped, \_ absent from titles, no Defined in lines to rot.
Runtime: zero console errors/warnings/failed requests on all authored pages; CLS 0 everywhere; every table in a scroll container and no page-level horizontal overflow at 1440; base handled in assets, favicon, 404.html, sitemap, llms, home CTAs, hreflang; language switcher lands on the same slug in both directions; dark mode persists; body/table/code base contrast ≥11:1 both modes; search finds fence-only identifiers, API symbols and CJK phrases; all images have alt; rspress dev loads clean.
Gate: dead link/anchor/image checks are build errors, not warnings, in production (link.js:35,74, image.js:33); relative, absolute, .md-suffixed, definition, self/relative/absolute/cross-locale anchor, markdown-image, .mdx<img>, _meta.json and missing-zh-file probes all fail the build as documented; generated TypeDoc and reference pages are written in config hooks before RouteService.create, so anchors into them resolve (cold); checkDeadLinks.excludes correctly whitelists the llms(-full).txt assets; languageParity.exclude: ['api'] has no accidental substring victim in the real tree; explicitTrigger: true keeps twoslash to opted-in fences; Rsdoctor E1001/E1002 clean.
Deps / CI: all @rspress/* at latest; no legacy rspress package; Node engines consistent across root, website, Rspress, Rsbuild, typedoc, shiki; concurrency groups correct (docs-main non-cancelling, docs-<n> cancelling for PRs, pages group on deploy); permissions minimal; Pages build_type: workflow, https_enforced, branch policy main only; Dependabot covers the pnpm workspace sub-manifests and workflows; .gitignore covers doc_build/, api/**/*.md, the four generated reference pages.
Caveats
Builds ran on a shared 20-thread machine; the sampler flagged foreign builds during cold feat: add RSC plugin framework and audiobook curator example #1 (overlapping lane 4's build) and the no-twoslash A/B (12/16 samples), so those two are upper bounds — the no-twoslash compile-phase delta is measured in-process and robust, its wall figure is not. Single runs, not medians (cold repeat variance 190.2 vs 186.3 s). The 4-vCPU CI projection for experimentalWorker is arithmetic, not measured, and its peak RSS (10.3 GB here) must be re-measured on the hosted runner before adoption.
The TypeDoc router: 'module' A/B disabled checkDeadLinks/checkAnchors (the variant fails them for the reasons listed) and flattened api/_meta.json in the scratch worktree only; its numbers are therefore a lower bound on the work, not a drop-in config.
Runtime numbers come from rspress preview on localhost (LCP/timing not network-realistic); production behaviour was cross-checked with curl only, not a browser run against Pages. The API sample was 40 of 1,832 pages; all authored pages were crawled. a11y checks are hand-rolled (axe-core is not installed).
Search index-build timing was measured once per fresh browser context, 8 trials; the root-cause reading of LocalProvider.init is from the shipped dist, not a debugger.
zh "word counts" are CJK character counts; JSDoc-coverage figures are a text heuristic; description lengths are code points.
The _meta.json/sidebar collapse behaviour and hook ordering were read from @rspress/core@2.0.21 source and then confirmed on the rendered site (921-item sidebar observed).
main moved during the audit; the docs.yml/dependabot.yml P3s were re-checked against main, everything else against b75073b24.
Lanes
Config surface — every cfg key vs <shared>/<core> typings and runtime; produced the twoslash-any P1, the llmsUI and EditLink findings, the head/robots P3s, and the "already compliant" config list.
Content architecture — 33 pages/locale, 10 _meta.json/_nav.json per locale, 916 API pages/locale; produced both diagnostics.md P1s, the sidebar/collapsed and locale-drift P2s, descriptions/snippets/thin-page P3s, orphan/heading/frontmatter/link sweeps.
Build measurement — cold ×2 / warm builds, phase breakdown, Rsdoctor, the experimentalWorker and no-twoslash A/Bs, per-page client JS, and 18 probe docs roots against the dead-link/anchor/image/parity checks; produced the experimentalWorker P2, the gate false-negative P2 (frontmatter/nav/JSX links, warm-cache anchors), the sidebar-is-93 %-of-HTML analysis, and the hash/lastmod/printFileSize P3s.
Runtime quality — headless-Chromium crawl of 114 pages plus search, switcher, dark-mode, 404 and live-Pages matrices; produced the search-race P1, the live 404 evidence for llmsUI/EditLink/robots, the overflow measurements, and the contrast/a11y P3s.
Accuracy sampling — 25 sentences + AB-code / CLI-flag / config-key sweeps; 20 % drift, the cli.mdx:288 and hooks.mdx:132 P1s, the configuration.mdx and hooks.mdx:146 P2s.
Currency / deps / docs.yml — versions vs latest, peer constraints, action pins, last 80 runs, gate parity; no P1/P2, six P3s.
Coordinator — the TypeDoc router: 'module' A/B in a third worktree; verification of every P1 against the built HTML, the config, and live Pages; integration.
Third of the three toolchain audits. #566 (§4 docsite P3s) and #572 (docsite P2: Rspress
performance.buildCachewritten every CI run) are the baseline; nothing they list is restated here. Same format as #572 / #576.Preamble
b75073b24(origin/mainat start, feat(workbench): launch the standalone MCP Inspector and open it in a new tab #579) in detached worktrees under/fast/projects/agent-bundle-wt/audit-rspress*(removed afterwards).mainmoved twice during the audit (ci: split Verify into a fast leg + 2 integration shards behind the required gate; pin the browser; retry setup; nightly pools (#576) #583, build: compile on one Rspack engine with Rslib 1.0 and Rsbuild 2.2 (#566) #575);website/**is byte-identical on both, and the two places where the workflow differs are marked "onmain".main. The only sampled statement that the Emit one composite plugin artifact; use targets to select the host projections inside it #555 composite-root rewrite would change is theplugintarget's install surface row (reference/targets-artifacts.mdx:19-23); nothing else below depends on per-host output roots.@rspress/core+plugin-{llms,sitemap,twoslash,typedoc}2.0.21 → 2.0.21 ·@rsbuild/core2.2.1 via lockfile (main: 2.2.3) → 2.2.3 ·typedoc0.28.20 → 0.28.20 andtypedoc-plugin-markdown4.12.0 → 4.13.0, both pinned exactly by@rspress/plugin-typedoc(upstreamrspressmainstill pins 4.12.0, so a lockfile refresh cannot move them) ·typescript6.0.3 → 7.0.2, blocked by two peers (typedoc5.0–6.0.x,@rspress/plugin-twoslash^6.0.3; the pin itself is Rstack skills audit: build compiler, package builds, tests, docsite #566 §4) ·react/react-dom19.2.8 → 19.2.8 ·shiki4.4.3 → 4.4.3 · Node 22.19.0 and pnpm 11.23.0 indocs.yml(builds here ran on Node 22.23). Actions:checkout@v7,pnpm/setup@v2,upload-pages-artifact@v5,configure-pages@v6,deploy-pages@v5— all current majors.rspress build×3 (cold ×2, warm ×1) plus an Rsdoctor run and three A/B builds (ssg.experimentalWorker: true; twoslash removed; TypeDocrouter: 'module'), each under/usr/bin/time -vwith a sampler that recorded whether any other build was running on the machine;pnpm docs:site:buildgate;rspress previewof the built site crawled with headless Chromium at 1440×900 (114 pages: all 74 authored + 40/api/**), 8 search-timing trials, language-switcher/dark-mode/404 matrices, anrspress devsample, andcurl -sIagainst the live Pages site to separate preview-server artefacts from production; a 25-sentence accuracy sample againstpackages/agent-bundle/src; a full sweep of 33 authored pages/locale and 916 TypeDoc pages/locale; dependency currency and the last 80docs.ymlruns.rspress-best-practices,rspress-v2-upgrade,rspress-custom-theme,rspress-docs-generator,rspress-description-generator,rsbuild-best-practices(forbuilderConfig),rsdoctor-analysis. Every option cited below was checked against the installed@rspress/shared@2.0.21typings (<shared>=node_modules/@rspress/shared/dist/index.d.ts) and@rspress/core@2.0.21runtime (<core>=node_modules/@rspress/core/dist), not memory.cfg=website/rspress.config.ts.Findings
P1 — user-visible breakage or a wrong statement on a reference page
P1 ·
docs/diagnostics.md:821· rspress-docs-generator "a source-backed reference must render what the source says" · TheAB4834row has four cells (Code | Severity | Trigger | Recovery) under a three-column header (:785,Code | Severity | Trigger). GFM drops the extra cell, so the recovery hint (Add ".agent-bundle/routes.d.ts" to tsconfig.json include …) is silently absent from both generatedreference/diagnosticspages and from GitHub. It is the only ragged row in 29 tables / 240 rows. Change: fold the recovery text into the Trigger cell, as the neighbouringAB48xxrows do. Risk: none.P1 ·
docs/diagnostics.mdvswebsite/docs/en/index.mdx:230,guide/distribution/validation.mdx:29,195-201,guide/authoring/mcp.mdx:735,reference/cli.mdx:99,index.mdx:175· rspress-docs-generator "every user-facing item has a reference entry" · The home page promises "Every diagnostic has a stableABcode documented in the diagnostics reference", but four codes the guides explain with specific semantics have no row:AB4204,AB6005,AB8003,AB8004(covered only by a one-line family catch-all). Breadth:packages/agent-bundle/srcemits 400 distinctABnnnnliterals;docs/diagnostics.mdnames 213 explicitly (237 with ranges); 169 emitted codes have no explicit or ranged row and 93 of those only a family row — including all 69AB80xxdev-server codes. Every code does fall under some family row, and the 6 documented codes with nosrcliteral are legitimate (AB8206,AB8233–8235live in the workbench/example;AB4807,AB4816are marked retired). Change: add the four rows now; add a check (websitecheckscript orpnpm lint) that fails when anAB\d{4}cited inwebsite/docs/**/*.mdxis absent fromdocs/diagnostics.mdexplicitly or in-range — it fails on exactly these four today. Risk: low; an "everysrcliteral must be explicit" variant would fail on 169 codes and needs a ratchet.P1 ·
website/docs/en/reference/cli.mdx:288(zh:267) · AGENTS.md "reference prose must match the source" · "2| An argument-parsing failure from the command-line parser." Five option validators throw a plainTypeError, notInvalidArgumentError:port,trialCount,installHost,installMode,installScope(src/cli.ts:217-246). Commander rethrows anything that is notcommander.invalidArgument, andrunClimaps onlyCommanderErrorto 2 — everything else is oneAB5000diagnostic on stderr andreturn 1(src/cli.ts:1156-1161). Only--profile,--allowanddoctor --host(cli.ts:251,260,268) exit 2. Change: "2— a parser-level failure: unknown option, missing argument, or an invalid--profile,--allow, ordoctor --hostvalue. An invalidinstall <host>,--scope,--mode,--port, or--trialsvalue is reported as oneAB5000diagnostic and exits1." (Or make the five validators throwInvalidArgumentError; the mixed usage incli.ts:217-268looks accidental.) Risk: scripts branching on exit 2 for badinstallarguments currently see 1 plus a JSON diagnostic.P1 ·
website/docs/en/guide/authoring/hooks.mdx:132(zh:120) · AGENTS.md "reference prose must match the source" · "outcome|continue,deny, orstop."src/adapters/hook-handler.ts:58says "No event admitsoutcome: 'stop'",hookResultContract(:62-71) has nostopcapability, and the generated wrappers accept the literal in the enum check (hook-contract.ts:895) and then fail it for every event (:902-905, "stop only accepts continue or deny with a reason" / "cannot stop the parent flow"). Change: "outcome|continueordeny.'stop'is not a legal result on any event:HookHandler<E>excludes it and the wrapper rejects it at run time." Risk: a reader who returns{ outcome: 'stop' }gets atscerror and a hook-time failure.P1 ·
cfg:156-167,website/tsconfig.typedoc.json:6-7,.github/workflows/docs.yml:54-61· rspress-best-practices "validate before deployment" / AGENTS.md "state what the code does" · The twoslashcompilerOptions.pathsmaps onlyagent-bundle,/config,/test,/eval; the TypeDoc tsconfig maps ten more (@agent-bundle/runtime*,rsc-markdown-stream) and its comment claims twoslash "mirrors" them — it does not.packages/agent-bundle/src/test/{render,matchers,target-capabilities}.tsandeval/assertions.tsimport types from@agent-bundle/runtime, whosetypesis./dist/index.d.ts;packages/rsc-runtime/distdoes not exist in a fresh checkout anddocs.ymlrunspnpm install → pnpm lint → pnpm docs:site:buildwith nopnpm build. Twoslash only validates the sample's diagnostics, so the unresolved module becomesanyin the published hovers: in the built HTML, 18 of 784 twoslash popups render: any—renderRoute<…>(target: any, …),invokeMcpTool<…>(tool: any, …),RenderRouteOptionsBase.input?: any,ContractRouteFixture.input?: any— onguide/development/{testing,evaluations,workbench}in both locales (5 + 3 + 1 per locale). Change (minimal): add the ten aliases to the twoslashpaths(absolute, like the existing four:'@agent-bundle/runtime': [path.join(repoRoot, 'packages/rsc-runtime/src/index.ts')],/plugin,/flight/server,/lineage,/mount,/notices,/notices/inbox-route,/state,/state/sqlite,'rsc-markdown-stream'). Better: readtsconfig.typedoc.jsononce withts.readConfigFile(file, ts.sys.readFile)(it is JSONC) and feed both consumers from one map — the "never copy a helper" rule. Risk: twoslash type-checksrsc-runtime/srctoo — slower fences, and TS-7-only syntax there fails under the pinned TS 6.0.3 (the failure modecfg:122-126already documents for TypeDoc).P1 ·
cfg:79-81(search) +website/plugins/mirror-api-locale.ts· rspress-best-practices "validate before deployment" · The first search query races the index build and shows "No matching results". The per-locale index is 1.71 MB raw / 338 KB gz (952 docs, of which 916 TypeDoc pages contribute 1.21 MB); it is prefetched, but FlexSearch construction takes 1.7–2.3 s after download, and a query typed ≤1.2 s after opening the panel returned nothing or a partial list in 7 of 8 trials (快速开始0 → 3 on retype,参与贡献0 → 1,AB60051 → 3). Root cause is in the theme:LocalProvider.init(<core>/theme/components/Search/logic/providers/LocalProvider.js:81-85) callsaddAsyncfor every document without awaiting, soSearchPanel.initSearchflipsinitStatusto'inited'(SearchPanel.js:97-98) while indexing is still running, andNoSearchResultrenders as soon as the list is empty andinitStatus === 'inited'(:271) — no spinner, so the term reads as absent.tokenize: 'full'makes the cost proportional to content size, and this site's index is ~70 % API pages. Change: (a) file upstream (await Promise.allaround theaddAsynccalls, or gate the pending query on completion); (b) site-side, shrink the window: amodifySearchIndexDatahook (<shared>:815) inmirror-api-locale.tsthat blankscontentfor/api/pages (titles and headers stay indexed, soCapabilityStateErroris still found by title) — orsearch: falsefrontmatter on the mirrored API pages (FrontMatterMeta.search,<shared>:90; honoured atcreatePageData.js:33) to drop them entirely. Risk: (b) trades API body-text search for a ~3× smaller index; it does not remove the race, only its window.P1 ·
cfg:115(llmsUI: { placement: 'outline' }) +cfg:170-191(exclude: /api/) · rspress-best-practices "no broken assets" · "Copy Markdown" / "Open in ChatGPT" / "Open in Claude" are broken on all 1,832/api/**pages.llmsUIrenders the outline rows on every page (Outline/index.js:35-39) and the hiddenLlmsHintadvertisesroutePathToMdPath(page.routePath); bothpluginLlmsentries exclude/api/, andinclude/excludeapply tomdFilesas well asllms.txt(<llms>/index.js:142-148,236-244), so no.mdexists for those routes. Live:https://scriptedalchemy.github.io/agent-bundle/api/classes/api.TargetRegistry.md→ 404; the ChatGPT/Claude links embed that URL;getLlmsCopyContent.js:10copies whateverresponse.text()returns with no status check (the 404 page — on preview, the home-page HTML).LlmsUIhas no route gating (<shared>:292-315:injectLlmsHint,viewOptions,placementonly). Change (cheapest, custom-theme level 3): inwebsite/theme/index.tsxre-exportLlmsCopyRow,LlmsOpenRow,LlmsHintwrappers that returnnullwhenusePageData().page.routePathincludes/api/. Alternative that also closes #566 §4'sllms-full.txtgap: a thirdpluginLlmsentry{ llmsTxt: false, llmsFullTxt: false, mdFiles: { mdxToMd: false }, include: ({ page }) => page.routePath.includes('/api/') }(Optionsallowsfalsefor both,<llms>/index.d.ts:42-50) — at the cost of 1,832 extra SSG-MD renders per build. Risk: none for the wrapper; build time for the alternative.P2 — real quality / perf / maintainability gain with a concrete change
P2 ·
cfg:127-144(pluginTypeDoc),website/docs/{en,zh}/api/_meta.json· rspress-docs-generator "shape the API surface for readers" / rsdoctor-analysis ·@rspress/plugin-typedocbootstraps TypeDoc withrouter: 'kind'(<typedoc>/index.js:41), which emits 916 pages per locale (13 modules, 151 functions, 21 classes, 499 interfaces, 186 type aliases, 45 variables). Consequences measured on the built site: every one of the 1,832 API routes renders a 921-item sidebar (the sixdirgroups carry nocollapsed, and 2.0.21 defaultscollapsed = false,SidebarGroup.js:25), items sort by filenamemodule.Symbolwhile labels come from the h1, so the module grouping is invisible and two labels collide (Type Alias: AgentBundleConfig×2,Variable: default×2); each API HTML page averages 326 KB raw / 17.7 KB gz (authored: 54 KB) because the SSR'd<aside>embeds all 916 sidebar links — collapsed groups render their children withgridTemplateRows: '0fr', not conditionally — so 561 MB of the 602 MB of HTML (93 %) is sidebar,doc_buildis 623 MB (the published Pages site sits at ~62 % of GitHub's documented 1 GB limit; the Pages artifact is 36 MB compressed per deploy); the entry chunkstatic/js/index.*.js(2.23 MB raw / 368 KB gz, loaded on every page) is O(pages):virtual-page-data1,346 KB (219 KB gz —toc/frontmatter/titlefor all 1,906 routes) +virtual-routes488 KB (73 KB gz) +virtual-site-data287 KB (31 KB gz); 143 API pages have ≤2 body lines. The zh mirror (plugins/mirror-api-locale.ts) doubles all of it: 916 → 1,832 pages ≈ +300 MB HTML, ≈ +40 s single-thread SSG, +916 routes in every chunk above — while the mirrored body stays English. A/B (this audit, link checks disabled, otherwise identical):app.options.setValue('router', 'module')in the existingsetuphook → 102 routes instead of 1,906, wall 90.3 s vs 186.3 s (−52 %), peak RSS 4.99 GB vs 8.52 GB (−41 %),doc_build8.4 MB vs 623 MB,index.*.js474 KB vs 2.23 MB; the largest module page (api/api.html) is 731 KB. Costs found by the A/B: 43 authored → API links per locale target member URLs (/api/classes/api.TargetRegistry) and must become module anchors; TypeDoc's slugger emits-1-suffixed anchors for duplicate names that Rspress's slugger does not, socheckAnchorsfails on the generated pages until the titles are deduplicated; the trackedapi/_meta.json(hand-maintained — the plugin only writes it when absent) must be flattened to the 13 module files. Change: (1) today, at zero risk: add"collapsed": trueto the sixdirentries in both_meta.jsonfiles (fixes the 921-item sidebar UX only — the HTML is unchanged); (2) then pick the lever — themodulerouter as measured, an intermediate typedoc-plugin-markdown router, and/or dropping the zh mirror in favour of pointing zh API links at/api/— and rewrite the 43 links; (3) either way, therequiredToBeDocumentedwarnings (~65/151 functions, ~250/499 interfaces, ~120/186 type aliases with no JSDoc prose) mean the thin pages stay thin until the source is documented. Risk: (1) none; (2) URL churn for any external links into/api/**(the sitemap advertises 1,832 of them today).P2 ·
cfg(nossg) · rsbuild-best-practices "measure the resolved config before tuning" · SSG is the largest build phase — 77–87 s of the ~186 s — because the default path (<core>/node/ssg/renderPages.js:85-91) renders all 1,906 routes in one thread (SSGConcurrency32 is async, and rendering is CPU-bound).ssg.experimentalWorker: true(<shared>:1146-1157, defaultfalse) spawns a Tinypool ofmin(ceil(pages/100), cpus)threads (:46-50). Two quiet A/B runs: SSG 77.4 s → 8.7 s / 7.7 s, wall 186.3 s → 101.1 s / 110.8 s (−46 % / −41 %), peak RSS 8.52 → 9.82 / 10.35 GB; output identical (1,907 HTML files both, 7/7 sampled pages byte-identical after content-hash normalisation, HTML total 602,258,970 vs 602,258,375 B). Together with the TypeDoc surface above this is the whole "why does the docsite take three minutes" answer (#572 measured the persistent-cache lever at ~4 %; the warm run here was −3 %). Change:ssg: { experimentalWorker: true }, measured once on the hosted runner before committing to it —ubuntu-latesthas 4 vCPU → 4 threads → projected SSG ≈ 20–25 s and wall ≈ −55–65 s, not −85 s. Risk: experimental API; worker-written HTML bypasses Rsbuild's asset table (the file-size table no longer lists HTML — cosmetic); +1.3–1.8 GB RSS to verify on the runner.P2 ·
cfg:116-118(editLink) · rspress-custom-theme "override at the lowest level that works" ·useEditLinkbuildsdocRepoBaseUrl + page._relativePathunconditionally (<core>/theme/components/EditLink/useEditLink.js:10-11) and the link renders in every outline.en/api/**(916),zh/api/**(916) and the 8*/reference/{hosts,events,notices,diagnostics}.mdpages are gitignored (.gitignore:19-25), so 1,840 pages carry an "Edit this page / 编辑此页面" link to a GitHub 404 (verified:…/tree/main/website/docs/en/reference/hosts.md,…/en/api/index.md). 2.0.21 has no per-page switch:EditLinkis{ docRepoBaseUrl }only (<shared>:31-37) andFrontMatterMetahas noeditLinkkey. Change: inwebsite/theme/index.tsx, wrapEditLinkfrom@rspress/core/theme-original: returnnullwhenpage._relativePathmatches^(en|zh)/(api/|reference/(hosts|events|notices|diagnostics)\.md$), or — better for the reference pages — point them at their real sources (docs/diagnostics.md,packages/agent-bundle/src/adapters/capabilities/*.json) with a small map. Risk: the regex must track.gitignore.P2 ·
cfg:86(markdown),AGENTS.md"code samples wrap at roughly 90 columns" · rspress-best-practices · Measured at 1440×900 with the outline present (every authored page has one): the code-block container is 690 px, thepre686 px, 14 px monospace at 8.4 px/char → ~81 characters fit, so the 90-column rule is ~10 columns too wide. 66 of 201 visible code blocks on en authored pages (33 %, 18 of 33 pages) need a horizontal scrollbar (39 % across both locales, 132/336 blocks on 39 pages); every block iswhite-space: prewith the wrap toggle off by default. Worst:/reference/cli(158-char line, +682 px, 3 of 4 blocks),/guide/authoring/package-entries(139 chars, +530 px, 9/30),/guide/authoring/mcp(124 chars, +396 px, 17/38),/guide/distribution/installation(124, +396),/guide/authoring/hooks(114, +312, 5/9),/guide/development/testing(114, +312, 4/7),/reference/diagnostics(104, +228, 2/2). At the source level 26 fences exceed 90 columns and 11 exceed 100 (reference/cli.mdx:153= 158;guide/distribution/installation.mdx:165,guide/authoring/mcp.mdx:155= 124). Change: eithermarkdown: { defaultWrapCode: true }(<shared>:383-387; the copy button still copies raw text) or correct AGENTS.md to "≤ 80 columns" and re-flow the 26 fences above 90 first. Risk: wrapped shell one-liners are less scannable; none for correctness.P2 ·
cfg:110-113(languageParity),website/package.json:11(check) · AGENTS.md "en page and its zh translation in the same PR" ·checkLanguageParitycompares only the set of.md/.mdxrelative paths acrossconfig.locales(checkLanguageParity.js:16-19,36-38,62-67);_meta.json,_nav.json, frontmatter, headings and code are not compared, and it runs infinallyafter the whole build, so a miss costs a full build. Today nothing is stale (all 33 pairs share the same last commit and commit count; h2/h3, table, link,AB-code and:::counts match; fences are byte-identical after stripping comments — the only diffs are translated trailing#///comments in 16 fences across 10 pages), but no gate keeps it that way. Change (minimal):website/scripts/check-locale-drift.mjs, run bycheckbeforerspress build, failing when for anyen/X↔zh/Xthe fence count differs, fence bodies differ after stripping comment lines and trailing comments, theAB\d{4}sets differ, table-row counts differ, or h2/h3 counts differ by >2; plus a PR-level complement that fails whengit diff --name-only origin/main...HEADtouchesen/X.mdxwithoutzh/X.mdx. Risk: false positives on a deliberately zh-specific example (none exist today) or translated string literals inside code (all kept English today).P2 ·
AGENTS.md:116-117("pnpm docs:site:buildis the gate"),cfg:98-106,website/package.json:11· rspress-best-practices "verify broken links before deployment" · The gate does not check the link forms the site itself relies on.<core>/node/mdx/remarkPlugins/link.js:162-174visits only mdastlink/definitionnodes andimage.js:77,100-105sees markdown images plus<img>only as mdxJsx nodes (i.e. in.mdx); nothing inspects frontmatter,_nav.json, or raw HTML/JSX in.md. A probe docs root with a deadhero.actions[].link, deadfeatures[].link, missinghero.image,<a href="/dead">in.mdand.mdx,<Link href="/dead">, a dead_nav.jsonentry, an en page with no zh twin under a directory namedcapi/(the parityexcludeis a substring match onapi/), and a zh page with unrelated content built with exit 0 — while the same probe root confirmed that[x](/abs),[x](./rel),[x](/x.md), definitions,#anchors (self, relative, absolute, cross-locale), markdown images,<img>in.mdx,_meta.jsonentries and a missing zh file all fail the build as they should. The live docs carry 28 frontmatter links (docs/{en,zh}/index.mdx:16-65) and 10_nav.jsonlinks in exactly the unchecked forms. Second miss: with the persistent Rspack cache that a local build writes towebsite/node_modules/.cache/rspack/by default, a dead anchor added to page A that targets unchanged page B passes (B's loader never runs, so theRouteService.onRouteAnchorIdsconsumer,route/RouteService.js:139-148, never fires) — reproduced: warm exit 0, same content cold exit 1. CI is safe only because the cache is not persisted between runs; a localpnpm docs:site:buildre-run is not. Change: a ~50-line post-build script inwebsite/(run bycheckafterrspress build) that walksdoc_build/**/*.html, resolves everyhref/srcunder/agent-bundle/to a file and every#idto anid=in the target — covering frontmatter, nav, sidebar, JSX, generated pages and the cache case uniformly (+2–3 s); plusRSPRESS_PERSISTENT_CACHE=falsein thecheckscript itself (extends #572's docs.yml-only proposal; the warm gain is 3 %). The locale-drift check above is the parity half of the same gap. Risk: low.P2 ·
website/docs/en/reference/configuration.mdx:24,51-53(zh:24,50) · AGENTS.md "reference prose must match the source" · (a) "targets|string[]| Adapter-selected." — exactly one adapter is registered as default (.register(portableAdapter, { default: true }),src/adapters/registry.ts:780-786) andresolveTargetsreturnsregistry.defaultTargetNames()when config omitstargetsand no--targetwas passed (src/config/normalize.ts:1114-1122); no page says so. (b) "defineConfigaccepts anAgentBundleConfig… andvalidateSourceenforces the rules on this page" while the table listsroutesandevals—AgentBundleConfig(src/core/types.ts:309-328) declares neither; both ride the[key: string]: unknownindex signature (types.ts:328,src/routes/graph.ts:336),config/validate.tsnever readsevals, and theevalsrules at:145-147are enforced bynormalizeEvalConfig(src/eval/config.ts:82-125) only when an eval or the Workbench loads the config, asEVAL_CONFIG_INVALID, not an AB diagnostic. Sibling::38marketplace"Adapter-selected." →false(onlymarketplace === trueis carried,normalize.ts:1301). Change: (a) "['portable'](the only default adapter);--targetoverrides both." (b) add after:53: "evalsandroutesare not members ofAgentBundleConfig; they are read through its index signature, sotscdoes not check them andagent-bundle validatedoes not report them — theevalsrules below fire when an eval runs (EVAL_CONFIG_INVALID)." Risk: a user omittingtargetsexpects host artifacts and gets onlyartifact/portable; a misspelled top-level key is accepted by bothtscandvalidateSource.P2 ·
website/docs/en/guide/authoring/hooks.mdx:146(zh:133) · AGENTS.md "reference prose must match the source" · "agentStop… on Codex it cannot add context." The Cursor wrapper also rejects it (hook-contract.ts:906, "Cursor … has no additional-context channel") and the typed contract setsagentStop: { additionalContext: false }for every host (hook-handler.ts:66-67: "Claude carries additionalContext on SubagentStop; Codex and Cursor reject it"). Change: "…and only Claude Code carriesadditionalContextfrom it — Codex and Cursor reject it, and the typed contract excludes it everywhere." Risk: none.P3 — polish
cfg(nohead),website/docs/public/· rspress-best-practices "first-class options first" · SSG emitsog:title/og:descriptionand en/zhhreflangalternates on 114/114 pages but norel=canonical,og:url,og:image,og:type,twitter:card,theme-colororx-default; 25 pages have ameta description>160 chars (max 842 onapi/type-aliases/*, inherited from TypeDoc bodies);/api/is titled "agent-bundle - agent-bundle".UserConfig.head(<shared>:1079) accepts per-route functions evaluated at SSG, so canonical +og:urlare one entry (withBase/withSiteOrigin/normalizeHrefare exported from@rspress/core). Live/agent-bundle/robots.txt→ 404 andplugin-sitemapemits none;sitemap.xml(1,906<loc>) is therefore unadvertised — addwebsite/docs/public/robots.txtwithSitemap: https://scriptedalchemy.github.io/agent-bundle/sitemap.xml.cfg:83(cleanUrls: true) · deployment · leaf pages are emitted asx.html, so/agent-bundle/guide/start/quick-startand.htmlare 200 on Pages while the trailing-slash form…/quick-start/is 404 on Pages (200 underrspress preview, whose SPA fallback masks it). The site never emits that form (sitemap, hreflang and nav are extensionless), so this only bites shared links; the cheap fix is a redirect snippet in the 404 page (headscript: strip a trailing slash whenlocation.pathnameis not a directory route).rspress preview· acceptance workflow · the rsbuild static server servesindex.htmlfor every extensionless dotted route (/api/classes/api.TargetRegistry), producing React feat(routes): reference MCP App resourceUri from static config and resolve App templates route-relative (#388) #418 hydration errors and a content flash on 30/30 sampled API pages in preview only; the same pages hydrate clean as.htmland on Pages. Any browser acceptance against preview therefore shows fake errors and hides the trailing-slash 404 above — previewdoc_buildwith a plain static server (npx serve doc_build) or document it.website/plugins/mirror-api-locale.ts· i18n · 916 zh mirror pages carry<html lang="zh">and Chinese chrome (目录 / 上一页 / 下一页) around an English body with no notice that the API reference is English-only, and inherit the English site description. Prepend an:::infonote in the mirror plugin or setlang/descriptionfrontmatter there.cfg:137· rspress-best-practices ·pageTitleTemplates.index: '{projectName} {version}'renders# agent-bundle—{version}is empty becauseincludeVersionis not set; drop it orapp.options.setValue('includeVersion', true).website/docs/en/_nav.json:22-26: "Type API" lands on the bare generated index (13 bullets named by entry file —index,routes/public,test/browser— no prose, 0/916 API pages have adescription); the mapping toagent-bundle,agent-bundle/routes, … exists only inreference/api.mdx— point the nav there or add a "Module page" column toreference/api.mdx:12-29.website/plugins/generated-reference.ts:18-36· AGENTS.md "look for the helper before writing it" · re-declares looseCapabilityRow/HostCapabilityTableshapes whilesrc/adapters/capability-state.ts:47,94,99ownsEventRouteCapabilityTableEntry,CapabilityTableRow,NoticeDeliveryCapabilityTableEntry; the package does not export them, so a schema change renders stale columns silently. Export them from a public entry andimport typein the plugin (changeset +api/**regeneration).index.88eed14fa7.jsandindex.5960409c3a.js, same length 2,227,901 B, first difference at byte 1,837,065 = sidebar key order invirtual-site-data("/reference"first vs"/contributing"first) — every deploy invalidates the 368 KB gz entry for every returning visitor; upstream@rspress/coreauto-nav-sidebar, no repo change. (b)sitemap.xmllastmod= source-file mtime (<sitemap>/index.js:51): 66 URLs carry the checkout time and 1,836 the build time, so every deploy tells crawlers every page changed. (c) Rsbuild's defaultperformance.printFileSizeprints a 3,900-line size table per CI log, 4.6–8.9 s afterready built;builderConfig: { performance: { printFileSize: { detail: false } } }removes the noise (rsbuild-best-practices; the seconds are not fully attributed — treat as log hygiene).cfg:92-96,cfg:79-81· rspress-best-practices "remove unused plugins" · the docs contain 2[!code highlight]markers and zero[!code ++/--]/[!code focus], sotransformerNotationDiff()andtransformerNotationFocus()run on every fence for nothing;search.codeBlocks: truerestates the default (<shared>:356-359).themeConfig.footeris unset (repo is Apache-2.0).examples/audiobook-curator196,reference/limitations193,examples/mcp-app194,reference/runtime-environment179,reference/index176,guide/distribution/installation174,reference/security173,examples/hooks-and-scripts172,reference/cli170,guide/development/evaluations169,guide/development/testing168,reference/configuration164,guide/development/workbench163); all zh ≤111; 0 missing, 0 wrong language, 0 "This page…" openers.defineConfigfence is byte-identical in 4 en pages (guide/authoring/index.mdx:12,guide/start/project-structure.mdx:69,guide/start/quick-start.mdx:68,reference/configuration.mdx:10) and four paragraphs are duplicated verbatim across pages (reference/security.mdx:19≡guide/development/evaluations.mdx:196;reference/limitations.mdx:50-51≡guide/development/testing.mdx:350-351;guide/distribution/installation.mdx:7≡guide/start/quick-start.mdx:107;guide/distribution/validation.mdx:179≡guide/authoring/package-entries.mdx:223) — MDX fragment candidates.hooks.mdx:139· "sessionStart,afterTool, andagentStartcannot deny" is true forHookHandler<E>and the Claude/Codex wrapper, but the Cursor wrapper admits a denyingagentStartwith a reason (hook-contract.ts:899-901,hook-handler.ts:16) — "the typed contract rejects it; Cursor's runtime tolerates one".styles/index.cssto override) · appearance switch is adiv.rp-switch-appearancewith no role/name and not focusable; the GitHub social link has no accessible name; no skip link; home page has no<main>/<h1>; prev/next 12 px labels 1.85:1 light / 3.19:1 dark; active-nav brand#0d8f80on white 3.99:1; Shiki light string token#31a94d3.04:1 (body/table/code base ≥11:1 in both modes). Override--rp-c-text-3and the brand token; addaria-labelviathemeConfig.socialLinks..github/workflows/docs.yml:44·name: Docs site (Node 22.19)is a required context onmain(required_status_checksalso namesHost install proofs (Node 22.19),Examples check (Node 22.19),Release gates (Node 22.19)); bumping Node renames the check and blocks every PR until protection is edited — use a stablename: Docs site.package.json:24roottypecheckcovers root/workbench/create-agent-bundle/rsc-markdown-stream but notwebsite/tsconfig.json(config,plugins/**,theme/**, 1,307 lines), which onlydocs:site:buildcompiles — so AGENTS.md's "gate before pushing" never typechecks the website locally or inci.ymlVerify; appendpnpm --filter @agent-bundle/docs typecheck.website/package.json:24,28:@types/react-domandagent-bundle: workspace:*have no importer (twoslash resolvesagent-bundle*viapaths, TypeDoc viatsconfig.typedoc.json);react/react-domare also unreferenced by website code but@rspress/corealiases to<cwd>/node_modules/reactif present, else its own copy — remove both or neither.rstest.integration-tests.ts:8:workspaceTestFileGlob = 'packages/**/tests/**/*.test.ts'means anywebsite/**/*.test.tswould never be collected, andscripts/classify-docs-only.mjs:20skips Verify forwebsite/**-only PRs — zero tests exist for the ~900-linegenerated-reference.tstoday, so nothing is silently skipped yet; when the first test lands, addwebsite/**/*.test.tstorstest.unit.config.tsand run it indocs.yml. Actions are major-tag pinned, none by SHA (dependency-review-actionruns; Dependabot updates SHA pins). Onmain,.github/dependabot.yml:7-8: since ci: split Verify into a fast leg + 2 integration shards behind the required gate; pin the browser; retry setup; nightly pools (#576) #583pnpm/setup@v2lives only in the composite.github/actions/setup-workspace/action.yml, which thegithub-actionsecosystem withdirectory: /does not scan — usedirectories: ["/", "/.github/actions/*"].Measurements
(a) Builds (
/usr/bin/time -v,website/, Node 22.23, this machine; "quiet" = the sampler saw no other build during the run)doc_build+ caches removed)doc_build,node_modules/.cachekept), quietssg.experimentalWorker: true, quietrouter: 'module'(link checks off)doc_build8.4 MBtsc -p website/tsconfig.json×2.d.ts; no MDX#572's 196.6 s / 188.8 s cross-check to 190.2 / 184.5 s here. Phase breakdown from log timestamps (s):
markdown generated)build started)Rendering pages)Pages rendered in)Twoslash is worth its cost: 64 opted-in fences (32 per locale;
explicitTriggerdefaults totrue, so the other 120ts/tsx/jsfences are not type-checked), 26 output pages carry hovers (guide/authoring/mcp51), compile −37.6 s without it (~20 % of wall). Shiki and twoslash ship no client JS (@shikijs/langs8.6 MB source,parsedSize 0, tree-shaken); hover HTML is inlined in the route chunks (mcp.mdxroute 287 KB / 30 KB gz).Client JS per page (raw / gz):
index.*.js2,227,901 / 368,666 · search chunk (flexsearch) 226,677 / 74,372, a blocking<script>on every page ·lib-react189,213 / 59,843 ·lib-router35,321 / 12,714 ·styles79,775 / 14,677 CSS · route chunk 4.8–29.5 KB / 1–4.6 KB → ≈ 2.70 MB raw / 520 KB gz per page, identical on home, guide and API pages, plus the 1.71 MB / 338 KB gz search index fetched atrequestIdleCallbackon every page (SearchPanel.js:177-182), not on focus. Rsdoctor 1.6.3: no E1001/E1002 duplicates; top retained modules by gz are.rspress/virtual-page-data.js1,345.6 KB / 218.9 KB,virtual-routes.js487.7 / 73.2,react-dom174.1 / 54.9, thereference/diagnostics.mdroutes 257 / 53 each,guide/authoring/mcp.mdxroutes 268–287 / 30–34,virtual-site-data.js286.8 / 30.8 — i.e. the bundle is dominated by the repo's own route count, not by dependencies. Rspack cache after a build: 90 MB inwebsite/node_modules/.cache/rspack/.(b) Output —
doc_build623,206,738 B apparent (98 MB on a compressing filesystem), 3,909 files:.html1,907 = 602.3 MB (sidebar 561.2 MB; API pages avg 326 KB, largestzh/api/modules/api.html385.6 KB; authored avg 54 KB);.js1,916 (1,911async/route-*) = 14.8 MB; search index 2 × 1.71 MB; llms.txt4 = 1.15 MB (llms-full.txt565 KB per locale;llms.txt9.2 KB, 36 URLs, all.mdtargets 200); llmsUI.mdmirrors 74 = 1.12 MB;sitemap.xml397 KB (1,906<loc>, 953/zh/, 1,832/api/, 0 llms URLs);.css80 KB;404.html11 KB; 0.map, 3.LICENSE.txt. Pages artifact 36 MB compressed (last 5 deploys 35.9–37.1 MB). Module-router variant: 8.4 MB total,static/js2.9 MB,index.*.js474 KB, largest pageapi/api.html731 KB.(c) Runtime (preview, 1440×900, 114 pages) — HTTP 114/114 → 200; console errors/warnings,
pageerror,requestfailed, ≥400 responses: 0 on all 74 authored pages; hydration warnings 0 authored, 30/30 API at extensionless URL under preview only (0/30 via.html);<html lang>en 57 / zh 57 correct; meta description present 114/114, >160 chars 25; canonical 0 / hreflang 114;og:type,title,description114,og:url,image/twitter:*0; LCP avg 155 ms / p50 148 / max 444 ms, CLS 0.000 on 114/114; DCL avg 296 / max 653 ms; 14–15 requests per page; cold JS transfer 2.68 MB raw ≈ 520 KB gz + 338 KB gz search index; CDP heap 10.6 / 12.9 / 18.4 / 12.6 MB and DOM nodes 1,148 / 1,977 / 10,719 / 3,182 on home / guide / diagnostics / API;rspress dev(4 pages) 0 errors, first load 4.0–5.6 s. Tables: 278/278 wrapped indiv.rp-table-scroll-container; 4 wider than their container (hosts "Install surface" +33 px, events "Canonical event routes" +36 px, ×2 locales) → they scroll; page-levelscrollWidth > 1440: 0/114;<wbr>fromrehypeTableCellBreakspresent (24–48 per hosts table). Search matrix (steady state, 170–250 ms to first result):AB6005en 3 hits (Artifact validation, MCP servers and MCP Apps), zh 3;AB60113 (diagnostics reference);AB47700 (correct, does not exist);hooks61;defineConfig16;readPluginRoot(fence-only) 1 —codeBlocksworks;CapabilityStateError,DevRuntimeMcpAppRunBinding(API-only) found;脚手架6,宿主能力8 — zh indexed separately (952 docs each);--target0 (leading punctuation stripped;target119). Language switcher: same slug both directions for every page type (/reference/hosts↔/zh/reference/hosts,/api/classes/api.TargetRegistry↔ zh mirror — English body), 404 → locale home by design. Dark mode toggleshtml.dark, persists vialocalStorage['rspress-theme-appearance']. Diagnostics anchors are heading ids:#ab6005lands,#AB6005does not.(d) Live Pages matrix (
curl) —/guide/start/quick-start200 ·.html200 ·…/quick-start/404 ·/guide/start301 →/guide/start/200 ·/robots.txt404 ·/api/classes/api.TargetRegistry.md404 (.html200) ·/guide/start/quick-start.md200 ·/sitemap.xml200 ·/404.html200 with correct/agent-bundle/asset and home paths.(e) Accuracy sample — 25 reference sentences vs
packages/agent-bundle/src: 5 / 25 = 20 % drift (1 DRIFT, 4 PARTIAL)2= any argument-parsing failureAB5000, exit 1)--trialspositive integer ≤ 100--scopedefaultuser; Codex/Cursor user-scoped--modelocal/marketplace paths (Cursor only)0incl.--help/--version--template mcp-server --targets …targetsdefault "Adapter-selected"['portable'])runtime.nodefloor 22.127d/500/16777216validateSourceenforces the rules on this page" (incl.evals,routes)runtime.noderaises, never lowers, the floor (AB4602)plugin)marketplace: true@agent-bundle/runtime,@rstest/core,reactoptional peerspeerDependenciesMetaengines.node >=22.19.0AB3001; invalid YAML →AB3002targets:in Skill frontmatter →AB3006portableskipped silently; explicit →AB4204AB8003/AB8004on refusaloutcome∈continue,deny,stopagentStopcannot add context "on Codex"timeoutomitted → host defaultagent; Codex noagent/file.readhooks.matchersAGENT_BUNDLE_ENV_FILEdelimiter / later wins /noneSweeps behind the sample: AB codes — 101 distinct on en pages, 0 without an emitter, 0 outside every family row, 4 named only at family level (the P1); CLI flags — 45 parsed, documented-not-parsed 0, parsed-not-documented 0 (after excluding 23 foreign-tool flags that are real in their own parsers); config keys — 19 + 4 host extensions documented, 17 + 4 typed, documented-but-untyped
routes,evals, known-but-undocumented 0; unknown top-level keys are rejected nowhere (index signature) and no page claims otherwise.(f) Dependency / CI currency — see preamble for versions.
docs.ymllast 80 runs: 62 success, 8 cancelled (PR supersession), 8action_required(allchangeset-release/mainbot PRs — repo Actions-approval setting,ci.ymlshows the same), 0 failures; build-job median 166 s (n = 9, 130–193 s), deploy 16 s,timeout-minutes: 25 / 10ample; head-of-main guard anddeploy-pagessucceeded on every completed push run.pnpm audit: 2 moderate (qs@6.15.3), 0 of 28 paths through@rspress/typedoc/shiki/mdx; 0 open Dependabot alerts. Gate parity:rslintcoverswebsite/**in Verify,docs.ymland locally;tsc -p website/tsconfig.json, the TypeDoc + twoslash compile of the package sources under TS 6.0.3, and the Rspress dead-link/anchor/image/parity checks run only indocs.yml;website/**unit tests run nowhere (none exist).Already compliant
siteOriginis a documentedUserConfigkey (<shared>:1052-1055) consumed by the sitemap fallback,hreflang,llms.txtURLs and absolute-link normalisation;base: '/agent-bundle/'+ defaultoutDirmatchdocs.yml:66and the Pageshtml_url;lang: 'en'+route.localeRedirect: 'never'emit no redirect script, en at/, zh at/zh/;route.cleanUrls: true(extensionless hrefs, hreflang, sitemaplocand address bar all agree); zh UI strings resolve throughDEFAULT_I18N_TEXT(目录, 上一页/下一页, 搜索, 编辑此页面, 复制 Markdown, 切换代码换行) so nothemeConfig.localesblock is needed; local search builds one index per locale;markdown.shikilazy: truecovers every fence language in use (sh118,ts100,text20,tsx16,md2,json2) and the extralangsonly pre-warm grammars for twoslash hovers as the comment says;link.checkDeadLinks: { excludes },checkAnchors: true,image.checkDeadImages: truematchRemarkLinkOptions/RemarkImageOptions; plugin order (TypeDocconfig→setup→convert→writeOutput, thenmirrorApiLocale, thengeneratedReference) is sequential with no race in dev;theme/index.tsxuses the first-classafterFeaturesslot and re-exportstheme-originalwith no cycle;styles/index.cssuses only existing--rp-*tokens and BEM classes (no dead selectors);icon/logo/hero.imageare base-prefixed; no deprecated keys (themeConfig.localeRedirect,addRuntimeModules) and no v1 imports;website/tsconfig.jsonscope is correct;socialLinks.mode: 'link'valid.activeMatchcarries/zh/); 10_meta.jsonper locale, identical order, translated labels,dir-section-headerfor the four Guide groups; a missing sidebar target throws at build ("Missing page file"), so the coupling to generated pages is fail-loud; exactly one h1 per page (66/66), 0 heading skips, titles unique per locale; all frontmatter keys valid; 0 orphans (33/33 pages reachable from nav + sidebar per locale; 953 generated pages reachable); no page under 120 words; only the two deliberate cross-locale links (reference/api.mdx); all 13blob/main/tree/mainGitHub targets exist; generated reference plugin fails loudly on malformed input, translates zh headings/prose/info boxes, escapes| < > { }, keeps all 240 diagnostics rows; TypeDoc surface: 916 pages, zh mirror 916/916 byte-identical, 3,149 internal links with 0 broken, 43 authored → API links per locale all resolve,<>{}escaped,\_absent from titles, noDefined inlines to rot.basehandled in assets, favicon, 404.html, sitemap, llms, home CTAs, hreflang; language switcher lands on the same slug in both directions; dark mode persists; body/table/code base contrast ≥11:1 both modes; search finds fence-only identifiers, API symbols and CJK phrases; all images havealt;rspress devloads clean.link.js:35,74,image.js:33); relative, absolute,.md-suffixed, definition, self/relative/absolute/cross-locale anchor, markdown-image,.mdx<img>,_meta.jsonand missing-zh-file probes all fail the build as documented; generated TypeDoc and reference pages are written inconfighooks beforeRouteService.create, so anchors into them resolve (cold);checkDeadLinks.excludescorrectly whitelists thellms(-full).txtassets;languageParity.exclude: ['api']has no accidental substring victim in the real tree;explicitTrigger: truekeeps twoslash to opted-in fences; Rsdoctor E1001/E1002 clean.@rspress/*at latest; no legacyrspresspackage; Node engines consistent across root, website, Rspress, Rsbuild, typedoc, shiki; concurrency groups correct (docs-mainnon-cancelling,docs-<n>cancelling for PRs,pagesgroup on deploy); permissions minimal; Pagesbuild_type: workflow,https_enforced, branch policymainonly; Dependabot covers the pnpm workspace sub-manifests and workflows;.gitignorecoversdoc_build/,api/**/*.md, the four generated reference pages.Caveats
experimentalWorkeris arithmetic, not measured, and its peak RSS (10.3 GB here) must be re-measured on the hosted runner before adoption.router: 'module'A/B disabledcheckDeadLinks/checkAnchors(the variant fails them for the reasons listed) and flattenedapi/_meta.jsonin the scratch worktree only; its numbers are therefore a lower bound on the work, not a drop-in config.rspress previewon localhost (LCP/timing not network-realistic); production behaviour was cross-checked withcurlonly, not a browser run against Pages. The API sample was 40 of 1,832 pages; all authored pages were crawled. a11y checks are hand-rolled (axe-core is not installed).LocalProvider.initis from the shippeddist, not a debugger._meta.json/sidebar collapse behaviour and hook ordering were read from@rspress/core@2.0.21source and then confirmed on the rendered site (921-item sidebar observed).mainmoved during the audit; thedocs.yml/dependabot.ymlP3s were re-checked againstmain, everything else againstb75073b24.Lanes
cfgkey vs<shared>/<core>typings and runtime; produced the twoslash-anyP1, the llmsUI and EditLink findings, thehead/robots P3s, and the "already compliant" config list._meta.json/_nav.jsonper locale, 916 API pages/locale; produced bothdiagnostics.mdP1s, the sidebar/collapsedand locale-drift P2s, descriptions/snippets/thin-page P3s, orphan/heading/frontmatter/link sweeps.experimentalWorkerand no-twoslash A/Bs, per-page client JS, and 18 probe docs roots against the dead-link/anchor/image/parity checks; produced theexperimentalWorkerP2, the gate false-negative P2 (frontmatter/nav/JSX links, warm-cache anchors), the sidebar-is-93 %-of-HTML analysis, and the hash/lastmod/printFileSizeP3s.cli.mdx:288andhooks.mdx:132P1s, theconfiguration.mdxandhooks.mdx:146P2s.docs.yml— versions vs latest, peer constraints, action pins, last 80 runs, gate parity; no P1/P2, six P3s.router: 'module'A/B in a third worktree; verification of every P1 against the built HTML, the config, and live Pages; integration.Cross-references: #566 (combined survey, §4 docsite), #572 (Rsbuild audit;
performance.buildCacheP2 not restated), #576 (format).