Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions lib/prompt-templates/create-local-circuit-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ async function fetchFileContent(url: string): Promise<string> {
}
}

async function fetchOptionalFileContent(url: string): Promise<string> {
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()
Expand All @@ -33,23 +43,42 @@ 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")
.filter((line) => !line.startsWith("#"))
.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:

Expand Down
93 changes: 93 additions & 0 deletions tests/create-local-circuit-prompt.test.ts
Original file line number Diff line number Diff line change
@@ -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 =
"<file_summary>auto-generated tscircuit docs for AI</file_summary>\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")
})
Loading