Production repository conventions for AI coding agents: a working contract
DEV Community

Production repository conventions for AI coding agents: a working contract

Production repository conventions for AI coding agents are a working contract between the codebase and the person or agent changing it. They answer where a feature belongs, which boundaries must hold, how to run the relevant checks, and what evidence is required before merge. Without that contract, an agent can produce a valid-looking patch that uses the wrong data path, duplicates an existing component, skips a denial case, or changes a public interface without noticing. The fix is not a longer prompt. It is a repository whose conventions are visible, scoped, and backed by tests. Start with a short repository map A new contributor should find the important boundaries in a few minutes. Put a compact map in the root README and link to deeper documents rather than describing every directory in one file. project/ β”œβ”€β”€ README.md β”œβ”€β”€ CLAUDE.md β”œβ”€β”€ .cursor/ β”‚ └── rules/ β”œβ”€β”€ apps/ β”‚ β”œβ”€β”€ web/ β”‚ └── mobile/ β”œβ”€β”€ packages/ β”‚ β”œβ”€β”€ ui/ β”‚ β”œβ”€β”€ data/ β”‚ └── config/ β”œβ”€β”€ server/ β”‚ β”œβ”€β”€ routes/ β”‚ β”œβ”€β”€ services/ β”‚ └── jobs/ β”œβ”€β”€ tests/ β”‚ β”œβ”€β”€ contract/ β”‚ └── fixtures/ └── docs/ β”œβ”€β”€ architecture/ └── decisions/ The names are not the convention by themselves. Each directory needs an ownership rule. routes/ can own transport validation and response mapping. services/ can own product operations. data/ can own persistence access. jobs/ can own work that outlives a request. The rule is useful only if a reviewer can tell when a change violates it. Avoid folders created for appearance. If nobody can explain what belongs in services/ rather than routes/ , the extra layer adds vocabulary without reducing uncertainty. Document commands as executable facts The root README should list the install, development, test, type, lint, build, and migration commands that actually work in the repository. State prerequisites beside each command: required local services, fixtures, configuration names, or test modes. A convention is stronger when it can be copied: Focused server tests: UI checks: Type and build checks: Before merge: Run focused checks, then the required full checks for the changed boundary. Do not invent commands for a repository you have not inspected. The point is to record the project’s real commands, not to make documentation look complete. Keep secret values out of examples; document configuration keys by name only. An agent should also know which files are generated, which migrations require review, and whether lockfile changes are expected. These details prevent a harmless task from expanding into an unexplained build change. Give each boundary one owner A production convention should make the request path legible. Prefer a route that delegates rather than one that validates input, checks access, queries several tables, calls a provider, and formats a response in one function. // server/routes/projects.ts export async function createProject(request: Request) { const actor = await requireActor(request) const input = createProjectInput.parse(await request.json()) const workspace = await requireWorkspaceAccess(actor, input.workspaceId) const project = await projectService.create({ workspaceId: workspace.id, name: input.name, }) return Response.json(toProjectResponse(project), { status: 201 }) } The route owns transport. The access function owns authorization. The service owns the product operation. The response mapper owns the public shape. An agent asked to change project creation now has a clear starting point and a smaller surface to inspect. This is not a demand for one architecture. It is a naming and ownership rule: each boundary should have one reason to change and a test that describes its contract. For a fuller treatment of making the tree discoverable, read agent-readable repository structure. Make ownership part of the data API Never ask an agent to infer workspace ownership from a table name or a comment. Put the authorization constraint close to the data operation and choose names that carry the rule. export async function findWorkspaceProject( actorId: string, workspaceId: string, projectId: string, ) { return db.project.findFirst({ where: { id: projectId, workspaceId, members: { some: { actorId } }, }, }) } Then test the denial path: it("does not return a project outside the actor workspace", async () => { const project = await findWorkspaceProject( "actor-a", "workspace-a", "project-owned-by-b", ) expect(project).toBeNull() }) A function called findProject leaves the important constraint ambiguous. findWorkspaceProject tells the next contributor which relationship is part of the operation. Use names that carry the rule rather than relying on an agent to reconstruct it from a distant policy file. Put durable rules at the right scope Claude’s project memory documentation describes CLAUDE.md files as a place for project architecture, coding standards, workflows, and commands. Use a project instruction file for facts that should apply across sessions: - Where authentication and data access are enforced. - Which commands verify a production build. - Which directories contain generated files. - Which interfaces require a migration note. - Which files are protected or server-only. - What a completion report must include. Keep the file concise. A rule that applies only to one directory belongs near that directory or in a path-scoped rule. A procedure that changes frequently belongs in its own document and should be linked from the project instructions. Cursor’s documentation lists rules among the tools for customizing how its coding agent works with a codebase. Keep the intent stable across tools: explain project decisions and verification, not generic programming advice that every agent already knows. The rule should be discoverable before editing begins. If an agent has to search the entire repository to find the instruction that protects a billing table or public API, the repository has already made the unsafe path easier. Define protected paths and interfaces Some files need an explicit review rule. Mark migrations, authentication policy, billing state, public response types, generated artifacts, and deployment configuration according to the project’s actual risk. A protection note can be simple: Protected changes - Do not edit generated files by hand. - Any schema migration needs a data-impact note. - Any authorization change needs a positive and negative test. - Any public response change needs a consumer check. - Any deploy configuration change needs a smoke-test result. - Never commit secrets or copy them into logs and fixtures. The text guides the agent. The enforcement still belongs in tests, CI, branch policy, or server authorization. A sentence cannot block a request that the application should reject. For agent tools, keep user and workspace scope outside model-controlled arguments where possible. Validate the tool input, check resource ownership immediately before execution, and require trusted approval for sensitive side effects. The AI coding agent acceptance checklist provides a task-level version of these checks. Keep tests beside the behavior they protect An agent can find a test more easily when the test name and location match the behavior. Use local tests for local rules, integration tests for boundaries, and a small number of end-to-end checks for user-critical flows. server/ β”œβ”€β”€ billing/ β”‚ β”œβ”€β”€ entitlement-service.ts β”‚ └── entitlement-service.test.ts └── routes/ β”œβ”€β”€ projects.ts └── projects.test.ts Route tests should establish authentication, ownership, input validation, and response behavior. Service tests should establish product rules. Neither test should require the other module’s entire implementation to understand its own contract. Keep fixtures for repeated boundary cases: an unauthorized actor, an expired entitlement, malformed input, a duplicate event, and a provider timeout. Redacted fixtures give agents concrete examples without exposing customer data. Name tests after decisions rather than implementation details. A test called rejects_cross_workspace_project remains useful after a refactor. A test called uses_project_repository_method_2 does not explain what the product must preserve. Record decisions the code cannot explain Some choices are not obvious from the implementation. Why is provider-specific behavior isolated? Why is a migration staged? Why can mobile navigation vary from web navigation? Put the answer in a short decision record. Decision: Keep provider-specific options behind the adapter. Context: Providers expose different response and tool controls. Choice: Normalize product behavior and preserve provider details behind an explicit escape hatch. Trade-off: Callers must opt in to provider-specific features. Record the choice, reason, and consequence. Link to the affected code or test. When the decision changes, add a new record rather than silently rewriting the old one. This prevents an agent from treating an unexplained exception as the preferred pattern. It also gives reviewers a concrete question: is this change following an existing decision, or does the decision need to be revisited? Require an evidence-shaped completion note A convention is incomplete if it tells an agent how to edit but not how to finish. Require the completion report to include changed files, checks run, checks not run, assumptions, and remaining risk. Changed: - Added the workspace ownership check. - Added positive and cross-workspace denial tests. Verified: - Focused service tests: passed. - Route tests: passed. - Type check: passed. Not run: - Full end-to-end suite; its local provider service was unavailable. Risk: - Add a provider-timeout fixture before the next release. β€œNot run” is useful evidence. It tells the reviewer what remains open. Do not allow an unavailable check to become a claim that the code should work. For AI workflows, include the request ID, tool names, policy decisions, retry count, and safe error categories in the verification plan. The LLM observability guide explains why generated tex

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.