How I Use AI to Develop Software Without Giving Up Engineering Judgment

How I use AI to develop software at RemarQable, from writing detailed specifications and following our engineering blueprint to independent code reviews, testing, and maintaining control over technical decisions.

AI has made generating code significantly faster, but I think there is an important distinction between generating code and actually developing software. Writing code is only one part of the process. Understanding the problem, making technical decisions, and ensuring the final implementation is maintainable are just as important.

At RemarQable, we use Claude extensively in our development workflow, but we don't let it make the technical decisions for us. I think through problems myself, determine how I want to implement a solution, and then use AI to help write the code. From there, we put the implementation through multiple stages of verification, testing, and manual review before considering it finished.

Our approach is built around one principle: AI should help us write code, not replace the engineering judgment required to develop good software.

Start With the Specification

Whenever we begin working on a new feature or fixing a bug, we start by thinking through the problem. Using our understanding of the codebase, we determine what needs to change and how we want to approach the implementation.

Once we've worked through the problem, we create a specification that outlines exactly how we want the feature implemented or the bug resolved. This includes the intended approach and the steps Claude should follow to make the changes.

We don't hand Claude a vague request and let it figure out the technical solution. We want to have already thought through the implementation and know what we would do if we were writing the code manually.

Once the specification is ready, we have Claude quickly review it alongside the affected areas of the codebase and our engineering blueprint. This gives it the context it needs before it begins implementing anything.

The important distinction is that Claude is implementing a plan we've already developed. We're not outsourcing the planning or the technical decision-making to the LLM.

Give AI Established Standards to Follow

Having a detailed specification is only part of the process. We also need to ensure the generated code follows the standards we have established at RemarQable.

We maintain an engineering blueprint for our development practices. It is a growing shared collection of knowledge and best practices contributed by current and former RemarQable developers, based on years of experience building production software.

The blueprint covers much more than formatting and naming conventions. It outlines how we structure code, implement authentication and multi-tenancy, handle errors, write tests, and approach other aspects of building production applications. We maintain separate blueprints for our Python and Go development.

You can explore them here:

Our CLAUDE.md instructions direct Claude to consult the appropriate blueprint before generating or modifying code. This gives it established patterns and standards to follow instead of leaving it to invent its own approach.

We want the generated code to follow the same practices we expect from developers on our team. Having these standards documented also gives us something concrete to verify against later.

Separate Implementation From Verification

After Claude finishes implementing the specification, we don't immediately start reviewing the code ourselves. First, we have it launch fresh-context subagents to independently verify the changes.

We believe the agent that implements the code should never be responsible for verifying its own work. Our reasoning is similar to why developers review each other's pull requests. Someone reviewing their own implementation may overlook issues because they are already familiar with the decisions and assumptions behind it.

We apply that same principle to AI agents. A fresh agent can examine the changes against established standards without relying on the original implementation agent's perspective. This doesn't guarantee an unbiased or correct review, but it gives us another independent perspective on the work.

One of the agents we always use performs our blueprint audit. This audit checks the generated code against our documented best practices, produces a score, and identifies violations that need to be fixed.

When the changes touch areas that could introduce security concerns, we also use security agents to perform more in-depth reviews.

These checks serve different purposes. The blueprint audit focuses on conformance with our engineering standards, while security reviews look for potential vulnerabilities. Neither replaces the other, and neither guarantees that every issue will be caught.

Test and Manually Review the Code

Once the initial verification and applicable security audits have passed, we run the full test suite to check for regressions and ensure our existing tests remain green.

However, passing tests doesn't automatically mean the code is good.

Tests can confirm expected behavior without necessarily identifying unnecessarily complicated implementations, duplicated functionality, or code that is difficult to maintain. This is why we still manually review the generated code.

During our reviews, there are a few things we pay particular attention to.

Keep it simple. We look for implementations that are more complicated than they need to be. AI can sometimes produce code that uses fewer lines but is actually harder to understand and maintain. We would rather have readable, straightforward code than a clever solution that makes future changes more difficult. The KISS principle is something we pay particular attention to.

Avoid unnecessary duplication. We also look for violations of DRY (Don't Repeat Yourself). Claude can sometimes recreate functionality that already exists elsewhere in the codebase rather than reusing an existing function or extracting shared logic. This creates unnecessary duplication and makes future maintenance harder.

Handle edge cases properly. We review how the implementation handles situations beyond the main expected use case. We want to understand what happens when something goes wrong, not just whether the feature works under ideal conditions.

Follow the blueprint. Even after an automated audit, we check that the implementation conforms to our standards and that nothing has been overlooked.

We also need to understand the code we're shipping. Manually reviewing AI-generated code isn't just about finding mistakes. It ensures we understand the implementation and can maintain it ourselves without having to go back to Claude to explain what it wrote.

After our manual review, we test the changes in the actual application to make sure everything works locally as expected.

Fix, Verify, and Repeat

This process isn't always linear. If an audit identifies a blueprint violation, we have Claude fix the specific finding. If we find something during manual review, we give Claude explicit instructions on what needs to change.

Just as with the original specification, we make the technical decisions about how we want the problem addressed rather than asking Claude to figure everything out on its own.

After changes are made, the verification process runs again. We don't want to rely on an earlier successful audit when the code has subsequently changed. The updated implementation needs to go through the appropriate checks again, followed by review and testing as needed.

This can create a circular process where implementation, verification, and corrections happen multiple times. We consider that a normal part of developing software, especially when using AI to generate code.

The goal isn't to get everything right on the first attempt. It's to have a process that identifies problems and ensures they are addressed before the changes are considered complete.

You Still Own the Code

We think one of the biggest mistakes developers can make when using AI to develop software is allowing the LLM to take over technical decision-making and critical thinking.

Code is cheap, but technical decisions and critical thinking skills are not.

We shouldn't stop using our own judgment simply because AI can produce an implementation quickly. We still need to understand the codebase, evaluate possible solutions, and make decisions about how our software should be built.

There are times when I consult Claude about something I genuinely don't know. I might use it to discuss an unfamiliar concept, explore possible approaches, or understand the trade-offs between different solutions. I see this as similar to consulting documentation or Stack Overflow, except that I can have a much more direct conversation.

But consulting AI doesn't mean handing it the final decision. We still need to understand the options and decide which approach makes sense for the application.

The same principle applies to the code itself. We shouldn't blindly commit and push AI-generated code simply because it works and the tests are passing. That approach can introduce technical debt, make a codebase harder to maintain, and leave developers without a proper understanding of the software they're responsible for.

Ultimately, it doesn't matter whether we wrote the code by hand or an LLM generated it. If we're shipping it, we need to understand it, review it, and take responsibility for maintaining it.

AI has become a valuable tool in our development workflow because it allows us to spend less time manually writing code. But the specifications, technical decisions, verification process, and final review are what allow us to use that tool without giving up control over the quality of the software we build.

Think your business fits?

We buy established B2B SaaS businesses and run them for the long term. Conversations are confidential, and we’ll tell you plainly whether it’s a fit.