From c4372f5a2e221fe4b131440f752c28c1ddf6ccba Mon Sep 17 00:00:00 2001 From: jottakka Date: Thu, 16 Jul 2026 11:19:21 -0300 Subject: [PATCH 1/5] fix: render toolkit documentation chunks consistently Share chunk ordering between rendered pages and copied Markdown so custom toolkit guidance remains complete and predictably placed. Co-authored-by: Cursor --- .../documentation-chunk-renderer.tsx | 54 +------ .../toolkit-docs/lib/documentation-chunks.ts | 53 +++++++ app/_lib/toolkit-markdown.ts | 141 ++++++++++++++---- tests/documentation-chunks.test.ts | 19 +++ tests/toolkit-markdown.test.ts | 130 +++++++++++++++- 5 files changed, 316 insertions(+), 81 deletions(-) create mode 100644 app/_components/toolkit-docs/lib/documentation-chunks.ts create mode 100644 tests/documentation-chunks.test.ts diff --git a/app/_components/toolkit-docs/components/documentation-chunk-renderer.tsx b/app/_components/toolkit-docs/components/documentation-chunk-renderer.tsx index 8ed9808ca..b404288ec 100644 --- a/app/_components/toolkit-docs/components/documentation-chunk-renderer.tsx +++ b/app/_components/toolkit-docs/components/documentation-chunk-renderer.tsx @@ -11,6 +11,7 @@ import { SignupLink } from "../../analytics"; import TabbedCodeBlock from "../../tabbed-code-block"; import TableOfContents from "../../table-of-contents"; import ToolFooter from "../../tool-footer"; +import { sortDocumentationChunks } from "../lib/documentation-chunks"; import type { DocumentationChunk, DocumentationChunkLocation, @@ -239,64 +240,13 @@ function ChunkContent({ chunk }: { chunk: DocumentationChunk }) { ); } -/** - * Default priority for chunks without an explicit priority - */ -const DEFAULT_CHUNK_PRIORITY = 100; - -/** - * Compare two chunks by header alphabetically. - * Returns a negative/positive number if both have headers, - * -1 if only a has a header, 1 if only b has a header, - * or null if neither has a header (fall through to next criterion). - */ -function compareByHeader( - a: DocumentationChunk, - b: DocumentationChunk -): number | null { - const headerA = (a.header ?? "").replace(HEADER_PREFIX_REGEX, "").trim(); - const headerB = (b.header ?? "").replace(HEADER_PREFIX_REGEX, "").trim(); - - if (headerA && headerB) { - return headerA.localeCompare(headerB); - } - if (headerA) { - return -1; - } - if (headerB) { - return 1; - } - return null; -} - /** * Sorts documentation chunks deterministically by: * 1. Priority (lower = earlier) * 2. Header alphabetically (for chunks with same priority) * 3. Content string (for chunks with same priority and no header) */ -export function sortChunksDeterministically( - chunks: readonly DocumentationChunk[] -): DocumentationChunk[] { - return [...chunks].sort((a, b) => { - const priorityDiff = - (a.priority ?? DEFAULT_CHUNK_PRIORITY) - - (b.priority ?? DEFAULT_CHUNK_PRIORITY); - - if (priorityDiff !== 0) { - return priorityDiff; - } - - const headerResult = compareByHeader(a, b); - if (headerResult !== null) { - return headerResult; - } - - // Finally, sort by content for stability. - // Some call sites pass lightweight chunk objects where content is optional. - return (a.content ?? "").localeCompare(b.content ?? ""); - }); -} +export const sortChunksDeterministically = sortDocumentationChunks; /** * DocumentationChunkRenderer diff --git a/app/_components/toolkit-docs/lib/documentation-chunks.ts b/app/_components/toolkit-docs/lib/documentation-chunks.ts new file mode 100644 index 000000000..19bed1a14 --- /dev/null +++ b/app/_components/toolkit-docs/lib/documentation-chunks.ts @@ -0,0 +1,53 @@ +import type { DocumentationChunk } from "../types"; + +const DEFAULT_CHUNK_PRIORITY = 100; +const HEADER_PREFIX_REGEX = /^#+\s*/; + +type SortableDocumentationChunk = Pick< + DocumentationChunk, + "header" | "priority" +> & + Partial>; + +function compareByHeader( + left: T, + right: T +): number | null { + const leftHeader = (left.header ?? "") + .replace(HEADER_PREFIX_REGEX, "") + .trim(); + const rightHeader = (right.header ?? "") + .replace(HEADER_PREFIX_REGEX, "") + .trim(); + + if (leftHeader && rightHeader) { + return leftHeader.localeCompare(rightHeader); + } + if (leftHeader) { + return -1; + } + if (rightHeader) { + return 1; + } + return null; +} + +export function sortDocumentationChunks( + chunks: readonly T[] +): T[] { + return [...chunks].sort((left, right) => { + const priorityDifference = + (left.priority ?? DEFAULT_CHUNK_PRIORITY) - + (right.priority ?? DEFAULT_CHUNK_PRIORITY); + if (priorityDifference !== 0) { + return priorityDifference; + } + + const headerDifference = compareByHeader(left, right); + if (headerDifference !== null) { + return headerDifference; + } + + return (left.content ?? "").localeCompare(right.content ?? ""); + }); +} diff --git a/app/_lib/toolkit-markdown.ts b/app/_lib/toolkit-markdown.ts index 1c4629c9a..8233f8cfb 100644 --- a/app/_lib/toolkit-markdown.ts +++ b/app/_lib/toolkit-markdown.ts @@ -1,4 +1,6 @@ +import { sortDocumentationChunks } from "@/app/_components/toolkit-docs/lib/documentation-chunks"; import type { + DocumentationChunk, ToolDefinition, ToolkitData, ToolParameter, @@ -13,6 +15,51 @@ import type { */ const JSON_INDENT = 2; +function documentationBlocks( + chunks: readonly DocumentationChunk[] | null | undefined, + location: DocumentationChunk["location"], + position: DocumentationChunk["position"] +): string[] { + const blocks: string[] = []; + const sorted = sortDocumentationChunks( + (chunks ?? []).filter( + (chunk) => chunk.location === location && chunk.position === position + ) + ); + + for (const chunk of sorted) { + const content = chunk.content.trim(); + if (!content) { + continue; + } + blocks.push( + chunk.type === "code" ? `\`\`\`text\n${content}\n\`\`\`` : content + ); + } + + return blocks; +} + +function sectionBlocks( + chunks: readonly DocumentationChunk[] | null | undefined, + location: DocumentationChunk["location"], + defaultBlock: string | null +): string[] { + const before = documentationBlocks(chunks, location, "before"); + const replacement = documentationBlocks(chunks, location, "replace"); + const after = documentationBlocks(chunks, location, "after"); + const hasReplacement = (chunks ?? []).some( + (chunk) => chunk.location === location && chunk.position === "replace" + ); + let content: string[] = []; + if (hasReplacement) { + content = replacement; + } else if (defaultBlock) { + content = [defaultBlock]; + } + return [...before, ...content, ...after]; +} + /** Collapse newlines and escape pipes so a value is safe inside a table cell. */ function cell(value: string | null | undefined): string { return (value ?? "") @@ -48,37 +95,52 @@ function exampleBlock(tool: ToolDefinition): string | null { function toolBlock(tool: ToolDefinition): string { const blocks: string[] = [`### ${tool.qualifiedName}`]; - if (tool.description) { - blocks.push(tool.description.trim()); - } - - const scopes = tool.auth?.scopes ?? []; - if (scopes.length > 0) { - blocks.push( - `**Required OAuth scopes:** ${scopes.map((s) => `\`${s}\``).join(", ")}` - ); - } + blocks.push( + ...sectionBlocks( + tool.documentationChunks, + "description", + tool.description?.trim() ?? null + ) + ); + + const parameterBlock = + tool.parameters && tool.parameters.length > 0 + ? `**Parameters**\n\n${[ + "| Name | Type | Required | Description |", + "| --- | --- | --- | --- |", + ...tool.parameters.map(parameterRow), + ].join("\n")}` + : "_No parameters._"; + blocks.push( + ...sectionBlocks(tool.documentationChunks, "parameters", parameterBlock) + ); const secrets = tool.secrets ?? []; - if (secrets.length > 0) { - blocks.push(`**Secrets:** ${secrets.map((s) => `\`${s}\``).join(", ")}`); - } - - if (tool.parameters && tool.parameters.length > 0) { - const rows = [ - "| Name | Type | Required | Description |", - "| --- | --- | --- | --- |", - ...tool.parameters.map(parameterRow), - ]; - blocks.push(`**Parameters**\n\n${rows.join("\n")}`); - } else { - blocks.push("_No parameters._"); - } + const secretsBlock = + secrets.length > 0 + ? `**Secrets:** ${secrets.map((secret) => `\`${secret}\``).join(", ")}` + : null; + blocks.push( + ...sectionBlocks(tool.documentationChunks, "secrets", secretsBlock) + ); - if (tool.output) { - const desc = tool.output.description ? ` — ${tool.output.description}` : ""; - blocks.push(`**Output:** \`${tool.output.type}\`${desc}`); - } + const scopes = tool.auth?.scopes ?? []; + const scopesBlock = + scopes.length > 0 + ? `**Required OAuth scopes:** ${scopes + .map((scope) => `\`${scope}\``) + .join(", ")}` + : null; + blocks.push(...sectionBlocks(tool.documentationChunks, "auth", scopesBlock)); + + const outputBlock = tool.output + ? `**Output:** \`${tool.output.type}\`${ + tool.output.description ? ` — ${tool.output.description}` : "" + }` + : null; + blocks.push( + ...sectionBlocks(tool.documentationChunks, "output", outputBlock) + ); const example = exampleBlock(tool); if (example) { @@ -90,19 +152,42 @@ function toolBlock(tool: ToolDefinition): string { export function toToolkitMarkdown(data: ToolkitData): string { const blocks: string[] = [`# ${data.label || data.id}`]; + const chunks = data.documentationChunks; if (data.description) { blocks.push(data.description.trim()); } + blocks.push(...documentationBlocks(chunks, "header", "before")); + blocks.push(...documentationBlocks(chunks, "description", "before")); + blocks.push(...documentationBlocks(chunks, "description", "after")); + blocks.push(...documentationBlocks(chunks, "header", "replace")); + blocks.push(...documentationBlocks(chunks, "header", "after")); if (data.summary) { blocks.push(data.summary.trim()); } + blocks.push(...documentationBlocks(chunks, "auth", "before")); + blocks.push(...documentationBlocks(chunks, "auth", "after")); + blocks.push( + ...documentationBlocks(chunks, "before_available_tools", "before") + ); + blocks.push( + ...documentationBlocks(chunks, "before_available_tools", "after") + ); + blocks.push(...documentationBlocks(chunks, "custom_section", "before")); + blocks.push(...documentationBlocks(chunks, "custom_section", "after")); const tools = data.tools ?? []; blocks.push(`## Tools (${tools.length})`); + blocks.push( + ...documentationBlocks(chunks, "after_available_tools", "before") + ); + blocks.push(...documentationBlocks(chunks, "after_available_tools", "after")); for (const tool of tools) { blocks.push(toolBlock(tool)); } + blocks.push(...documentationBlocks(chunks, "footer", "before")); + blocks.push(...documentationBlocks(chunks, "footer", "replace")); + blocks.push(...documentationBlocks(chunks, "footer", "after")); return `${blocks.join("\n\n")}\n`; } diff --git a/tests/documentation-chunks.test.ts b/tests/documentation-chunks.test.ts new file mode 100644 index 000000000..038e8a921 --- /dev/null +++ b/tests/documentation-chunks.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, test } from "vitest"; +import { sortDocumentationChunks } from "../app/_components/toolkit-docs/lib/documentation-chunks"; +import type { DocumentationChunk } from "../app/_components/toolkit-docs/types"; + +describe("sortDocumentationChunks", () => { + test("supports lightweight chunks with optional content", () => { + const chunks = [ + { header: undefined }, + { header: undefined }, + { header: "## B" }, + { header: "## A" }, + ] as unknown as DocumentationChunk[]; + + expect(() => sortDocumentationChunks(chunks)).not.toThrow(); + expect( + sortDocumentationChunks(chunks).map((chunk) => chunk.header ?? null) + ).toEqual(["## A", "## B", null, null]); + }); +}); diff --git a/tests/toolkit-markdown.test.ts b/tests/toolkit-markdown.test.ts index 456358329..df5fb15e9 100644 --- a/tests/toolkit-markdown.test.ts +++ b/tests/toolkit-markdown.test.ts @@ -22,6 +22,15 @@ const fixture: ToolkitData = { docsLink: "", }, auth: null, + documentationChunks: [ + { + type: "markdown", + location: "description", + position: "after", + content: "## Toolkit setup\n\nConfigure the toolkit first.", + priority: 10, + }, + ], customImports: [], subPages: [], tools: [ @@ -43,7 +52,15 @@ const fixture: ToolkitData = { secrets: ["API_KEY"], secretsInfo: [], output: { type: "json", description: "The result" }, - documentationChunks: [], + documentationChunks: [ + { + type: "warning", + location: "parameters", + position: "before", + content: "Use a verified recipient.", + priority: 20, + }, + ], codeExample: { toolName: "Demo.DoThing", parameters: { @@ -72,4 +89,115 @@ describe("toToolkitMarkdown", () => { expect(md).toContain("API_KEY"); expect(md).toContain("Example input"); }); + + test("includes toolkit and tool documentation chunks", () => { + expect(md).toContain("## Toolkit setup"); + expect(md).toContain("Configure the toolkit first."); + expect(md).toContain("Use a verified recipient."); + expect(md.indexOf("## Toolkit setup")).toBeLessThan(md.indexOf("## Tools")); + expect(md.indexOf("Use a verified recipient.")).toBeLessThan( + md.indexOf("**Parameters**") + ); + }); + + test("preserves repeated chunk content at different locations", () => { + const repeated = "Repeat this guidance."; + const output = toToolkitMarkdown({ + ...fixture, + documentationChunks: [ + { + type: "info", + location: "description", + position: "after", + content: repeated, + }, + { + type: "warning", + location: "auth", + position: "before", + content: repeated, + }, + ], + }); + + expect(output.split(repeated)).toHaveLength(3); + }); + + test("uses replacement chunks instead of the default section", () => { + const output = toToolkitMarkdown({ + ...fixture, + tools: [ + { + ...fixture.tools[0], + documentationChunks: [ + { + type: "markdown", + location: "parameters", + position: "replace", + content: "Custom parameter guidance.", + }, + ], + }, + ], + }); + + expect(output).toContain("Custom parameter guidance."); + expect(output).not.toContain("| Name | Type | Required | Description |"); + }); + + test("empty replacement chunks still suppress the default section", () => { + const output = toToolkitMarkdown({ + ...fixture, + tools: [ + { + ...fixture.tools[0], + documentationChunks: [ + { + type: "markdown", + location: "parameters", + position: "replace", + content: " ", + }, + ], + }, + ], + }); + + expect(output).not.toContain("| Name | Type | Required | Description |"); + }); + + test("sorts headed chunks before unheaded chunks like the page renderer", () => { + const output = toToolkitMarkdown({ + ...fixture, + documentationChunks: [ + { + type: "markdown", + location: "custom_section", + position: "before", + content: "No header", + }, + { + type: "markdown", + location: "custom_section", + position: "before", + content: "Section B", + header: "## B", + }, + { + type: "markdown", + location: "custom_section", + position: "before", + content: "Section A", + header: "## A", + }, + ], + }); + + expect(output.indexOf("Section A")).toBeLessThan( + output.indexOf("Section B") + ); + expect(output.indexOf("Section B")).toBeLessThan( + output.indexOf("No header") + ); + }); }); From 7abbf2ea849833fb5975ae1b341b885a96cf9aee Mon Sep 17 00:00:00 2001 From: jottakka Date: Thu, 16 Jul 2026 15:37:52 -0300 Subject: [PATCH 2/5] fix: honor toolkit description replacement chunks Make copied Markdown apply before, replace, and after description chunks in the same order as rendered toolkit pages. Co-authored-by: Cursor --- app/_lib/toolkit-markdown.ts | 8 +++----- tests/toolkit-markdown.test.ts | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/app/_lib/toolkit-markdown.ts b/app/_lib/toolkit-markdown.ts index 8233f8cfb..27141f62c 100644 --- a/app/_lib/toolkit-markdown.ts +++ b/app/_lib/toolkit-markdown.ts @@ -154,12 +154,10 @@ export function toToolkitMarkdown(data: ToolkitData): string { const blocks: string[] = [`# ${data.label || data.id}`]; const chunks = data.documentationChunks; - if (data.description) { - blocks.push(data.description.trim()); - } blocks.push(...documentationBlocks(chunks, "header", "before")); - blocks.push(...documentationBlocks(chunks, "description", "before")); - blocks.push(...documentationBlocks(chunks, "description", "after")); + blocks.push( + ...sectionBlocks(chunks, "description", data.description?.trim() ?? null) + ); blocks.push(...documentationBlocks(chunks, "header", "replace")); blocks.push(...documentationBlocks(chunks, "header", "after")); if (data.summary) { diff --git a/tests/toolkit-markdown.test.ts b/tests/toolkit-markdown.test.ts index df5fb15e9..a87039d6d 100644 --- a/tests/toolkit-markdown.test.ts +++ b/tests/toolkit-markdown.test.ts @@ -145,6 +145,43 @@ describe("toToolkitMarkdown", () => { expect(output).not.toContain("| Name | Type | Required | Description |"); }); + test("uses toolkit description chunks around the default description", () => { + const output = toToolkitMarkdown({ + ...fixture, + documentationChunks: [ + { + type: "markdown", + location: "description", + position: "before", + content: "Read this first.", + }, + { + type: "markdown", + location: "description", + position: "replace", + content: "Custom toolkit description.", + }, + { + type: "markdown", + location: "description", + position: "after", + content: "Read this last.", + }, + ], + }); + + expect(output).toContain("Read this first."); + expect(output).toContain("Custom toolkit description."); + expect(output).toContain("Read this last."); + expect(output).not.toContain("A demo toolkit."); + expect(output.indexOf("Read this first.")).toBeLessThan( + output.indexOf("Custom toolkit description.") + ); + expect(output.indexOf("Custom toolkit description.")).toBeLessThan( + output.indexOf("Read this last.") + ); + }); + test("empty replacement chunks still suppress the default section", () => { const output = toToolkitMarkdown({ ...fixture, From aa8672d215be4863d3d02a32c28efc498c1f50dc Mon Sep 17 00:00:00 2001 From: jottakka Date: Thu, 16 Jul 2026 15:40:33 -0300 Subject: [PATCH 3/5] fix: preserve all toolkit documentation replacements Apply replacement chunks consistently across exported toolkit sections so Markdown matches the rendered page ordering and content. Co-authored-by: Cursor --- app/_lib/toolkit-markdown.ts | 22 +++--------- tests/toolkit-markdown.test.ts | 61 ++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/app/_lib/toolkit-markdown.ts b/app/_lib/toolkit-markdown.ts index 27141f62c..7ca77d73d 100644 --- a/app/_lib/toolkit-markdown.ts +++ b/app/_lib/toolkit-markdown.ts @@ -154,32 +154,20 @@ export function toToolkitMarkdown(data: ToolkitData): string { const blocks: string[] = [`# ${data.label || data.id}`]; const chunks = data.documentationChunks; - blocks.push(...documentationBlocks(chunks, "header", "before")); + blocks.push(...sectionBlocks(chunks, "header", null)); blocks.push( ...sectionBlocks(chunks, "description", data.description?.trim() ?? null) ); - blocks.push(...documentationBlocks(chunks, "header", "replace")); - blocks.push(...documentationBlocks(chunks, "header", "after")); if (data.summary) { blocks.push(data.summary.trim()); } - blocks.push(...documentationBlocks(chunks, "auth", "before")); - blocks.push(...documentationBlocks(chunks, "auth", "after")); - blocks.push( - ...documentationBlocks(chunks, "before_available_tools", "before") - ); - blocks.push( - ...documentationBlocks(chunks, "before_available_tools", "after") - ); - blocks.push(...documentationBlocks(chunks, "custom_section", "before")); - blocks.push(...documentationBlocks(chunks, "custom_section", "after")); + blocks.push(...sectionBlocks(chunks, "auth", null)); + blocks.push(...sectionBlocks(chunks, "before_available_tools", null)); + blocks.push(...sectionBlocks(chunks, "custom_section", null)); const tools = data.tools ?? []; blocks.push(`## Tools (${tools.length})`); - blocks.push( - ...documentationBlocks(chunks, "after_available_tools", "before") - ); - blocks.push(...documentationBlocks(chunks, "after_available_tools", "after")); + blocks.push(...sectionBlocks(chunks, "after_available_tools", null)); for (const tool of tools) { blocks.push(toolBlock(tool)); } diff --git a/tests/toolkit-markdown.test.ts b/tests/toolkit-markdown.test.ts index a87039d6d..b6bcd31e3 100644 --- a/tests/toolkit-markdown.test.ts +++ b/tests/toolkit-markdown.test.ts @@ -182,6 +182,67 @@ describe("toToolkitMarkdown", () => { ); }); + test("renders replacement chunks at toolkit-level locations", () => { + const output = toToolkitMarkdown({ + ...fixture, + documentationChunks: [ + { + type: "markdown", + location: "header", + position: "before", + content: "Header first.", + }, + { + type: "markdown", + location: "header", + position: "replace", + content: "Header replacement.", + }, + { + type: "markdown", + location: "header", + position: "after", + content: "Header last.", + }, + { + type: "markdown", + location: "auth", + position: "replace", + content: "Custom auth guidance.", + }, + { + type: "markdown", + location: "before_available_tools", + position: "replace", + content: "Custom tool introduction.", + }, + { + type: "markdown", + location: "after_available_tools", + position: "replace", + content: "Custom tool footer.", + }, + { + type: "markdown", + location: "custom_section", + position: "replace", + content: "Custom section.", + }, + ], + }); + + expect(output).toContain("Custom auth guidance."); + expect(output).toContain("Custom tool introduction."); + expect(output).toContain("Custom tool footer."); + expect(output).toContain("Custom section."); + expect(output.indexOf("Header first.")).toBeLessThan( + output.indexOf("Header replacement.") + ); + expect(output.indexOf("Header replacement.")).toBeLessThan( + output.indexOf("Header last.") + ); + }); + test("empty replacement chunks still suppress the default section", () => { const output = toToolkitMarkdown({ ...fixture, From 06973112cb13db09f3e7a3ed96520e539059a156 Mon Sep 17 00:00:00 2001 From: jottakka Date: Thu, 16 Jul 2026 15:44:12 -0300 Subject: [PATCH 4/5] fix: skip malformed toolkit documentation chunks Keep Markdown export available when an invalid chunk lacks content instead of failing the whole copy operation. Co-authored-by: Cursor --- app/_lib/toolkit-markdown.ts | 3 ++- tests/toolkit-markdown.test.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/_lib/toolkit-markdown.ts b/app/_lib/toolkit-markdown.ts index 7ca77d73d..b0f8d377e 100644 --- a/app/_lib/toolkit-markdown.ts +++ b/app/_lib/toolkit-markdown.ts @@ -28,7 +28,8 @@ function documentationBlocks( ); for (const chunk of sorted) { - const content = chunk.content.trim(); + const content = + typeof chunk.content === "string" ? chunk.content.trim() : ""; if (!content) { continue; } diff --git a/tests/toolkit-markdown.test.ts b/tests/toolkit-markdown.test.ts index b6bcd31e3..5fa7e55c9 100644 --- a/tests/toolkit-markdown.test.ts +++ b/tests/toolkit-markdown.test.ts @@ -243,6 +243,22 @@ describe("toToolkitMarkdown", () => { ); }); + test("ignores malformed chunks without content", () => { + const output = toToolkitMarkdown({ + ...fixture, + documentationChunks: [ + { + type: "markdown", + location: "description", + position: "before", + content: undefined, + } as unknown as (typeof fixture.documentationChunks)[number], + ], + }); + + expect(output).toContain("A demo toolkit."); + }); + test("empty replacement chunks still suppress the default section", () => { const output = toToolkitMarkdown({ ...fixture, From d1663689d7fc6596b5b50c6e1a1f4575f12356a1 Mon Sep 17 00:00:00 2001 From: jottakka Date: Thu, 16 Jul 2026 15:46:10 -0300 Subject: [PATCH 5/5] fix: place toolkit footer chunks after tools Render after-available-tools documentation only after the complete tool list in copied Markdown. Co-authored-by: Cursor --- app/_lib/toolkit-markdown.ts | 2 +- tests/toolkit-markdown.test.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/_lib/toolkit-markdown.ts b/app/_lib/toolkit-markdown.ts index b0f8d377e..6d2d675ad 100644 --- a/app/_lib/toolkit-markdown.ts +++ b/app/_lib/toolkit-markdown.ts @@ -168,10 +168,10 @@ export function toToolkitMarkdown(data: ToolkitData): string { const tools = data.tools ?? []; blocks.push(`## Tools (${tools.length})`); - blocks.push(...sectionBlocks(chunks, "after_available_tools", null)); for (const tool of tools) { blocks.push(toolBlock(tool)); } + blocks.push(...sectionBlocks(chunks, "after_available_tools", null)); blocks.push(...documentationBlocks(chunks, "footer", "before")); blocks.push(...documentationBlocks(chunks, "footer", "replace")); blocks.push(...documentationBlocks(chunks, "footer", "after")); diff --git a/tests/toolkit-markdown.test.ts b/tests/toolkit-markdown.test.ts index 5fa7e55c9..d0d365dfd 100644 --- a/tests/toolkit-markdown.test.ts +++ b/tests/toolkit-markdown.test.ts @@ -241,6 +241,9 @@ describe("toToolkitMarkdown", () => { expect(output.indexOf("Header replacement.")).toBeLessThan( output.indexOf("Header last.") ); + expect(output.indexOf("Custom tool footer.")).toBeGreaterThan( + output.indexOf("### Demo.DoThing") + ); }); test("ignores malformed chunks without content", () => {