We use cookies to ensure you get the best experience on our website.

8 min read
Spec → Test → Code: Closing the Loop with AI-Powered Pipelines
Shows how to generate unit tests and code from the same spec to reduce drift. Step-by-step tutorial using modern AI tooling with CI/CD integration.

What is Spec-Driven Development?

Link to this section

Spec-driven development is an approach where a detailed specification is the single source of truth for building software. This practice ensures that business requirements, tests, and application code all align, minimizing the drift that often occurs when these components are developed in isolation.

Traditionally, development might start with a rough idea, followed by code, with tests written afterward (if at all). Methodologies like Test-Driven Development (TDD) improved this by putting tests first. Spec-driven development is the next logical evolution, especially in the age of AI. By starting with a clear, machine-readable (or at least machine-understandable) specification, we can automate the generation of both the tests that validate the feature and the code that implements it.

How Does AI Close the Loop?

Link to this section

AI acts as a powerful accelerator in the spec-driven workflow, creating a tight, automated loop between the specification, the tests, and the code. This process ensures that what you specified is what you tested, and what you tested is what you built.

The AI-powered Spec → Test → Code pipeline works like this:

  1. Write a Clear Specification: The process starts with a human writing a detailed description of the feature. This could be a user story with acceptance criteria, a Gherkin file for Behavior-Driven Development (BDD), or even a well-commented function signature.
  2. AI Generates Tests: An AI model consumes the specification and generates the corresponding unit, integration, or API tests. Because the AI has the full context of the requirements, it can create test cases that cover expected outcomes, edge cases, and error handling.
  3. AI Generates Code: Using the exact same specification, the AI model generates the application code designed to fulfill the requirements and pass the previously generated tests.
  4. CI/CD Pipeline Executes: A continuous integration (CI) pipeline automatically runs the generated tests against the generated code. If all tests pass, the loop is closed, and you have high confidence that the code perfectly matches the specification.

This automated cycle dramatically reduces the manual effort of writing boilerplate code and tests, freeing up developers to focus on complex logic and architecture.

A Practical Tutorial with Modern Tooling

Link to this section

Let’s walk through a simplified example of generating an API endpoint using this workflow. We’ll use natural language for our spec and a conceptual AI tool integrated into a GitHub Actions pipeline.

Our Goal: Create a Node.js Express endpoint GET /api/users/{userId} that retrieves a user’s profile from a database.

Step 1: Write the Specification

Link to this section

First, we create a specification file, let’s call it get-user-profile.spec.md.

# Feature: Get User Profile

## Endpoint

`GET /api/users/{userId}`

## Description

Retrieves the profile for a single user by their ID. The profile should only include the `id`, `name`, and `email` fields.

## Success Criteria

-   **Scenario**: User is found
    -   Given a `userId` that exists in the database.
    -   When a GET request is made to `/api/users/{userId}`.
    -   Then the response status code should be 200.
    -   And the response body should be a JSON object containing the user's `id`, `name`, and `email`.

## Failure Criteria

-   **Scenario**: User is not found
    -   Given a `userId` that does not exist in the database.
    -   When a GET request is made to `/api/users/{userId}`.
    -   Then the response status code should be 404.
    -   And the response body should contain an error message: "User not found".

Step 2: Generate Tests with an AI Prompt

Link to this section

Next, we feed this specification to an AI model to generate our tests. We might use a CLI tool or a custom script that calls an AI API.

Prompt: “Based on get-user-profile.spec.md, generate Jest and Supertest code for an Express.js application.”

AI-Generated Output (user.test.js):

const request = require("supertest");
const express = require("express");
const {getUserById} = require("./userService");

// Mock the userService
jest.mock("./userService");

const app = express();
// Assume the router is set up similarly in the actual app
const userRouter = require("./userRouter");
app.use("/api/users", userRouter);

describe("GET /api/users/:userId", () => {
    it("should return 200 and the user profile if the user exists", async () => {
        const mockUser = {id: "123", name: "Alex Doe", email: "alex@example.com"};
        getUserById.mockResolvedValue(mockUser);

        const res = await request(app).get("/api/users/123");

        expect(res.statusCode).toEqual(200);
        expect(res.body).toEqual({
            id: "123",
            name: "Alex Doe",
            email: "alex@example.com"
        });
    });

    it("should return 404 if the user does not exist", async () => {
        getUserById.mockResolvedValue(null);

        const res = await request(app).get("/api/users/999");

        expect(res.statusCode).toEqual(404);
        expect(res.body.error).toEqual("User not found");
    });
});

Step 3: Generate Code from the Same Spec

Link to this section

Now, we use the same spec to generate the application code.

Prompt: “Based on get-user-profile.spec.md, generate the Express.js router and handler function. Assume a userService module handles database interaction.”

AI-Generated Output (userRouter.js):

const express = require("express");
const {getUserById} = require("./userService");

const router = express.Router();

router.get("/:userId", async (req, res) => {
    const {userId} = req.params;
    try {
        const user = await getUserById(userId);
        if (!user) {
            return res.status(404).json({error: "User not found"});
        }
        // Return only the specified fields
        const userProfile = {
            id: user.id,
            name: user.name,
            email: user.email
        };
        res.status(200).json(userProfile);
    } catch (error) {
        res.status(500).json({error: "Internal server error"});
    }
});

module.exports = router;

Step 4: Automate with a CI/CD Pipeline

Link to this section

Finally, we create a GitHub Actions workflow that runs the tests on every push.

.github/workflows/ci.yml:

name: CI Pipeline

on: [push]

jobs:
    test:
        runs-on: ubuntu-latest
        steps:
            - name: Checkout code
              uses: actions/checkout@v3
            - name: Setup Node.js
              uses: actions/setup-node@v3
              with:
                  node-version: "18"
            - name: Install dependencies
              run: npm install
            - name: Run tests
              run: npm test

With this setup, any change to the spec, code, or tests will trigger the pipeline, immediately notifying you if anything is out of sync.

Why is This Approach So Powerful?

Link to this section

Adopting a Spec → Test → Code pipeline offers significant advantages for development teams.

  • Reduces Drift: By generating tests and code from a single source of truth, you eliminate inconsistencies between what was requested, what was built, and how it was tested.
  • Increases Velocity: AI automates the creation of boilerplate code and standard test cases, allowing engineers to focus on higher-value problems.
  • Improves Quality: This process naturally leads to higher test coverage and ensures that every requirement is validated automatically.
  • Enhances Collaboration: A clear, formal specification serves as a contract between product managers, developers, and QA, ensuring everyone is aligned.

Common Challenges and How to Mitigate Them

Link to this section

While powerful, this AI-driven workflow comes with its own set of challenges.

ChallengeMitigation Strategy
Vague or Ambiguous SpecsThe “garbage in, garbage out” principle applies. Invest in training your team to write clear, precise, and unambiguous specifications. Use templates and linters for specs just as you would for code.
AI “Hallucinations”AI can sometimes generate incorrect or suboptimal code and tests. Always treat the AI’s output as a first draft. Human oversight and code review remain critical steps to ensure quality and security.
Over-reliance on AIDon’t let AI replace critical thinking. Developers should still understand the code and tests being generated, using the AI as a productivity tool, not a substitute for expertise.
Tooling and IntegrationSetting up the full pipeline can be complex. Start small by integrating AI assistance into one part of your workflow (e.g., test generation) and expand from there as your team becomes more comfortable.

Addressing these challenges proactively ensures that you can harness the benefits of AI without falling victim to its potential downsides.

How Kinde Helps with Secure and Scalable Pipelines

Link to this section

When building real-world applications, your specifications will inevitably involve core services like authentication, authorization, and user management. This is where a service like Kinde provides a massive advantage in an AI-driven pipeline.

Kinde’s well-documented and predictable APIs act as a perfect “spec” for an AI model. Instead of asking an AI to invent a secure authentication system, you can write a spec like:

Scenario: User updates their profile. Given the user is authenticated with a valid JWT. When a PATCH request is made to /api/me. Then the application should use the Kinde Management API to update the user’s given_name.”

The AI can then use Kinde’s SDKs and API documentation to generate the correct, secure code to handle that interaction. This approach combines the speed of AI generation with the security and reliability of a dedicated identity provider, letting you build complex, secure features faster than ever.

For more information, you can explore the Kinde Management API to see how its clear structure supports this development style.

Kinde doc references

Link to this section

Get started now

Boost security, drive conversion and save money — in just a few minutes.