
Guide
Structure a repo so AI agents need less prompting
Move facts out of prose and into typed structures the build can check. Part one of three on local development environments built for coding agents.
Published August 2, 2026
Move facts out of prose and into typed structures the build can check. An agent that has to infer conventions from surrounding code will infer them wrong eventually; one that reads a typed registry and a constraint file gets the same answer every time. Structure beats instruction, because structure fails loudly.
Why prompting harder stops working
Part one of three. The instinct when an agent gets something wrong is to write a longer prompt, and it works — once. The next session starts with an empty context window and the same repo, so the correction has to be made again. Anything you find yourself explaining twice is not a prompting problem; it is a missing piece of repository structure.
There are three places a fact can live, in increasing order of reliability: in your head, in a markdown file the agent reads, or in typed code the build refuses to compile without. The work of making a repo agent-friendly is mostly the work of moving facts down that list.
Make the single source of truth an actual source file
The knowledge base on this site is the clearest example. Every article is a page component plus one entry in a typed registry, and that registry feeds the index, the sitemap, the RSS feed, the OpenGraph card, and the structured data. An agent adding an article does not have to discover those five consumers, because it cannot add an article without going through the one place they all read from.
export interface Post {
slug: string
title: string
/** Meta description, ~150 chars, answer-first */
description: string
type: PostType
status: PostStatus
datePublished: string
/** ISO date — bump only with real content edits */
dateModified: string
/** The query this post is written to win — the measurement ledger */
targetQuery: string
cluster: PostCluster
related: string[]
/** 1600x900 illustration. `alt` describes the photo, not the article. */
image: { src: string; alt: string }
}Every comment in that interface is a rule that used to live in a document nobody reread. targetQuery being required means no post ships without someone deciding what it is for. image being required — rather than optional with a fallback — means a missing illustration is a type error rather than three broken layouts discovered later. The types are the documentation, and unlike documentation they are enforced.
Fail loudly, and say what to do
Structure only helps if violating it is noisy. The lookup that resolves a slug to its registry entry throws rather than returning undefined, and the message names the fix:
export function getPost(slug: string): Post {
const post = posts.find((p) => p.slug === slug)
if (!post) {
throw new Error(
`No entry in data/posts.ts for slug "${slug}" — add one before creating the page.`
)
}
return post
}That error fired for real while this series was being written: a page existed with no registry entry, and the build stopped with a message that named the file, the slug, and the required action. Compare that to the alternative — a page that renders with an undefined title and a missing OpenGraph image, discovered weeks later by whoever notices the social preview is blank.
Write the failure text as a prompt, because that is what it is. If it says “update the budget in check-budget.mjs”, an agent will helpfully do exactly that and report success.
What belongs in CLAUDE.md instead
Types cannot hold reasoning, and reasoning is what an agent needs for the cases you did not anticipate. The constraint file carries the why — including the failures that produced each rule, because a rule with its history attached survives contact with a situation its author never imagined.
- **Never tween opacity on animated text** — axe and Lighthouse compute
contrast from the mid-tween opacity, so a crossfade caught mid-fade fails
color-contrast nondeterministically. Animate `transform` only (slide
behind a `clip-path`), keeping text at full opacity in every frame.
- Localhost Lighthouse under-reports LCP (~2-3s simulated even when real
paint is <0.5s; the throttling model chains all JS into text LCP when
paint lands after script eval). Judge LCP only on the deployed URL.Both of those exist because someone lost an afternoon. The second one in particular saves an agent from a specific, expensive wrong turn: seeing a bad local LCP number, believing it, and “fixing” a page that was already fast. A rule that only said “keep LCP low” would have caused that afternoon rather than prevented it.
Where this goes next
Structure gives an agent the right facts. It does not tell it whether the change it just made actually worked — for that it needs commands it can run and a browser it can look through, which is part two. Part three covers the reusable prompts that make a known stack repeatable. If you want the enforcement side of this specifically for performance, that is in the Core Web Vitals rules.
The same structure that helps an agent helps a new engineer, which is why it lands early in a custom build rather than being added once the codebase is already confusing.
Questions
Isn't a registry file just more code to maintain?
It replaces maintenance rather than adding it. Before, adding an entry meant remembering to touch the index, the sitemap, the feed, and the schema — four places, each of which could be forgotten silently. Now it is one entry and a page, and forgetting either fails the build loudly.
How much should go in CLAUDE.md versus a rules file?
CLAUDE.md carries the constraints and the reasoning behind them, because reasoning is what lets an agent handle a case you didn't anticipate. Machine-checkable facts — which routes exist, which budget applies, which fields a post needs — belong in typed code, where they can be enforced instead of remembered.
Does this work for an existing messy repo?
Yes, and incrementally. Start with the single most expensive recurring mistake, write the constraint that prevents it, and add the check that catches it. You do not need a coherent system on day one — you need the first rule to pay for itself, which it does the first time it fires.
Related notes
- A local dev environment an AI agent can verify in
- Reusable prompts for a known tech stack
- Migrate from .cursorrules to .cursor/rules
More on ai integration
- Extract action items from Notion with Claude
- SEO reports from the CLI, sized for AI agents
- Use a content registry instead of a CMS
- Local LLM or hosted API: how to choose
Related service: AI Integration
Want this kind of engineering on your project?
Tall Karol takes on fractional and project-based engagements for startups and agencies.
Book a working session