From 7b2ca4a1b643ef0558c4b68c005147f4354a8426 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sat, 5 Sep 2026 09:43:19 +0000 Subject: [PATCH 1/2] docs(website): anchor rewrite and label check compare the member name exactly; module-page naming covers */index.ts entries (#610 review) --- website/docs/en/reference/api.mdx | 14 +++++---- website/docs/zh/reference/api.mdx | 8 +++-- website/plugins/mirror-api-locale.ts | 42 +++++++++++++++++---------- website/scripts/check-built-links.mjs | 12 ++++---- 4 files changed, 47 insertions(+), 29 deletions(-) diff --git a/website/docs/en/reference/api.mdx b/website/docs/en/reference/api.mdx index 1ae31c4b7..211bf35f7 100644 --- a/website/docs/en/reference/api.mdx +++ b/website/docs/en/reference/api.mdx @@ -35,11 +35,12 @@ disagree, the generated pages are correct. ## How the pages are organized -[/api/](/api/) lists the modules. Each module page is named after the entry point's source file -(`agent-bundle/routes` is compiled from `routes/public.ts`, so its page is `routes/public`; the -root `agent-bundle` export is `index`, served at `/api/index-1` because `/api/` is the module -index itself) and groups its members under `Classes`, `Interfaces`, `Type Aliases`, `Variables`, -and `Functions`. A name an entry point re-exports from another entry point is listed under +[/api/](/api/) lists the modules. Each module page is named after the entry point's source path +with `.ts` and a trailing `/index` dropped (`agent-bundle/routes` is compiled from +`routes/public.ts`, so its page is `routes/public`; `agent-bundle/app` from `app/index.ts` is +`app`; the root `agent-bundle` export is `index`, served at `/api/index-1` because `/api/` is the +module index itself) and groups its members under `Classes`, `Interfaces`, `Type Aliases`, +`Variables`, and `Functions`. A name an entry point re-exports from another entry point is listed under `References` with a link to the page that documents it — `runEvals` on `index` points to [`runEvals`](/api/api#runevals) on `api`. @@ -65,7 +66,8 @@ this Reference — are complete translations. ## Where to start - Compiling from a script: [`build`](/api/api#build-2) and [`validate`](/api/api#validate-1) in `agent-bundle/api`. -- Calling typed tools from an MCP App: `createAppClient` in `agent-bundle/app`, explained in +- Calling typed tools from an MCP App: [`createAppClient`](/api/app#createappclient) in + `agent-bundle/app`, explained in [The typed App client](../guide/authoring/mcp.mdx#the-typed-app-client). - Writing tests: [`renderRoute`](/api/test#renderroute), [`expectDocument`](/api/test#expectdocument), and the matrices in `agent-bundle/test`, explained in [Testing](../guide/development/testing.mdx). - Writing evals: [`defineEvalSuite`](/api/eval#defineevalsuite) and the assertions in `agent-bundle/eval`, explained in [Evaluations](../guide/development/evaluations.mdx). diff --git a/website/docs/zh/reference/api.mdx b/website/docs/zh/reference/api.mdx index 012cd0fe7..88cafeb41 100644 --- a/website/docs/zh/reference/api.mdx +++ b/website/docs/zh/reference/api.mdx @@ -33,8 +33,9 @@ description: '生成的 agent-bundle 类型 API:每个公开入口点一页、 ## 页面如何组织 -[/zh/api/](/zh/api/) 列出了所有模块。每个模块页以入口点的源文件命名(`agent-bundle/routes` 由 -`routes/public.ts` 编译而来,因此其页面是 `routes/public`;根导出 `agent-bundle` 是 `index`,位于 +[/zh/api/](/zh/api/) 列出了所有模块。每个模块页以入口点的源路径命名,去掉 `.ts` 与末尾的 +`/index`(`agent-bundle/routes` 由 `routes/public.ts` 编译而来,因此其页面是 `routes/public`; +`agent-bundle/app` 由 `app/index.ts` 编译而来,页面是 `app`;根导出 `agent-bundle` 是 `index`,位于 `/zh/api/index-1`,因为 `/zh/api/` 本身就是模块索引),并把成员归入 `Classes`、`Interfaces`、 `Type Aliases`、`Variables` 与 `Functions` 之下。一个入口点从另一个入口点重新导出的名称会列在 `References` 下,并链接到实际记录它的页面——`index` 页上的 `runEvals` 指向 @@ -59,7 +60,8 @@ description: '生成的 agent-bundle 类型 API:每个公开入口点一页、 ## 从哪里开始 - 从脚本中编译:`agent-bundle/api` 中的 [`build`](/zh/api/api#build-2) 与 [`validate`](/zh/api/api#validate-1)。 -- 在 MCP App 中调用带类型的工具:`agent-bundle/app` 中的 `createAppClient`,详见 +- 在 MCP App 中调用带类型的工具:`agent-bundle/app` 中的 + [`createAppClient`](/zh/api/app#createappclient),详见 [带类型的 App 客户端](../guide/authoring/mcp.mdx#带类型的-app-客户端)。 - 编写测试:`agent-bundle/test` 中的 [`renderRoute`](/zh/api/test#renderroute)、[`expectDocument`](/zh/api/test#expectdocument) 与那些矩阵,在[测试](../guide/development/testing.mdx)中介绍。 - 编写 eval:`agent-bundle/eval` 中的 [`defineEvalSuite`](/zh/api/eval#defineevalsuite) 与那些断言,在[评测](../guide/development/evaluations.mdx)中介绍。 diff --git a/website/plugins/mirror-api-locale.ts b/website/plugins/mirror-api-locale.ts index 3f99087cd..e372e99d0 100644 --- a/website/plugins/mirror-api-locale.ts +++ b/website/plugins/mirror-api-locale.ts @@ -45,35 +45,44 @@ function headingText(raw: string): string { interface PageAnchors { readonly ids: Set; - readonly memberIds: Set; + /** `### Member` heading id → member name (`build()` → `build`). */ + readonly members: Map; +} + +function memberName(text: string): string { + return text.replace(/\(\)$/, ''); } function collectPageAnchors(markdown: string): PageAnchors { const slugger = new GithubSlugger(); const ids = new Set(); - const memberIds = new Set(); - const outsideFences = markdown.replace(/^```[\s\S]*?^```[ \t]*$/gm, ''); + const members = new Map(); + const outsideFences = markdown.replace(/^`{3,}[\s\S]*?^`{3,}[ \t]*$/gm, ''); for (const match of outsideFences.matchAll(/^(#{1,6}) (.+)$/gm)) { - const id = slugger.slug(headingText(match[2])); + const text = headingText(match[2]); + const id = slugger.slug(text); ids.add(id); if (match[1].length === 3) { - memberIds.add(id); + members.set(id, memberName(text)); } } - return { ids, memberIds }; + return { ids, members }; } /** - * TypeDoc reserves some exported names while building its reflection URLs, so - * links to those `### Member` headings receive a spurious `-1`. Rspress runs + * TypeDoc's first anchor pass hands compound slugs to nested reflections + * (`EvalRunStoreError.code` → `evalrunstoreerrorcode`), so a later link to the + * `### EvalRunStoreErrorCode` heading receives a spurious `-1`. Rspress runs * github-slugger over the rendered headings instead, where the member's first * occurrence has the unsuffixed anchor, so those links are dead. Rewrite a * `#name-N` link to `#name` only when the fragment does not exist on the * target page (the link is actually dead — a legitimate `#protocol-v1` whose - * heading exists is never touched) and `name` is the id of a `###` member - * heading there. Anything else is left as TypeDoc wrote it for the build's - * anchor check to judge. + * heading exists is never touched), `name` is the id of a `###` member + * heading there, and the link's label is that member's exact name: ids are + * case-folded, so `FooCode` and `fooCode` share a base and only the label + * tells which one the link meant. Anything else is left as TypeDoc wrote it + * for the build's anchor check to judge. */ async function alignTypeDocMemberLinks(directory: string, files: string[]): Promise { const anchorsByPage = new Map(); @@ -87,13 +96,16 @@ async function alignTypeDocMemberLinks(directory: string, files: string[]): Prom const filePath = path.resolve(directory, relativePath); const markdown = await readFile(filePath, 'utf8'); const aligned = markdown.replace( - /(\]\()([^)\s#]*#)([^)\s#]+?)-\d+(\))/g, - (link, opening: string, target: string, base: string, closing: string) => { + /(\[([^\]]*)\]\()([^)\s#]*#)([^)\s#]+?)(-\d+)(\))/g, + (link, opening: string, label: string, target: string, base: string, suffix: string, closing: string) => { const linkedPath = target.slice(0, -1); const targetPath = linkedPath ? path.resolve(path.dirname(filePath), linkedPath) : filePath; const anchors = anchorsByPage.get(targetPath); - const fragment = link.slice(opening.length + target.length, -closing.length); - if (!anchors || anchors.ids.has(fragment) || !anchors.memberIds.has(base)) { + if ( + !anchors || + anchors.ids.has(base + suffix) || + anchors.members.get(base) !== memberName(headingText(label)) + ) { return link; } return `${opening}${target}${base}${closing}`; diff --git a/website/scripts/check-built-links.mjs b/website/scripts/check-built-links.mjs index e97697a34..b872058f4 100644 --- a/website/scripts/check-built-links.mjs +++ b/website/scripts/check-built-links.mjs @@ -147,12 +147,14 @@ const main = () => { if (target === null) continue; members += 1; const fragment = decode(member[1]); - const heading = new RegExp(`]*\\sid="${fragment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"[^>]*>([\\s\\S]*?)`).exec(fs.readFileSync(target, 'utf8')); + // Colliding ids differ only in case or level (`build()` member vs `build` + // property, `FooCode` vs `fooCode`), so the label must equal the member + // heading's text exactly, and the heading must be a module member (h3). + const heading = new RegExp(`]*\\sid="${fragment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"[^>]*>([\\s\\S]*?)`).exec(fs.readFileSync(target, 'utf8')); if (heading === null) continue; - const headingLabel = visibleText(heading[1]).replace(/^#/, '').trim(); - if (headingLabel.toLowerCase() !== label.toLowerCase()) { - if (!broken.has(href)) broken.set(href, `${href} — labelled "${label}" but #${fragment} is the heading "${headingLabel}" (e.g. in ${pagePath})`); - } + const headingLabel = visibleText(heading[2]).replace(/^#/, '').replace(/\?$/, '').trim(); + const reason = heading[1] !== '3' ? `an , not a module member` : headingLabel !== label ? `the heading "${headingLabel}"` : null; + if (reason !== null && !broken.has(href)) broken.set(href, `${href} — labelled "${label}" but #${fragment} is ${reason} (e.g. in ${pagePath})`); } } } From c3f9e913d7b0d9a99371ddff36f9a2c8bda61431 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sat, 5 Sep 2026 09:51:25 +0000 Subject: [PATCH 2/2] docs(website): drop a field comment that restated memberName (#624 deslop) --- website/plugins/mirror-api-locale.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/website/plugins/mirror-api-locale.ts b/website/plugins/mirror-api-locale.ts index e372e99d0..534b26fe2 100644 --- a/website/plugins/mirror-api-locale.ts +++ b/website/plugins/mirror-api-locale.ts @@ -45,7 +45,6 @@ function headingText(raw: string): string { interface PageAnchors { readonly ids: Set; - /** `### Member` heading id → member name (`build()` → `build`). */ readonly members: Map; }