Reading code with AI, not generating it
I joined Spatie as a back-end developer at the end of January 2026 and was immediately dropped into a rather large client project. The codebase had been in development since June 2025, and was still only halfway done by the time I showed up, with over 150.000+ LoC.
That's a lot of code to catch up on.
Agentic coding has taken huge leaps over the last few months, so instead of pointing AI at the "generate more code" problem, I tried the opposite. I had it help me understand the code that was already there.
What I started with
Luckily, my colleagues had already set up a good AI workflow on the project:
- Laravel Boost
- A handful of Claude Skills, both project-specific and global
- Substantial
AGENTS.mdandCLAUDE.mdfiles - A
docs/folder of business-logic notes that had clearly been abandoned a few weeks in π
Two of those did the heaviest lifting.
Laravel Boost is the underrated one. It's an MCP server that gives Claude live access to schema, routes, config, artisan, and tinker. So when I asked "Is there already a service that handles X?" Claude could use Boost instead of guessing from filenames.
On the skills side, I leaned hardest on php-guidelines-from-spatie for our PHP and Laravel conventions, and laravel-inertia-react-structure for the frontend. Between them, Claude knows our code style and conventions without me repeating it on every prompt.
That's a solid base, but it doesn't replace actually understanding the system. So I asked Claude to give me a high-level outline of the entire codebase.
Where to start
The one thing I'd recommend trying first is the codebase-onboarding skill. Install it, point it at your project, and it gives you back two files:
- An onboarding guide covering tech stack, architecture, entry points, request lifecycle, conventions, and common tasks.
- A project-specific
CLAUDE.mdcovering stack, code style, testing, and build commands.
If you'd rather take a different approach, Repomix bundles your entire codebase into a single AI-friendly file. Turn on output.compress to strip non-structural code (Tree-sitter under the hood, drops token count by around 70%) and output.tokenCountTree so you can see where your context budget is going. Then give the file to your LLM, it won't need to crawl the same files again and again.
I'd start there before anything else, then use targeted prompts to dig into specific domains.
Prompts I kept reaching for
One thing before we go any further: Treat anything Claude tells you about the codebase as a hypothesis, not a fact. Cross-reference every non-trivial claim against the actual code.
The following prompts are a grasp of the questions I asked Claude throughout the first weeks of working on the project (or at least the ones I'm not scared of sharing on the internet).
For getting to know the codebase:
Give me a map of this codebase. What are the main domains/modules,
what does each own, and how do they relate to each other?
Describe the core data model. What are the most important entities,
their relationships, and any non-obvious design decisions?
Give me a dependency map of all domains/modules. Which depend on
which, any circular dependencies, and places where boundaries seem violated.
Tip: ask for a diagram, not a prose description. The fireworks-tech-graph skill takes a structural description like this one and exports a publication-ready SVG and PNG. A circular dependency in a graph is a lot more obvious than the same thing buried in a wall of text.

When I wanted to go deeper:
What does this codebase assume about its environment that isn't
obvious from the code itself? (config values, external services,
queue/cache drivers, DB assumptions, etc.)
For a specific domain:
Look at the [X] domain. What problems was it clearly designed to
solve and how does it solve them?
These feel obvious in hindsight, but having a small set of go-to prompts saved me from rewriting variations every time I opened a new branch.
Where Claude got it wrong
Claude was confidently wrong about plenty during my first weeks. Don't trust AI output blindly, especially on a codebase neither of you has any context of. π
The cleanest example: our local development setup runs through Docker, with a Makefile wrapping most of the CLI commands, like npm, composer or php artisan. Neither the CLAUDE.md nor AGENTS.md said so. So when Claude went to run something, it defaulted to the Laravel happy path, php artisan directly on the host, instead of the make targets we actually use. Wrong host, wrong PHP, lots of errors.
## Artisan Commands
.PHONY: artisan
artisan: ## Run artisan command (Usage: make artisan route:list)
docker-compose -f $(COMPOSE_FILE) exec $(APP_CONTAINER) php artisan $(if $(CMD),$(CMD),$(filter-out $@,$(MAKECMDGOALS)))
## Package Management Commands
.PHONY: composer
composer: ## Run composer command (Usage: make composer require package)
docker-compose -f $(COMPOSE_FILE) exec $(APP_CONTAINER) composer $(if $(CMD),$(CMD),$(filter-out $@,$(MAKECMDGOALS)))
## Frontend Commands
.PHONY: npm-dev
npm-dev: ## Start Vite development server
docker-compose -f $(COMPOSE_FILE) exec $(APP_CONTAINER) npm run dev
# ...
That one is on us, not on Claude. Anthropic's own guide on CLAUDE.md is clear. Build and run commands belong in there. We've since added that context, but it's a reminder that AI defaults to the framework's happy path. Anywhere your project diverges (Docker wrappers, custom Makefiles, devcontainers, ...), write it down before you onboard either a human or an AI.
It's important when using AI to analyze your code, that it's getting the correct information first and foremost. Break down the entire workflow into prompts or context that Claude (or any AI) can use later with ease and keep that information up to date.
Domains as a learning curve
The project follows a domain-driven layout. Each domain has clear ownership, so once Claude has mapped them out, you can read one at a time without getting lost.
A quick aside if you haven't heard of domain-drive development yet: In a "standard" Laravel project, files are grouped by type: all models in app/Models, all controllers in app/Http/Controllers, all jobs in app/Jobs, and so on. To follow the lifecycle of, say, an order, you end up jumping between four or five top-level folders.
Domain-driven development flips that around. Files are grouped by business concept. Each domain (Identity, Orders, Billing, whatever the project happens to need) lives in its own folder with its own models, actions, events, policies, etc. Reading one folder front-to-back gives you most of the story without leaving the directory.
// One specific domain folder per business concept
src/Domain/Invoices/
βββ Actions
βββ Commands
βββ QueryBuilders
βββ Collections
βββ DataTransferObjects
βββ Events
βββ Exceptions
βββ Listeners
βββ Models
βββ Rules
βββ States
src/Domain/Customers/
// β¦
Our Laravel Beyond CRUD course covers this in detail. The upside? A domain is small enough to read in one sitting for a human and an AI, which is exactly how Claude's analysis groups the code.
That said, you don't need a domain-driven codebase to get value out of this workflow. The prompts and tools above work just as well on a more conventional Laravel layout. The domain split just happened to make my own onboarding even smoother.
Feeding Claude the data model
For database design, our team uses Luna Modeler. The .dmm files it produces are really just JSON behind the scenes, which means you can drop them straight into Claude and ask it to break the architecture down for you. Combined with the codebase itself, this gives the model a lot of context to reason from.

What was useful wasn't just the schema, but the history around it. A long-running client project picks up a lot of "we changed our mind" moments. Tables that were modeled one way at the start get extended, split, or partially deprecated as the client redirects scope, or as later features introduce constraints that nobody saw coming.
Claude pointed out where the .dmm file and the migrations told different stories. Looking at those same areas in Luna's diagram made it obvious which parts of the schema had been worked on the most, the tables that had been pulled in different directions over time.
That turned out to be the context I needed when I started shipping features of my own. Knowing which tables had a stable shape and which ones had been reshaped three times meant I could make better calls about where to extend, where to refactor, and where to leave alone until the next round of changes hit.
Wrapping up
Joining a project that's already seven months deep used to mean a few weeks of poking around before really being productive and getting to know the project well. With the right tools, that timeframe dropped significantly. Most of my first days consisted of Claude turning a monolith into something I could actually navigate. Over the next few weeks I built my first real big features that fit neatly into the existing patterns.
Without the analysis up front and the structured review on the way out, the same work would have taken a lot longer and probably looked less consistent with the rest of the project.