From Fantasyland to Filesystem: Grounding LLMs with Real Code Context Using `{{claudemd}}` and `{{fileTree}}`
Discover how we tackled LLM code hallucination by injecting real codebase context—directory structures and project documentation—directly into our prompts, using custom `{{claudemd}}` and `{{fileTree}}` variables.
In the exciting world of AI-powered development, Large Language Models (LLMs) are revolutionary. They can generate code, explain complex concepts, and even refactor entire modules. Yet, ask an LLM to work on a specific codebase it hasn't been explicitly trained on, and you'll often encounter a frustrating phenomenon: hallucination.
One of the most common and disruptive forms of hallucination we've faced is when an LLM invents file paths, directory structures, or even entire architectural patterns that simply don't exist in the actual repository. This leads to wasted time, incorrect suggestions, and a significant drop in trust in the AI's capabilities.
Our goal was clear: eliminate LLM hallucinations related to file paths and project architecture by grounding prompts in the real codebase context. Today, I'm excited to share how we achieved a significant leap forward by introducing two powerful new template variables into our workflow engine: {{claudemd}} and {{fileTree}}.
The Problem: LLMs Dreaming Up Codebases
Imagine asking an LLM to "add a new feature to src/components/dashboard/analytics.tsx" only for it to suggest creating app/features/analytics/DashboardAnalytics.jsx. Or perhaps it references a utils/helpers.ts file that was refactored out months ago. This "architectural amnesia" stems from the fact that while LLMs are incredibly knowledgeable, they lack real-time, precise context about your specific project's current state. They rely on their training data, which might be outdated or too generic.
This isn't just an annoyance; it's a productivity killer. Developers spend time correcting non-existent paths, debugging against phantom files, and ultimately losing faith in the AI's utility.
Our Solution: Injecting Reality with {{claudemd}} and {{fileTree}}
To combat this, we decided to explicitly provide the LLM with the context it desperately needed. We implemented a system to fetch and inject two critical pieces of information directly into our prompts:
{{claudemd}}: A placeholder for project-specific documentation, typically from aCLAUDE.md(orREADME.md) file. This allows teams to provide high-level context, design decisions, specific instructions, or even a mini-manifesto for how the AI should approach the codebase.{{fileTree}}: A markdown-formatted representation of the actual repository's directory structure. This is the ultimate truth-teller, showing the LLM exactly what files and folders exist, and where.
Let's dive into how we brought this to life.
Under the Hood: Building the Contextual Bridge
Our recent development session focused entirely on integrating these variables into our existing workflow engine. Here’s a breakdown of the key steps:
1. Fetching the Source of Truth: The Repository File Tree
To get an accurate, up-to-date file tree, we leveraged the GitHub Git Trees API (/git/trees/{branch}?recursive=1). This powerful endpoint allows us to fetch a full directory listing of a repository in a single, efficient API call.
We implemented a new fetchRepoTree() function in src/server/services/github-connector.ts. Crucially, this function also includes:
- Noise Filtering: We filter out common development artifacts like
node_modules,.git,.next,dist, and lock files, ensuring the LLM only sees relevant code structure. - Branch Fallback: To maximize compatibility,
fetchRepoTreegracefully falls back from themainbranch tomasterifmainisn't found, accommodating older or differently configured repositories. - Entry Cap: The file tree is capped at 500 entries to prevent excessively large prompts, which could hit token limits or degrade performance.
The output is then formatted as a clean markdown code block, ready for prompt injection.
2. Loading Project-Specific Documentation (CLAUDE.md)
Alongside the file tree, we introduced loadClaudeMdContent() in src/server/services/workflow-engine.ts. This function attempts to load content from a CLAUDE.md file at the root of each linked repository. If CLAUDE.md isn't found, it intelligently falls back to README.md, ensuring that some form of project context is almost always available.
3. Seamless Integration into the Workflow Engine
To make these new contextual variables available to our LLM prompts, we:
- Extended
ChainContext: AddedclaudeMdContentandfileTreeContentfields to ourChainContextobject, which carries all relevant data throughout a workflow execution. - Resolved Variables in
resolvePrompt(): OurresolvePrompt()function, responsible for injecting dynamic data into prompt templates, was updated to recognize and substitute{{claudemd}}and{{fileTree}}with their fetched content. - Parallel Loading for Performance: Both
loadClaudeMdContent()andloadFileTreeContent()now run in parallel viaPromise.allduring workflow startup. This ensures minimal latency overhead, as they load alongside existing document and consolidation steps.
4. Targeted Prompt Engineering for Maximum Impact
The true power of these variables comes from their strategic placement within our prompt templates. We updated five critical prompt templates in src/lib/constants.ts that were identified as primary sources of hallucination:
extensionAnalyze: The initial analysis step, now grounded in real structure from the start.extensionPrompt: Our main implementation prompts, where hallucinated paths were most common.secRecon: For security reconnaissance, ensuring the LLM understands the actual attack surface.secPrompts: Security fix prompts, guiding the LLM to existing files.deepPrompt: For deep build implementation, preventing the creation of non-existent modules.
Beyond just injecting the variables, we also updated the system prompts for all these templates with a crucial directive:
"MUST reference real file paths from the provided file tree — never invent or guess paths."
This explicit instruction reinforces the importance of using the provided context, guiding the LLM towards more accurate and actionable responses.
Lessons Learned & Smooth Sailing
This particular session was remarkably smooth. The feature addition was straightforward, benefiting from well-defined APIs and our existing modular architecture. This stands in contrast to some previous sessions where we grappled with complex authentication flows, tricky Server-Sent Events (SSE) implementations, or subtle Prisma ORM gotchas. A clean session like this is a testament to the value of good upfront design and robust tooling.
One small but important consideration, however, is that the GitHub Trees API requires repo-level read access on the Personal Access Token (PAT) stored via our Bring Your Own Key (BYOK) system. This is a crucial security and configuration point for anyone looking to implement a similar solution.
What's Next?
With the core implementation complete, our immediate next steps involve:
- End-to-End Testing: Running our Extension Builder workflow with linked repositories to verify that prompts consistently contain real file paths and
CLAUDE.mdcontent. - Token Budget Verification: Ensuring that the added file tree and
CLAUDE.mdcontent don't inadvertently blow the LLM's context window, especially for larger repositories. The 500-file cap is a good start, but real-world testing is key. - Expanded Usage: If testing reveals further hallucination in downstream steps, we'll consider adding
{{claudemd}}and{{fileTree}}to more templates (e.g.,extensionFeatures,extensionExtend,secRemediation). - Documentation: Updating our internal and external documentation to list
{{claudemd}}and{{fileTree}}as supported and highly recommended template variables.
Conclusion
By introducing {{claudemd}} and {{fileTree}}, we've taken a significant step towards creating a more reliable and less frustrating experience with AI-powered code generation. We're moving from a world where LLMs dream up their own architectural fantasylands to one where they are firmly grounded in the reality of your actual codebase. This dramatically improves the accuracy, utility, and trustworthiness of our AI assistant, making it a truly indispensable tool for developers.
Stay tuned for more updates as we continue to refine and expand our AI capabilities!