AI Workflow
Backcap is designed to work well with AI coding assistants. The strict layer separation, typed interfaces, Result monad, and SKILL.md files give language models a clear, accurate map of your codebase before they write a single line of code.
Why Backcap Works Well with AI
Section titled “Why Backcap Works Well with AI”Predictable structure: Every domain has the same three layers with the same naming conventions. An AI that has read SKILL.md once knows where every type of file belongs.
Typed error handling: Result<T, E> makes failure modes explicit in the type signature. The AI can see exactly what errors a use case returns and generate correct error-handling code.
Port interfaces as contracts: Port interfaces define exactly what the AI needs to implement when writing a new adapter. There is no ambiguity about which methods are required.
Minimal coupling: Each layer only imports from the layer directly inside it. The AI cannot accidentally suggest an import that violates the architecture — the TypeScript compiler will catch it.
Loading Skills Before Making Changes
Section titled “Loading Skills Before Making Changes”Before making any architectural change to a Backcap project, load the relevant skill files into the AI’s context.
With Claude Code
Section titled “With Claude Code”Read the following skill files and use them as context for allsubsequent work in this session:
.claude/skills/backcap-core/SKILL.md.claude/skills/backcap-auth/SKILL.mdAfter loading the skills, the AI understands:
- Which layer each type of code belongs in
- Which imports are allowed at each layer
- What
Result<T, E>is and how to use it - Which errors each use case can return
- The naming convention for every file type
Example Prompt: Add a Use Case
Section titled “Example Prompt: Add a Use Case”I've loaded the backcap-core and backcap-auth skills.
Add an updateEmail use case to the auth domain. It should:- Accept userId and newEmail as inputs- Find the user by ID (return UserNotFound if not found)- Call user.updateEmail() (which returns Result<User, InvalidEmail>)- Save the updated user- Return Result<void, UserNotFound | InvalidEmail>
Follow the existing patterns in register-user.use-case.ts.The AI will generate a correctly structured use case that:
- Lives in
application/use-cases/update-email.use-case.ts - Receives
IUserRepositoryvia constructor injection - Returns
Result<void, Error>rather than throwing - Has the correct file naming suffix
Example Prompt: Write an Adapter
Section titled “Example Prompt: Write an Adapter”I've loaded the backcap-core and backcap-auth skills.
Write a Drizzle ORM adapter that implements IUserRepository.Place it at src/adapters/drizzle/auth/user-repository.adapter.ts.The AI will:
- Import
IUserRepositoryfrom the correct port path - Implement all three methods:
findByEmail,findById,save - Map between the domain
Userentity and the Drizzle schema type - Call
User.create().unwrap()safely (trusting data from the database)
Extending a Domain
Section titled “Extending a Domain”When extending an existing domain, always load the specific skill file for that domain. It contains the domain map — a table of every file, its export, and its responsibility.
Load backcap-auth SKILL.md.
Add a UserRole value object that:- Validates against allowed roles: "user" | "admin" | "moderator"- Follows the same pattern as email.vo.ts- Returns Result<UserRole, InvalidRole> from create()Reviewing AI-Generated Code
Section titled “Reviewing AI-Generated Code”Use the architecture rules as a review checklist:
Domain Layer Check
Section titled “Domain Layer Check”- No external imports (only
shared/result.tsfrom within the domain) - Private constructor with a static
create()factory - Factory returns
Result<T, E>, not the value directly - No async methods
Application Layer Check
Section titled “Application Layer Check”- Imports only from
domain/andapplication/within the domain - Use case receives port interfaces via constructor (not concrete classes)
-
execute()returnsResult<T, E> - No framework-specific code (no
req,res,prisma, etc.)
Contracts Layer Check
Section titled “Contracts Layer Check”- Only barrel is
contracts/index.ts - Factory function accepts
{ portName: IPortInterface }deps object - Returns the
IServiceinterface type, not the concrete class
Adapter Layer Check
Section titled “Adapter Layer Check”- Class uses the
implements IPortInterfacekeyword - Imports port from
application/ports/, entity fromdomain/ - Does not import from
contracts/
Common AI Mistakes to Watch For
Section titled “Common AI Mistakes to Watch For”Throwing instead of returning Result:
// Wrong — AI may generate thisasync execute(input: RegisterInput) { if (existing) throw new UserAlreadyExists(input.email);}
// Correctasync execute(input: RegisterInput): Promise<Result<{ userId: string }, Error>> { if (existing) return Result.fail(UserAlreadyExists.create(input.email));}Importing across layers:
// Wrong — importing Prisma into a use caseimport { prisma } from "../../lib/prisma";
// Correct — depend on the port interface onlyconstructor(private readonly userRepository: IUserRepository) {}Creating barrel exports in wrong places:
// Wrong — index.ts in domain/// domain/index.ts <-- do not create this
// Correct — only contracts/ has an index.ts// contracts/index.ts <-- this is the only barrelMutation instead of returning new instances:
// Wrong — mutating an entityupdateEmail(newEmail: string): void { this.email = newEmail; // entities are immutable}
// Correct — return a new Result<Entity, Error>updateEmail(newEmail: string): Result<User, InvalidEmail> { const emailResult = Email.create(newEmail); if (emailResult.isFail()) return Result.fail(emailResult.unwrapError()); return Result.ok(new User(this.id, emailResult.unwrap(), /* ... */));}The llms.txt Files
Section titled “The llms.txt Files”The Backcap documentation site publishes two files for AI consumption:
/llms.txt— a concise (300-500 word) summary of Backcap for use as a system prompt prefix/llms-full.txt— a comprehensive (2000-4000 word) reference covering all domains and architecture rules
These follow the llms.txt convention and can be fetched and embedded in AI tool configurations.
# Example: embed in Claude's system promptcurl https://faroke.github.io/backcap/llms.txt