From 48deebed1f664263a5b64eb91bc7308806ce4a32 Mon Sep 17 00:00:00 2001 From: Joseph Steele Date: Sat, 16 May 2026 17:53:16 +0100 Subject: [PATCH] feat: include auto-generated tscircuit docs in system prompt (#45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `createLocalCircuitPrompt` now fetches the auto-generated docs feed at https://docs.tscircuit.com/ai.txt and prepends them to the system prompt, ahead of the existing handwritten overview. The generated docs are treated as authoritative when they conflict with the handwritten section. Network resilience: - The two upstream fetches (`COMPONENT_TYPES.md` and `ai.txt`) run in parallel via `Promise.all`. - The generated-docs fetch is wrapped in `fetchOptionalFileContent`, which catches non-200 responses and network errors and returns an empty string. If `ai.txt` is unavailable, prompt creation still succeeds and the section is simply omitted — existing benchmarks keep running. Tests (`tests/create-local-circuit-prompt.test.ts`) cover all three paths via a mocked global `fetch`: 1. Success — generated docs are included and appear before "## tscircuit API overview". 2. 404 fallback — prompt is still built without the generated-docs section; handwritten overview and RULES section remain intact. 3. Throwing fetch — same fallback, errors swallowed. Closes #45 --- .../create-local-circuit-prompt.ts | 37 +++++++- tests/create-local-circuit-prompt.test.ts | 93 +++++++++++++++++++ 2 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 tests/create-local-circuit-prompt.test.ts diff --git a/lib/prompt-templates/create-local-circuit-prompt.ts b/lib/prompt-templates/create-local-circuit-prompt.ts index a93f11f..f051983 100644 --- a/lib/prompt-templates/create-local-circuit-prompt.ts +++ b/lib/prompt-templates/create-local-circuit-prompt.ts @@ -19,6 +19,16 @@ async function fetchFileContent(url: string): Promise { } } +async function fetchOptionalFileContent(url: string): Promise { + try { + return await fetchFileContent(url) + } catch { + return "" + } +} + +const AUTO_GENERATED_DOCS_URL = "https://docs.tscircuit.com/ai.txt" + export const createLocalCircuitPrompt = async () => { const footprintNamesByType = getFootprintNamesByType() const footprintSizes = getFootprintSizes() @@ -33,10 +43,17 @@ export const createLocalCircuitPrompt = async () => { "", ) - const propsDoc = - (await fetchFileContent( + // Fetch the required props doc and the optional auto-generated docs feed + // in parallel. The generated docs are optional — if fetch fails, fall back + // to the existing handwritten overview so prompt creation never breaks. + const [propsDocRaw, generatedDocs] = await Promise.all([ + fetchFileContent( "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md", - )) || "" + ), + fetchOptionalFileContent(AUTO_GENERATED_DOCS_URL), + ]) + + const propsDoc = propsDocRaw || "" const cleanedPropsDoc = propsDoc .split("\n") @@ -44,12 +61,24 @@ export const createLocalCircuitPrompt = async () => { .join("\n") .replace(/\n\n+/g, "\n\n") + const generatedDocsSection = generatedDocs.trim() + ? `## Auto-generated tscircuit docs + +The following is the latest auto-generated tscircuit documentation +(${AUTO_GENERATED_DOCS_URL}). Treat it as authoritative when it conflicts +with the handwritten overview below. + +${generatedDocs.trim()} + +` + : "" + return ` You are an expert in electronic circuit design and tscircuit, and your job is to create a circuit board in tscircuit with the user-provided description. YOU MUST ABIDE BY THE RULES IN THE RULES SECTION -## tscircuit API overview +${generatedDocsSection}## tscircuit API overview Here's an overview of the tscircuit API: diff --git a/tests/create-local-circuit-prompt.test.ts b/tests/create-local-circuit-prompt.test.ts new file mode 100644 index 0000000..2d82293 --- /dev/null +++ b/tests/create-local-circuit-prompt.test.ts @@ -0,0 +1,93 @@ +import { afterEach, beforeEach, expect, test } from "bun:test" +import { createLocalCircuitPrompt } from "lib/prompt-templates/create-local-circuit-prompt" + +// Issue #45: createLocalCircuitPrompt must include the auto-generated docs +// served at https://docs.tscircuit.com/ai.txt in the system prompt, and must +// remain resilient if that optional fetch fails. + +const PROPS_DOC_URL = + "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md" +const AI_DOC_URL = "https://docs.tscircuit.com/ai.txt" + +const STUB_PROPS_DOC = `# Components + +resistor: a passive resistor +capacitor: a passive capacitor +` + +const STUB_AI_DOC = + "auto-generated tscircuit docs for AI\nsentinel-marker-for-issue-45" + +type FetchFn = typeof fetch +let originalFetch: FetchFn + +beforeEach(() => { + originalFetch = globalThis.fetch +}) + +afterEach(() => { + globalThis.fetch = originalFetch +}) + +test("includes generated docs from docs.tscircuit.com/ai.txt when fetch succeeds", async () => { + const calls: string[] = [] + globalThis.fetch = (async (input: any) => { + const url = typeof input === "string" ? input : input.url + calls.push(url) + if (url === AI_DOC_URL) { + return new Response(STUB_AI_DOC, { status: 200 }) + } + if (url === PROPS_DOC_URL) { + return new Response(STUB_PROPS_DOC, { status: 200 }) + } + return new Response("", { status: 404 }) + }) as unknown as FetchFn + + const prompt = await createLocalCircuitPrompt() + + expect(calls).toContain(AI_DOC_URL) + expect(calls).toContain(PROPS_DOC_URL) + expect(prompt).toInclude("sentinel-marker-for-issue-45") + // The generated docs section should sit ahead of the handwritten overview. + expect(prompt.indexOf("sentinel-marker-for-issue-45")).toBeLessThan( + prompt.indexOf("## tscircuit API overview"), + ) +}) + +test("falls back gracefully when the generated-docs fetch fails", async () => { + globalThis.fetch = (async (input: any) => { + const url = typeof input === "string" ? input : input.url + if (url === AI_DOC_URL) { + return new Response("not found", { status: 404 }) + } + if (url === PROPS_DOC_URL) { + return new Response(STUB_PROPS_DOC, { status: 200 }) + } + return new Response("", { status: 404 }) + }) as unknown as FetchFn + + const prompt = await createLocalCircuitPrompt() + + expect(prompt).not.toInclude("sentinel-marker-for-issue-45") + // The rest of the prompt is still produced. + expect(prompt).toInclude("## tscircuit API overview") + expect(prompt).toInclude("### RULES") +}) + +test("falls back gracefully when the generated-docs fetch throws", async () => { + globalThis.fetch = (async (input: any) => { + const url = typeof input === "string" ? input : input.url + if (url === AI_DOC_URL) { + throw new Error("network unreachable") + } + if (url === PROPS_DOC_URL) { + return new Response(STUB_PROPS_DOC, { status: 200 }) + } + return new Response("", { status: 404 }) + }) as unknown as FetchFn + + const prompt = await createLocalCircuitPrompt() + + expect(prompt).not.toInclude("sentinel-marker-for-issue-45") + expect(prompt).toInclude("## tscircuit API overview") +})