Shipd
← All posts

AI that understands your codebase: how Shipd works with real projects

September 8, 2026 · 9 min read
codebase-awareai-codingdeveloper-workflow

AI has made it much easier to go from an idea to a working application. But that's only the beginning of software development.

Once an application has real users, real data, and real business logic, the requirement changes. You aren't asking "build me an application" any more. You're asking "change my application". And that is a different problem.

You already have a working project. It has its own folder structure, components, APIs, database models, authentication, and conventions. You don't want AI to create another version of your application. You want it to understand the one you already have and make the change you asked for.

Say you tell an AI: add email notifications when a user completes onboarding. Writing the email code is easy. The difficult part is figuring out where onboarding is completed, how the user is stored, whether the project already has an email service, and where the new logic should actually live.

"add onboarding emails" · the existing code the request travels through OnboardingForm frontend API call POST /api/onboarding OnboardingService User model Postgres EmailService email provider welcome email sent ✓
A feature that looks simple from the UI depends on several parts of the application that already exist.

If an AI only sees the prompt, it knows what you want. If it can work with the codebase, it can investigate how your application already does things. That distinction is the foundation of Shipd's codebase-aware workflow: analyse the repository, understand the architecture, find the relevant code, reuse what exists, make the change, and keep developing the same application.

The goal isn't to replace the codebase you've already built. It's to make AI useful inside it. Here's how that works, step by step.

It starts with your existing project

The first step isn't generating code. It's giving Shipd the project it needs to work on, because your repository contains information that would be extremely difficult to describe completely in a prompt:

src/
  components/
  pages/
  services/
  hooks/

server/
  routes/
  controllers/
  services/
  models/

database/
  migrations/

A developer already knows these directories aren't random. components holds reusable UI. services holds business logic. routes defines API endpoints. models represents database entities. The structure itself says something about how the application is built, and Shipd analyses that existing project instead of assuming it's starting from zero.

Finding where a change belongs

This is where a plain "AI code generator" workflow starts to break down. Suppose you ask: add a status filter to the customer dashboard. The visible change is just a dropdown, but the implementation runs from CustomerDashboard through useCustomers() and customerApi to GET /api/customers and the CustomerService behind it.

A useful change requires knowing that relationship, because if the existing API already supports filtering, the frontend may only need to pass a new parameter:

GET /api/customers already supports ?status · extend the call, don't rebuild the system
// CustomerDashboard.tsx - const customers = await customerApi.getCustomers(); + const customers = await customerApi.getCustomers({ + status: selectedStatus + });

The important part isn't the few lines of code. It's discovering that the existing application already has a place for that change, so a completely new filtering system would be unnecessary. That's what repository context makes possible.

Building context from the existing architecture

Once the structure is understood, the next question is: which parts of this codebase actually matter for this request?

Take the onboarding example. Shipd needs to connect "send an email when onboarding completes" with the existing implementation. Maybe the project already contains:

async function completeOnboarding(userId, data) {
  await saveOnboardingData(userId, data);
  await markOnboardingComplete(userId);
  return { success: true };
}

And elsewhere it already sends email:

await emailService.send({
  to: user.email,
  template: "welcome"
});

Now the feature doesn't require inventing a new email architecture. The existing system already shows how the application handles onboarding and email, and the requested change can be made around those pieces.

This is the important difference. Shipd isn't only asking "what code can I generate?". It's asking: what does this project already have, and where does this requested change belong?

Reusing components instead of rebuilding them

The same principle applies to the frontend. Your application already has a Button, a Form, an Input, a Select, a Toast, and they're used everywhere. When you ask for an edit-profile screen, a generic generator might create another set of buttons, inputs, and validation logic. An existing application doesn't need duplicates. It needs the new feature to follow the patterns the project already established.

already in your repo
Input Select Button Form Toast
new: edit profile screen
Each part of the new screen maps onto a component the project already has. Nothing gets rebuilt.

This is where codebase awareness earns its keep: existing architecture becomes an input to generation. The workflow is repository analysis, architecture understanding, component reuse, and then code generation, not generation first and integration later.

Understanding the request in the context of the code

Consider two prompts. "Create a user notification system" stands alone. "When a user completes onboarding, send them a welcome email using the notification system already in this project" assumes existing context: what "user" means in this application, where onboarding completes, what notification infrastructure exists, how email is configured, and which files need to change.

Without the repository, the AI has to guess at all of that. With the repository, the application itself provides most of the answers. That's why codebase-aware development is fundamentally different from generating code from a blank prompt.

Making the change without throwing away the project

This is the part that matters most on a real application. The workflow you don't want is: ask for a feature, receive unrelated code, integrate everything by hand. The workflow you want keeps the project as the source of truth: analyse the codebase, understand the change, find the relevant code, reuse the architecture, generate the required changes, and continue working on the same project.

You're not rebuilding the application every time you have a new idea. You're continuing development on it.

A real example, end to end

You have an existing SaaS application. A user signs up, sets up a profile, adds company information, and completes onboarding. You ask Shipd: when a user completes onboarding, send them a welcome email.

Without codebase context, an AI might produce sendWelcomeEmail(user.email) and leave the real questions open. Where does this go? Is user.email even available there? Does the project already have an email service? Does completion happen on the backend? Is sending queued? The generated code isn't necessarily wrong. It's missing the context required to know how this application works.

With the repository, the codebase reveals the chain that already exists, from OnboardingController through OnboardingService and UserService to EmailService. If the project already uses:

await emailService.sendTemplate(user.email, "welcome");

then the new behaviour is introduced through the existing services rather than beside them. The exact files always depend on the project, and that's the point: the code is generated from the context of the application instead of being invented independently of it.

The next feature builds on the last one

After onboarding emails, you might ask for onboarding status on the dashboard. Then an admin filter for users who haven't completed onboarding. Then a reminder email after seven days. These requests are connected. They all depend on the same underlying application, and each one has to work with what the previous one built.

your app +onboarding emails +status on dashboard +admin filter +7-day reminder
Not prompt, new code, repeat. The same codebase, updated by every feature, carries into the next one.

That continuity is the real value of working with an existing codebase, and it's why Shipd keeps the repo in sync rather than treating each request as a fresh start.

Why this matters for large codebases

The larger a project becomes, the harder it is to fit its context into a prompt. A mature application has a frontend, a backend, a database, authentication, payments, notifications, integrations, admin tools, shared components, tests, and configuration. A developer asking for a feature doesn't want to explain all of that every time. They want the AI to determine which parts are relevant.

For a payment request, the relevant slice runs from checkout through the payment service to the Stripe integration and the order tables. For a profile request, it's the profile page, the user API, and the user model. The entire repository doesn't need to weigh on every change. Finding and using the relevant context from the existing project is what makes codebase-aware development practical.

Code generation is only one part of the job

It's easy to measure an AI coding tool by how much code it generates. For existing applications, that's not the useful measurement. The better question is: how well can it work with the code that already exists?

Adding a feature usually involves much more than writing new lines. What exists? How does it work? Where does my change belong? What can I reuse? What actually needs to change? How does the new code fit the existing system? That's the problem Shipd's codebase-aware workflow is designed to address.

Connect your repo and try it →

Describe an app. Ship the real thing.

Shipd turns a prompt into a complete, multi-page app, and reads your codebase so the output matches it. Free to start, no credit card.

Start building free