19 min read

> "Give me a specification, and I will give you software. Give me a vague idea, and I will give you a vague program."

Chapter 10: Specification-Driven Prompting

"Give me a specification, and I will give you software. Give me a vague idea, and I will give you a vague program." -- Adapted from classical engineering wisdom


Learning Objectives

By the end of this chapter, you will be able to:

  1. Analyze the differences between vague prompts and specification-driven prompts and explain why specifications produce more predictable AI-generated code (Bloom's: Analyze)
  2. Create formal and semi-formal specification documents that AI coding assistants can translate directly into working code (Bloom's: Create)
  3. Apply requirements documents, user stories, API specifications, database schemas, and test specifications as structured prompts (Bloom's: Apply)
  4. Evaluate when specification-driven prompting improves outcomes versus when lighter-weight approaches are more appropriate (Bloom's: Evaluate)
  5. Design specification templates tailored to different project types and development contexts (Bloom's: Create)
  6. Synthesize multiple specification types into comprehensive prompting strategies for complex projects (Bloom's: Synthesize)

Introduction

In Chapter 8, you learned the fundamentals of prompt engineering -- how to structure prompts, provide context, and guide AI coding assistants toward better outputs. In Chapter 9, you explored context management -- how to feed the right information to AI at the right time. This chapter takes both concepts to their logical conclusion: what happens when you give AI a complete, formal specification instead of a conversational prompt?

The answer, as you will discover, is transformative. When you provide AI with a well-structured specification, the generated code becomes dramatically more predictable, more correct, and more aligned with your actual requirements. The AI stops guessing about edge cases, stops making assumptions about data types, and stops inventing features you never asked for.

Specification-driven prompting is the bridge between casual vibe coding and professional software development. It takes the structured thinking of traditional software engineering -- requirements documents, API contracts, database schemas, test plans -- and repurposes these artifacts as precision instruments for guiding AI code generation.

This chapter will teach you to write specifications in formats that AI understands exceptionally well, show you how each specification type produces specific kinds of improved output, and give you templates you can adapt for your own projects.


10.1 From Vague Idea to Precise Specification

Every software project begins with an idea. The quality of the code AI generates depends heavily on how far you refine that idea before presenting it to your AI assistant. Let us trace the journey from a vague idea to a precise specification through a concrete example.

The Spectrum of Specification Precision

Consider a developer who wants to build a user authentication system. Here is how that idea might be expressed at different levels of precision:

Level 1: The Vague Idea

Build me a login system.

This prompt gives the AI almost no constraints. What kind of login? For a web app, a CLI tool, a mobile app? What authentication method? What happens on failure? The AI will make dozens of assumptions, and many of them will be wrong for your specific context.

Level 2: The Rough Description

Build a user authentication system for a Flask web application.
It should support email/password login with password hashing.

Better. The AI now knows the framework, the authentication method, and that passwords should be hashed. But critical details are still missing: session management, password requirements, rate limiting, error messages, database schema.

Level 3: The Detailed Description

Build a user authentication system for a Flask web application with
the following features:
- Email/password registration and login
- Passwords hashed with bcrypt, minimum 8 characters
- JWT tokens for session management, 24-hour expiry
- Rate limiting: max 5 failed login attempts per 15 minutes
- Email verification on registration
- Password reset via email
- SQLAlchemy with PostgreSQL for user storage

Significantly better. The AI has concrete technical decisions to work with. However, this is still a description, not a specification. It does not define the exact API endpoints, the database schema, the error response formats, or the expected behavior in every edge case.

Level 4: The Formal Specification

# Authentication System Specification

## Data Model
User:
  id: UUID, primary key, auto-generated
  email: string, unique, max 255 chars, validated format
  password_hash: string, bcrypt, not exposed in any API response
  is_verified: boolean, default false
  created_at: datetime, UTC, auto-set
  updated_at: datetime, UTC, auto-updated
  failed_login_attempts: integer, default 0
  locked_until: datetime, nullable

## API Endpoints
POST /api/auth/register:
  request_body:
    email: string, required, valid email format
    password: string, required, min 8 chars, 1 uppercase, 1 number
  success_response: 201
    body: { user_id, email, message: "Verification email sent" }
  error_responses:
    400: Invalid email format or weak password
    409: Email already registered

POST /api/auth/login:
  request_body:
    email: string, required
    password: string, required
  success_response: 200
    body: { access_token, token_type: "bearer", expires_in: 86400 }
  error_responses:
    401: Invalid credentials
    423: Account locked (too many failed attempts)
    403: Email not verified

POST /api/auth/logout:
  headers: Authorization: Bearer <token>
  success_response: 200
  error_responses:
    401: Invalid or expired token

## Business Rules
- Passwords: minimum 8 characters, at least 1 uppercase, 1 number
- JWT tokens expire after 24 hours
- After 5 failed login attempts within 15 minutes, lock account for 30 minutes
- Verification emails expire after 24 hours
- Password reset tokens expire after 1 hour

## Technical Requirements
- Framework: Flask 3.x
- ORM: SQLAlchemy 2.x
- Database: PostgreSQL 15+
- Password hashing: bcrypt via passlib
- JWT: PyJWT
- Rate limiting: flask-limiter

This is a specification. Every endpoint is defined. Every data type is explicit. Every error case is enumerated. Every business rule is stated. When you give this to an AI coding assistant, the resulting code will be remarkably close to what you actually want.

Key Insight: The time you invest in writing a specification is almost always less than the time you would spend correcting AI-generated code that was based on vague prompts. A 30-minute specification can save hours of back-and-forth iteration.

The Specification Mindset

Thinking in specifications requires a mental shift. Instead of asking "What do I want this to do?" you need to ask:

  1. What data does this system manage? Define every entity, every field, every type, every constraint.
  2. What operations does this system support? Define every endpoint, every function, every command.
  3. What are the inputs and outputs of each operation? Define request formats, response formats, parameter types.
  4. What can go wrong? Define every error case and how the system should respond.
  5. What are the rules? Define business logic, validation rules, and behavioral constraints.
  6. What are the technical constraints? Define frameworks, libraries, versions, and infrastructure.

Callout -- Common Pitfall: Do not confuse a specification with implementation instructions. A specification says what the system should do, not how to code it. "Hash passwords with bcrypt" is a specification. "Create a function called hash_password that imports bcrypt and calls bcrypt.hashpw()" is implementation instruction. Let the AI handle implementation details; you focus on requirements.

When Specifications Emerge Naturally

You do not always need to sit down and write a specification from scratch. Specifications often emerge naturally from:

  • Existing API documentation you are reimplementing or integrating with
  • Database schemas from an existing system you are extending
  • Test suites that define expected behavior
  • Wireframes and mockups that define UI requirements
  • Business process documents that define workflows
  • Regulatory requirements that define compliance constraints

Learning to recognize these existing artifacts as potential AI prompts is one of the most valuable skills in specification-driven development.


10.2 Requirements Documents as Prompts

Traditional software engineering has long used requirements documents to define what a system should do before any code is written. These documents, often dismissed as bureaucratic overhead, turn out to be exceptionally effective prompts for AI coding assistants.

Functional Requirements as Prompts

A functional requirement states what the system must do. When formatted clearly, functional requirements translate almost directly into AI prompts that generate focused, correct code.

Before: Conversational prompt

I need a library management system that lets librarians manage books
and patrons can check books out.

After: Requirements-driven prompt

Implement a library management system with the following functional requirements:

FR-1: The system shall allow librarians to add new books with title,
      author, ISBN (validated 13-digit format), publication year,
      and quantity available.
FR-2: The system shall allow librarians to update book information
      for any field except ISBN.
FR-3: The system shall allow patrons to search books by title
      (partial match, case-insensitive), author, or ISBN (exact match).
FR-4: The system shall allow patrons to check out up to 5 books
      simultaneously.
FR-5: The system shall prevent checkout if the patron has any
      overdue books.
FR-6: The system shall set a due date of 14 days from checkout date.
FR-7: The system shall calculate late fees at $0.25 per day per book.
FR-8: The system shall send email reminders 3 days before due date
      and on the due date.
FR-9: The system shall allow librarians to waive late fees with a
      reason field (required).
FR-10: The system shall maintain a complete audit log of all
       checkouts, returns, and fee transactions.

The difference in AI output quality between these two prompts is substantial. The requirements-driven prompt eliminates ambiguity about search behavior (partial vs. exact match), defines specific business rules (5-book limit, 14-day loan period, $0.25/day fees), and specifies operational requirements (email reminders, audit logs) that the AI would never infer from the vague prompt.

Non-Functional Requirements

Non-functional requirements define quality attributes -- how well the system should perform, not what it should do. These are frequently overlooked in casual prompting but critically important for production code.

Non-Functional Requirements:

NFR-1: Performance
  - Book search shall return results within 200ms for databases
    up to 100,000 books
  - Checkout/return operations shall complete within 500ms

NFR-2: Security
  - All passwords shall be hashed using bcrypt with cost factor 12
  - API endpoints shall require JWT authentication
  - Patron data shall be encrypted at rest

NFR-3: Reliability
  - The system shall handle concurrent checkouts without race
    conditions (use database-level locking)
  - All database operations shall use transactions

NFR-4: Scalability
  - The system shall support up to 10,000 concurrent users
  - Database queries shall use appropriate indexes

NFR-5: Maintainability
  - Code shall follow PEP 8 style guidelines
  - All public functions shall have docstrings
  - Test coverage shall be at least 80%

Callout -- Pro Tip: Including non-functional requirements in your prompts often causes AI to generate architecturally sound code from the start. Without NFR-1 above, the AI might generate book searches that scan the entire table. With it, the AI is more likely to include database indexing and efficient query patterns.

The MoSCoW Method for Priority

When your requirements are extensive, use the MoSCoW method (Must have, Should have, Could have, Won't have) to help AI prioritize implementation:

Implement the following requirements using MoSCoW prioritization:

MUST HAVE (implement these first, these are critical):
- User registration and authentication
- Book CRUD operations
- Checkout and return functionality

SHOULD HAVE (implement after must-haves are working):
- Search with filters
- Late fee calculation
- Email notifications

COULD HAVE (implement if time allows):
- Book recommendation engine
- Reading history analytics
- Mobile-responsive UI

WON'T HAVE (explicitly excluded from this version):
- Multi-branch library support
- E-book lending
- Social features

This prioritization helps the AI structure its output logically, implementing foundational components first and building additional features on top of them. It also prevents the AI from spending effort on features you explicitly do not want.

Converting Existing Requirements Documents

If your organization already has requirements documents, you can often feed them directly to AI with minimal modification. The key adaptations are:

  1. Remove organizational boilerplate -- approval signatures, revision history, and document metadata add noise without helping the AI.
  2. Clarify ambiguous language -- requirements documents often use phrases like "the system should provide appropriate feedback." Replace these with specific behaviors.
  3. Add technical context -- requirements documents may not specify the tech stack. Add a technical context section.
  4. Resolve cross-references -- if requirements reference other documents, inline the relevant information.

10.3 User Story-Driven Development with AI

User stories are a lightweight, human-centered way to express requirements. They follow the format: "As a [role], I want [capability], so that [benefit]." When enriched with acceptance criteria, user stories become powerful prompts for AI code generation.

Anatomy of an Effective User Story Prompt

A basic user story is too vague for AI:

As a user, I want to reset my password so that I can regain access
to my account.

An enriched user story with acceptance criteria gives AI everything it needs:

User Story: Password Reset

As a registered user who has forgotten my password,
I want to request a password reset via email,
So that I can regain access to my account without contacting support.

Acceptance Criteria:
1. GIVEN I am on the login page
   WHEN I click "Forgot Password"
   THEN I see a form asking for my email address

2. GIVEN I enter a registered email address
   WHEN I submit the reset request
   THEN I receive an email with a reset link within 2 minutes
   AND the link contains a unique, cryptographically secure token
   AND the link expires after 1 hour

3. GIVEN I click a valid, non-expired reset link
   WHEN I enter a new password meeting the requirements
   THEN my password is updated
   AND all existing sessions are invalidated
   AND I am redirected to the login page with a success message

4. GIVEN I click an expired or invalid reset link
   WHEN the page loads
   THEN I see an error message: "This reset link has expired or
        is invalid. Please request a new one."
   AND I see a link to request a new reset

5. GIVEN I enter a new password that does not meet requirements
   WHEN I submit the form
   THEN I see specific validation messages for each unmet requirement
   AND the form retains my input (except the password field)

6. GIVEN I enter an unregistered email address
   WHEN I submit the reset request
   THEN I see the same success message as for registered emails
   (to prevent email enumeration attacks)

Technical Notes:
- Reset tokens: 32-byte random, stored as SHA-256 hash in database
- Email delivery: use SendGrid API
- Password requirements: min 8 chars, 1 uppercase, 1 lowercase,
  1 number, 1 special character
- Framework: Django 5.x with Django REST Framework

The Gherkin-style acceptance criteria (GIVEN/WHEN/THEN) are particularly effective because they map naturally to test cases. AI assistants recognize this format and often generate both implementation code and corresponding tests.

From Product Backlog to Implementation

A product backlog is an ordered list of user stories. When working with AI, you can feed an entire backlog or a sprint's worth of stories to give the AI context about the whole system while focusing on specific stories.

# Product Context
We are building "BookClub", a web application for neighborhood
book clubs to manage their reading lists, schedule meetings,
and discuss books.

Tech Stack: Python 3.12, FastAPI, SQLAlchemy 2.x, PostgreSQL,
React frontend (separate repo)

# Sprint 3 Backlog (implement in this order)

## Story 3.1: Club Creation (8 points)
As a registered user,
I want to create a new book club,
So that I can organize a reading community.

Acceptance Criteria:
- Club requires: name (3-100 chars), description (max 500 chars),
  city, max_members (5-50)
- Creator automatically becomes club admin
- Club gets a unique URL slug generated from name
- Duplicate club names allowed (distinguished by slug)
...

## Story 3.2: Club Discovery (5 points)
As a registered user,
I want to search for book clubs in my city,
So that I can find and join a reading community.

Acceptance Criteria:
- Search by city name (autocomplete from known cities)
- Filter by: has_openings (boolean), genre preferences
- Sort by: newest, most_members, nearest (if location provided)
- Results paginated, 20 per page
- Each result shows: name, description preview (100 chars),
  member_count/max_members, city, genres
...

Callout -- Pattern Recognition: Notice how the backlog provides context about the broader system while each story defines specific functionality. This is exactly the kind of layered context management discussed in Chapter 9. The product context helps AI make consistent architectural decisions across stories, while the individual stories provide the precision needed for correct implementation.

Story Mapping for Complex Features

For features that span multiple user interactions, story mapping helps AI understand the flow:

# Feature: Book Club Meeting Management

## User Journey Map:

Step 1: Admin schedules meeting
  Story: "As a club admin, I want to schedule a meeting with date,
         time, location, and the book to discuss."

Step 2: Members receive notification
  Story: "As a club member, I want to be notified when a new meeting
         is scheduled so I can plan to attend."

Step 3: Members RSVP
  Story: "As a club member, I want to RSVP to meetings (yes/no/maybe)
         so the admin knows who is coming."

Step 4: Admin sees attendance
  Story: "As a club admin, I want to see RSVP counts and who is
         attending so I can plan the meeting space."

Step 5: Meeting occurs (no system interaction)

Step 6: Post-meeting discussion
  Story: "As a meeting attendee, I want to post discussion notes
         and ratings after the meeting."

Data Flow:
Meeting -> Notification -> RSVP -> Attendance Report -> Discussion

This story map gives the AI a complete picture of the data flow and user journey, leading to code that properly connects the components rather than implementing each story as an isolated feature.


10.4 API-First Specification

API-first development means designing your API contract before writing any implementation code. This approach is extraordinarily effective with AI because API specifications in standard formats (OpenAPI, GraphQL SDL) are highly structured, unambiguous, and well-understood by language models.

OpenAPI Specifications as Prompts

The OpenAPI Specification (formerly Swagger) is the industry standard for describing REST APIs. AI coding assistants understand OpenAPI exceptionally well because their training data contains thousands of OpenAPI documents.

openapi: 3.1.0
info:
  title: BookClub API
  version: 1.0.0
  description: API for managing neighborhood book clubs

paths:
  /api/clubs:
    get:
      summary: List book clubs
      operationId: listClubs
      parameters:
        - name: city
          in: query
          required: false
          schema:
            type: string
          description: Filter by city name (case-insensitive partial match)
        - name: has_openings
          in: query
          required: false
          schema:
            type: boolean
          description: Filter to clubs with available membership slots
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        '200':
          description: Paginated list of clubs
          content:
            application/json:
              schema:
                type: object
                properties:
                  clubs:
                    type: array
                    items:
                      $ref: '#/components/schemas/ClubSummary'
                  total:
                    type: integer
                  page:
                    type: integer
                  per_page:
                    type: integer

    post:
      summary: Create a new book club
      operationId: createClub
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateClubRequest'
      responses:
        '201':
          description: Club created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Club'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Authentication required
        '409':
          description: Club slug conflict

components:
  schemas:
    ClubSummary:
      type: object
      required: [id, name, city, member_count, max_members]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description_preview:
          type: string
          maxLength: 100
        city:
          type: string
        member_count:
          type: integer
        max_members:
          type: integer
        genres:
          type: array
          items:
            type: string

    CreateClubRequest:
      type: object
      required: [name, city, max_members]
      properties:
        name:
          type: string
          minLength: 3
          maxLength: 100
        description:
          type: string
          maxLength: 500
        city:
          type: string
        max_members:
          type: integer
          minimum: 5
          maximum: 50

    Club:
      type: object
      required: [id, name, slug, city, max_members, created_at]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        description:
          type: string
        city:
          type: string
        max_members:
          type: integer
        member_count:
          type: integer
        created_at:
          type: string
          format: date-time
        admin_id:
          type: string
          format: uuid

    ErrorResponse:
      type: object
      required: [error, message]
      properties:
        error:
          type: string
        message:
          type: string
        details:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              message:
                type: string

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

When you give an AI this OpenAPI spec along with a prompt like "Implement this API using FastAPI with SQLAlchemy," the resulting code will closely match the specification -- correct endpoint paths, proper request validation, accurate response schemas, and appropriate error handling.

The Prompt Pattern for API-First Development

Here is the recommended pattern for using API specs with AI:

I have the following OpenAPI specification for a BookClub API.
Please implement this specification using:
- Framework: FastAPI
- ORM: SQLAlchemy 2.x with async support
- Database: PostgreSQL
- Authentication: JWT via python-jose

Requirements:
1. Create SQLAlchemy models that match the component schemas
2. Implement all endpoints exactly as specified
3. Include request validation matching the schema constraints
4. Return the exact response formats defined in the spec
5. Implement proper error handling for all defined error responses
6. Use dependency injection for authentication

[paste OpenAPI spec here]

Callout -- Best Practice: When your API spec is large, break it into logical sections and implement one section at a time. Feed the complete spec for context but ask the AI to implement one group of endpoints per prompt. This keeps the AI focused while maintaining awareness of the overall API design.

GraphQL Schema as Specification

For GraphQL APIs, the Schema Definition Language (SDL) serves the same purpose as OpenAPI:

type Query {
  clubs(city: String, hasOpenings: Boolean, page: Int = 1,
        perPage: Int = 20): ClubConnection!
  club(slug: String!): Club
  myClubs: [Club!]!
}

type Mutation {
  createClub(input: CreateClubInput!): Club!
  joinClub(clubId: ID!): Membership!
  leaveClub(clubId: ID!): Boolean!
  scheduleMeeting(input: ScheduleMeetingInput!): Meeting!
}

type Club {
  id: ID!
  name: String!
  slug: String!
  description: String
  city: String!
  maxMembers: Int!
  memberCount: Int!
  members: [Membership!]!
  meetings: [Meeting!]!
  createdAt: DateTime!
  admin: User!
}

input CreateClubInput {
  name: String! @constraint(minLength: 3, maxLength: 100)
  description: String @constraint(maxLength: 500)
  city: String!
  maxMembers: Int! @constraint(min: 5, max: 50)
}

AI assistants trained on GraphQL schemas can generate resolvers, data loaders, and database queries that align precisely with the schema definition.


10.5 Schema-Driven Development

Database schemas and data models are among the most effective specifications for AI prompting. When the AI knows your exact data model, it generates code that correctly handles relationships, constraints, and queries.

SQL Schema as Specification

A well-defined SQL schema is an unambiguous specification for data storage:

-- Database Schema: BookClub Application

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) UNIQUE NOT NULL,
    display_name VARCHAR(100) NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    avatar_url VARCHAR(500),
    city VARCHAR(100),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE clubs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(100) NOT NULL,
    slug VARCHAR(120) UNIQUE NOT NULL,
    description TEXT,
    city VARCHAR(100) NOT NULL,
    max_members INTEGER NOT NULL CHECK (max_members BETWEEN 5 AND 50),
    admin_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE memberships (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    club_id UUID NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
    role VARCHAR(20) NOT NULL DEFAULT 'member'
        CHECK (role IN ('admin', 'moderator', 'member')),
    joined_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(user_id, club_id)
);

CREATE TABLE meetings (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    club_id UUID NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
    title VARCHAR(200) NOT NULL,
    description TEXT,
    meeting_date TIMESTAMP WITH TIME ZONE NOT NULL,
    location VARCHAR(300),
    book_title VARCHAR(300),
    book_author VARCHAR(200),
    created_by UUID NOT NULL REFERENCES users(id),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE rsvps (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    meeting_id UUID NOT NULL REFERENCES meetings(id) ON DELETE CASCADE,
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    status VARCHAR(10) NOT NULL CHECK (status IN ('yes', 'no', 'maybe')),
    responded_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(meeting_id, user_id)
);

-- Indexes for common query patterns
CREATE INDEX idx_clubs_city ON clubs(city);
CREATE INDEX idx_clubs_slug ON clubs(slug);
CREATE INDEX idx_memberships_user ON memberships(user_id);
CREATE INDEX idx_memberships_club ON memberships(club_id);
CREATE INDEX idx_meetings_club_date ON meetings(club_id, meeting_date);
CREATE INDEX idx_rsvps_meeting ON rsvps(meeting_id);

When you provide this schema to an AI with a prompt like "Generate SQLAlchemy models and a FastAPI CRUD layer for this database schema," the AI produces models that exactly match your constraints, relationships, and indexes.

Pydantic Models as Schema Specifications

For Python projects, Pydantic models serve as both schema definition and runtime validation. They are excellent specifications because they combine type information, validation rules, and documentation in one place:

from pydantic import BaseModel, Field, EmailStr
from datetime import datetime
from uuid import UUID
from enum import Enum


class MemberRole(str, Enum):
    ADMIN = "admin"
    MODERATOR = "moderator"
    MEMBER = "member"


class RSVPStatus(str, Enum):
    YES = "yes"
    NO = "no"
    MAYBE = "maybe"


class CreateClubRequest(BaseModel):
    """Request schema for creating a new book club."""
    name: str = Field(
        ..., min_length=3, max_length=100,
        description="Club name, must be unique within a city"
    )
    description: str | None = Field(
        None, max_length=500,
        description="Optional description of the club"
    )
    city: str = Field(
        ..., min_length=2, max_length=100,
        description="City where the club is based"
    )
    max_members: int = Field(
        ..., ge=5, le=50,
        description="Maximum number of members (5-50)"
    )


class ClubResponse(BaseModel):
    """Response schema for a book club."""
    id: UUID
    name: str
    slug: str
    description: str | None
    city: str
    max_members: int
    member_count: int
    created_at: datetime
    admin_id: UUID

    model_config = {"from_attributes": True}

Callout -- Why This Works: Pydantic models are a form of executable specification. They do not just describe the data -- they enforce it at runtime. When AI sees a Pydantic model, it understands both the data structure and the validation rules, and generates code that leverages Pydantic's validation automatically.

JSON Schema for Cross-Language Specifications

When your specification needs to be language-agnostic, JSON Schema is the standard:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "BookClub Configuration",
  "type": "object",
  "required": ["name", "city", "max_members"],
  "properties": {
    "name": {
      "type": "string",
      "minLength": 3,
      "maxLength": 100,
      "description": "Club name"
    },
    "description": {
      "type": "string",
      "maxLength": 500
    },
    "city": {
      "type": "string"
    },
    "max_members": {
      "type": "integer",
      "minimum": 5,
      "maximum": 50
    },
    "genres": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": ["fiction", "non-fiction", "mystery", "sci-fi",
                 "romance", "biography", "history", "science"]
      },
      "uniqueItems": true
    }
  }
}

10.6 Test-First Prompting

Test-first prompting inverts the typical AI coding workflow. Instead of describing what you want and asking AI to write code, you write the tests first and ask the AI to write code that passes them. This is AI-assisted Test-Driven Development (TDD), and it produces remarkably high-quality results.

The Test-First Workflow

The traditional TDD cycle is Red-Green-Refactor: write a failing test, write code to pass it, then refactor. With AI, the workflow becomes:

  1. You write the tests -- defining expected behavior precisely
  2. AI writes the implementation -- generating code to pass your tests
  3. You run the tests -- verifying the AI's implementation
  4. You refactor together -- improving the code while keeping tests green

Writing Tests as Specifications

Tests are the most precise form of specification because they define exact inputs, exact outputs, and exact behavior. There is no ambiguity in a test:

import pytest
from datetime import datetime, timedelta
from decimal import Decimal


class TestLateFeesCalculation:
    """Tests for the library late fee calculation system.

    Business Rules:
    - Standard fee: $0.25 per day per book
    - Maximum fee: $25.00 per book
    - Children's books: $0.10 per day, max $10.00
    - Grace period: 1 day (no fee if returned 1 day late)
    - Weekends and holidays do not count as late days
    """

    def test_no_fee_when_returned_on_time(self, calculator):
        due_date = datetime(2025, 6, 15)
        return_date = datetime(2025, 6, 15)
        fee = calculator.calculate_fee(
            due_date=due_date,
            return_date=return_date,
            book_type="standard"
        )
        assert fee == Decimal("0.00")

    def test_no_fee_within_grace_period(self, calculator):
        due_date = datetime(2025, 6, 15)  # Sunday
        return_date = datetime(2025, 6, 16)  # Monday, 1 day late
        fee = calculator.calculate_fee(
            due_date=due_date,
            return_date=return_date,
            book_type="standard"
        )
        assert fee == Decimal("0.00")

    def test_standard_fee_after_grace_period(self, calculator):
        due_date = datetime(2025, 6, 10)  # Tuesday
        return_date = datetime(2025, 6, 13)  # Friday, 3 days late
        # 3 days late - 1 grace day = 2 billable days
        fee = calculator.calculate_fee(
            due_date=due_date,
            return_date=return_date,
            book_type="standard"
        )
        assert fee == Decimal("0.50")

    def test_weekends_excluded(self, calculator):
        due_date = datetime(2025, 6, 13)  # Friday
        return_date = datetime(2025, 6, 18)  # Wednesday, 5 calendar days
        # Only Mon, Tue, Wed count = 3 late days - 1 grace = 2 billable
        fee = calculator.calculate_fee(
            due_date=due_date,
            return_date=return_date,
            book_type="standard"
        )
        assert fee == Decimal("0.50")

    def test_maximum_fee_cap(self, calculator):
        due_date = datetime(2025, 1, 1)
        return_date = datetime(2025, 12, 31)
        fee = calculator.calculate_fee(
            due_date=due_date,
            return_date=return_date,
            book_type="standard"
        )
        assert fee == Decimal("25.00")

    def test_childrens_book_reduced_rate(self, calculator):
        due_date = datetime(2025, 6, 10)  # Tuesday
        return_date = datetime(2025, 6, 13)  # Friday, 3 days late
        # 2 billable days * $0.10 = $0.20
        fee = calculator.calculate_fee(
            due_date=due_date,
            return_date=return_date,
            book_type="children"
        )
        assert fee == Decimal("0.20")

    def test_childrens_book_max_fee(self, calculator):
        due_date = datetime(2025, 1, 1)
        return_date = datetime(2025, 12, 31)
        fee = calculator.calculate_fee(
            due_date=due_date,
            return_date=return_date,
            book_type="children"
        )
        assert fee == Decimal("10.00")

    def test_early_return_no_fee(self, calculator):
        due_date = datetime(2025, 6, 15)
        return_date = datetime(2025, 6, 10)
        fee = calculator.calculate_fee(
            due_date=due_date,
            return_date=return_date,
            book_type="standard"
        )
        assert fee == Decimal("0.00")

Now you can give this test file to AI with a simple prompt:

Here are my tests for a late fee calculation system. Please write
the LateFeeCalculator class that passes all these tests. Include
the pytest fixture for 'calculator'. Follow the business rules
documented in the test class docstring.

The AI has no room for misinterpretation. The tests define exact inputs, exact outputs, and exact edge cases. The resulting implementation will be correct -- or it will not pass the tests, and you will know immediately.

Benefits of Test-First Prompting

Test-first prompting has several advantages over implementation-first approaches:

  1. Unambiguous specification: Tests define behavior precisely. There is no room for the AI to misinterpret "calculate late fees" when the exact calculation is demonstrated through examples.

  2. Built-in verification: You can immediately verify the AI's output by running the tests. No manual inspection required.

  3. Edge case coverage: Writing tests forces you to think about edge cases (early returns, maximum fees, weekends) that you might forget to mention in a natural language prompt.

  4. Regression protection: The tests remain valuable long after the initial implementation, protecting against bugs during future modifications.

  5. Documentation: The tests serve as living documentation of expected behavior.

Callout -- From Chapter 8: Remember the PRECISE framework from Chapter 8? Test-first prompting is the ultimate expression of the "E" (Examples) element. Instead of providing one or two examples in your prompt, you provide a comprehensive set of examples in executable form.

Property-Based Test Specifications

For mathematical or algorithmic functions, property-based tests can be even more powerful than example-based tests:

from hypothesis import given, strategies as st


class TestSortingAlgorithm:
    """Properties that any correct sorting implementation must satisfy."""

    @given(st.lists(st.integers()))
    def test_output_length_equals_input_length(self, input_list):
        result = sort_function(input_list)
        assert len(result) == len(input_list)

    @given(st.lists(st.integers()))
    def test_output_is_ordered(self, input_list):
        result = sort_function(input_list)
        for i in range(len(result) - 1):
            assert result[i] <= result[i + 1]

    @given(st.lists(st.integers()))
    def test_output_is_permutation_of_input(self, input_list):
        result = sort_function(input_list)
        assert sorted(result) == sorted(input_list)

    @given(st.lists(st.integers(), min_size=0, max_size=0))
    def test_empty_list(self, input_list):
        result = sort_function(input_list)
        assert result == []

    @given(st.lists(st.integers(), min_size=1, max_size=1))
    def test_single_element(self, input_list):
        result = sort_function(input_list)
        assert result == input_list

These property-based tests do not specify a particular sorting algorithm; they specify the properties that any correct sort must satisfy. This gives the AI freedom to choose an implementation approach while constraining the correctness.


10.7 Interface and Contract Specifications

Interface specifications define the boundaries between components. When you specify interfaces before implementation, you give the AI a clear picture of how components should interact, leading to more modular, maintainable code.

Python Protocol and Abstract Base Class Specifications

Python's Protocol classes and ABCs (Abstract Base Classes) are natural specification tools:

from typing import Protocol, runtime_checkable
from datetime import datetime
from uuid import UUID


@runtime_checkable
class BookRepository(Protocol):
    """Interface for book data access.

    Any class implementing this protocol must support these operations.
    Implementations may use SQL databases, NoSQL stores, or in-memory
    storage.
    """

    async def get_by_id(self, book_id: UUID) -> Book | None:
        """Retrieve a book by its unique identifier.

        Returns None if no book exists with the given ID.
        Must complete within 100ms for indexed lookups.
        """
        ...

    async def search(
        self,
        query: str,
        filters: BookFilters | None = None,
        page: int = 1,
        per_page: int = 20
    ) -> PaginatedResult[Book]:
        """Search books by title or author.

        Query performs case-insensitive partial matching.
        Results ordered by relevance, then by title alphabetically.
        Must complete within 200ms for databases up to 100k books.
        """
        ...

    async def create(self, book: CreateBookRequest) -> Book:
        """Create a new book record.

        Raises DuplicateISBNError if ISBN already exists.
        Raises ValidationError if required fields are missing.
        """
        ...

    async def update(
        self, book_id: UUID, updates: UpdateBookRequest
    ) -> Book:
        """Update an existing book record.

        Raises NotFoundError if book does not exist.
        Raises ValidationError if updates violate constraints.
        ISBN cannot be modified after creation.
        """
        ...

    async def delete(self, book_id: UUID) -> bool:
        """Delete a book record.

        Returns True if deleted, False if not found.
        Raises IntegrityError if book has active checkouts.
        """
        ...

This interface specification tells the AI exactly what methods to implement, what parameters they take, what they return, and what errors they raise. You can then prompt:

Implement the BookRepository protocol using SQLAlchemy 2.x async
with PostgreSQL. Create a class called PostgresBookRepository.
Ensure all performance requirements in the docstrings are met
through appropriate indexing and query optimization.

Design by Contract

Design by Contract (DbC) adds preconditions, postconditions, and invariants to your specifications:

class ShoppingCart:
    """Shopping cart with contract specifications.

    Invariants:
    - total_price is always >= 0
    - total_price always equals sum of (item.price * item.quantity)
      for all items
    - item_count always equals sum of item.quantity for all items
    - No item can have quantity <= 0
    """

    def add_item(self, product_id: str, quantity: int,
                 unit_price: Decimal) -> None:
        """Add an item to the cart.

        Preconditions:
        - quantity > 0
        - unit_price >= 0
        - product_id is non-empty string

        Postconditions:
        - If product was already in cart, its quantity increases
          by the given amount
        - If product was not in cart, it is added with given
          quantity and price
        - total_price increases by (quantity * unit_price)
        - item_count increases by quantity
        """
        ...

    def remove_item(self, product_id: str) -> None:
        """Remove an item entirely from the cart.

        Preconditions:
        - product_id exists in the cart

        Postconditions:
        - The item is no longer in the cart
        - total_price decreases by (removed_item.quantity *
          removed_item.unit_price)
        - item_count decreases by removed_item.quantity

        Raises:
        - ItemNotFoundError if product_id not in cart
        """
        ...

    def apply_discount(self, code: str) -> Decimal:
        """Apply a discount code to the cart.

        Preconditions:
        - code is a valid, non-expired discount code
        - No discount has already been applied

        Postconditions:
        - total_price is reduced by the discount amount
        - total_price is never negative (floor at 0.00)
        - The discount code is marked as used for this cart
        - Returns the discount amount applied

        Raises:
        - InvalidDiscountError if code is invalid or expired
        - DiscountAlreadyAppliedError if a discount was already used
        """
        ...

Contracts give AI precise behavioral requirements that go beyond type signatures. The AI can use preconditions to generate input validation, postconditions to generate assertions and state updates, and invariants to generate consistency checks.

Callout -- Integration with Testing: Contract specifications naturally produce test cases. Each precondition suggests a negative test (what happens when the precondition is violated), and each postcondition suggests a positive test (verify the postcondition holds after the operation).


10.8 Configuration and Environment Specs

Configuration specifications define how your application should behave in different environments. These are often overlooked in AI prompting but can prevent a whole class of deployment issues.

Environment Configuration Specification

# Environment Configuration Specification

environments:
  development:
    database:
      host: localhost
      port: 5432
      name: bookclub_dev
      pool_size: 5
      echo_sql: true
    server:
      host: 0.0.0.0
      port: 8000
      reload: true
      debug: true
    auth:
      jwt_secret: dev-secret-key-not-for-production
      token_expiry_hours: 72
      require_email_verification: false
    logging:
      level: DEBUG
      format: verbose
    cors:
      allowed_origins: ["http://localhost:3000"]

  testing:
    database:
      host: localhost
      port: 5432
      name: bookclub_test
      pool_size: 2
      echo_sql: false
    server:
      host: 127.0.0.1
      port: 8001
      reload: false
      debug: false
    auth:
      jwt_secret: test-secret-key
      token_expiry_hours: 1
      require_email_verification: false
    logging:
      level: WARNING
      format: json

  production:
    database:
      host: ${DB_HOST}
      port: ${DB_PORT}
      name: ${DB_NAME}
      pool_size: 20
      echo_sql: false
      ssl_mode: require
    server:
      host: 0.0.0.0
      port: ${PORT}
      reload: false
      debug: false
      workers: 4
    auth:
      jwt_secret: ${JWT_SECRET}
      token_expiry_hours: 24
      require_email_verification: true
    logging:
      level: INFO
      format: json
    cors:
      allowed_origins: ${ALLOWED_ORIGINS}

When you give this to AI with a prompt like "Create a configuration management module that loads these settings using Pydantic Settings," the AI generates code that handles environment variables, defaults, and validation correctly.

Docker and Infrastructure Specifications

Infrastructure specifications are also effective prompts:

# Docker Compose Specification

services:
  api:
    build: ./api
    ports: ["8000:8000"]
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/bookclub
      - JWT_SECRET=${JWT_SECRET}
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  db:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    environment:
      - POSTGRES_DB=bookclub
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d bookclub"]
      interval: 5s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    ports: ["6379:6379"]

volumes:
  pgdata:

Callout -- Environment Awareness: When your AI prompt includes infrastructure specifications, the generated application code often handles environment-specific concerns more gracefully -- connection pooling, health checks, graceful shutdown, and configuration via environment variables.


10.9 Specification Templates

Having reusable specification templates accelerates your workflow. Instead of writing specifications from scratch for each project, you adapt templates to your specific needs.

Microservice Specification Template

# Microservice Specification: [Service Name]

## Overview
- **Purpose**: [One-sentence description]
- **Domain**: [Bounded context in your architecture]
- **Team**: [Owning team]

## Technical Stack
- Language: [e.g., Python 3.12]
- Framework: [e.g., FastAPI]
- Database: [e.g., PostgreSQL 16]
- Cache: [e.g., Redis 7]
- Message Queue: [e.g., RabbitMQ / Kafka topic]

## Data Model
[SQL schema or Pydantic models for all entities]

## API Endpoints
[OpenAPI spec or endpoint list with request/response formats]

## Events Published
[List of events this service publishes to the message queue]
- Event: [name]
  Payload: [schema]
  Trigger: [when this event is published]

## Events Consumed
[List of events this service subscribes to]
- Event: [name]
  Source: [publishing service]
  Handler: [what this service does when receiving this event]

## External Dependencies
[APIs, services, or resources this service depends on]

## Configuration
[Environment variables and their purposes]

## Health Checks
- Readiness: [what must be true for the service to receive traffic]
- Liveness: [what is checked to ensure the service is running]

## Non-Functional Requirements
- Latency: [p50, p95, p99 targets]
- Throughput: [requests per second]
- Availability: [target percentage]

CLI Tool Specification Template

# CLI Tool Specification: [Tool Name]

## Purpose
[What problem does this tool solve?]

## Installation
[How is it installed? pip, brew, binary download?]

## Commands

### [command-name]
Description: [what it does]
Usage: tool-name command-name [options] <arguments>

Arguments:
  <arg1>    [description] (required)
  <arg2>    [description] (optional, default: X)

Options:
  -o, --option    [description] (default: X)
  -v, --verbose   [description]
  --format        [output format: json|table|csv] (default: table)

Examples:
  $ tool-name command-name "input" --format json
  $ tool-name command-name --verbose

Exit Codes:
  0: Success
  1: General error
  2: Invalid input
  3: Network error

### [next-command]
...

## Configuration File
Location: ~/.tool-name/config.yaml
Format:
  default_format: table
  api_key: ${TOOL_API_KEY}
  timeout: 30

## Output Formats
[Define what each output format looks like]

CRUD Application Specification Template

# CRUD Application: [App Name]

## Entities

### [Entity Name]
Fields:
  - id: UUID, auto-generated, primary key
  - [field]: [type], [constraints], [description]
  - created_at: datetime, auto-set
  - updated_at: datetime, auto-updated

Relationships:
  - [relationship description]

Validation Rules:
  - [rule 1]
  - [rule 2]

### [Next Entity]
...

## Operations per Entity

### Create
- Required fields: [list]
- Optional fields: [list]
- Auto-generated fields: [list]
- Side effects: [e.g., send notification, update cache]

### Read (Single)
- Lookup by: [id, slug, etc.]
- Include related: [related entities to eager-load]

### Read (List)
- Filters: [filterable fields and operators]
- Sort options: [sortable fields, default sort]
- Pagination: [style -- offset or cursor, default page size]

### Update
- Updatable fields: [list]
- Immutable fields: [list]
- Side effects: [e.g., clear cache, notify subscribers]

### Delete
- Soft delete or hard delete?
- Cascade behavior: [what happens to related records]
- Authorization: [who can delete]

## Authorization Rules
- [Role]: [allowed operations]
- [Role]: [allowed operations]

Callout -- Template Customization: These templates are starting points, not rigid forms. Remove sections that do not apply to your project and add sections for domain-specific concerns. The goal is to prompt your own thinking as much as to prompt the AI.


10.10 When Specifications Help vs. Hinder

Specification-driven prompting is powerful, but it is not always the right approach. Understanding when to invest in detailed specifications and when to use lighter-weight prompting is a judgment skill that improves with experience.

When Specifications Help Most

Complex business logic: When the code must implement intricate rules with many edge cases, a specification prevents the AI from missing cases.

# Without specification: "Calculate shipping costs"
# (AI guesses at zones, weights, surcharges)

# With specification:
Shipping Cost Rules:
- Zone A (< 100 miles): base $5.99
- Zone B (100-500 miles): base $9.99
- Zone C (500+ miles): base $14.99
- Weight surcharge: +$1.50 per pound over 5 lbs
- Oversize surcharge: +$8.00 if any dimension > 24 inches
- Hazmat surcharge: +$12.00
- Free shipping if order total > $75 (before surcharges)
- Express: 2x base rate
- Saturday delivery: +$15.00 (only Zone A and B)

Multi-component systems: When code must interact with other components, specifications ensure the interfaces match.

Regulatory or compliance requirements: When code must meet specific standards, specifications document exactly what is required.

Team projects: When multiple developers (or multiple AI sessions) work on the same system, specifications ensure consistency.

APIs consumed by others: When other developers will use your API, getting the contract right matters more than implementation speed.

When Specifications Hinder

Exploration and prototyping: When you are not sure what you want yet, writing a detailed specification is premature. Use conversational prompting to explore ideas, then formalize what works.

# Good for exploration:
"What are some approaches to building a recommendation engine
for a book club app? Show me a simple prototype of each."

# Premature specification:
"Build a recommendation engine with these exact weights:
genre_match: 0.35, author_match: 0.25, ..."
# (You don't know the right weights yet)

Simple, well-understood tasks: If you need a function to parse a CSV file, a conversational prompt is often sufficient. Over-specifying trivial tasks wastes time.

Rapidly changing requirements: If requirements are changing daily, maintaining detailed specifications creates overhead. Use lighter-weight user stories until requirements stabilize.

Learning and experimentation: When your goal is to learn how something works, a specification constrains the AI from showing you alternative approaches. Conversational exploration is more educational.

The Specification Spectrum

In practice, most projects benefit from a mix of approaches:

Component Specification Level Why
Database schema High (formal schema) Data structures are foundational; errors here propagate everywhere
API endpoints High (OpenAPI spec) APIs are contracts; changes break consumers
Business logic Medium-High (requirements + tests) Complex rules need precision; tests catch errors
UI components Medium (user stories + mockups) UI is iterative; over-specifying limits exploration
Utility functions Low (conversational) Simple, well-understood patterns
Prototypes Minimal (exploratory) Goal is to learn, not to build

Incremental Specification

You do not need to write a complete specification before starting. A practical approach is incremental specification:

  1. Start with a rough description to generate a prototype
  2. Identify the parts that matter most (data model, API contracts, critical business logic)
  3. Write formal specifications for those parts and regenerate them
  4. Keep other parts as conversational prompts until they stabilize
  5. Formalize more over time as the system matures

This approach gives you the benefits of specification-driven development where they matter most while avoiding the overhead where they do not.

Callout -- The 80/20 Rule of Specifications: In most projects, 20% of the code contains 80% of the complexity. Focus your specification effort on that 20%. The authentication system, the payment processing, the complex business rules -- these deserve detailed specs. The "list all items" endpoint does not need a page-long specification.

Signs You Need More Specification

Watch for these indicators that your prompts need more formality:

  • The AI keeps getting the same thing wrong after multiple iterations -- you have an ambiguity that only a specification can resolve
  • Generated code handles some edge cases but misses others -- you need to enumerate all cases explicitly
  • Two parts of the system do not work together -- you need interface specifications
  • You keep correcting data types or formats -- you need schema specifications
  • The AI generates extra features you did not want -- you need explicit scope boundaries

Signs You Have Too Much Specification

Conversely, watch for these signs of over-specification:

  • You spend more time writing specs than the AI would spend generating code -- simplify
  • The specification is so long the AI truncates or ignores parts -- break it into focused sections (as covered in Chapter 9's context management)
  • You are specifying implementation details instead of requirements -- step back and focus on what, not how
  • Requirements are still changing and you keep updating the specification -- use a lighter format until things stabilize

Chapter Summary

Specification-driven prompting represents a maturation in how we work with AI coding assistants. By investing time in clear, structured specifications, we dramatically improve the quality, predictability, and correctness of generated code.

The key insight of this chapter is that specifications are not bureaucratic overhead -- they are precision instruments for communicating with AI. Every format we explored -- requirements documents, user stories, OpenAPI specs, database schemas, test suites, interface definitions, and configuration specifications -- gives AI a different kind of precision:

  • Requirements documents eliminate ambiguity about what the system should do
  • User stories with acceptance criteria define behavior from the user's perspective
  • API specifications define contracts that generated code must conform to
  • Database schemas ensure correct data modeling and relationships
  • Test specifications provide the most precise behavioral definition possible
  • Interface contracts ensure components work together correctly
  • Configuration specs prevent environment-related issues

The art lies in knowing when each approach is appropriate and how much formality to apply. Use the spectrum: formal specifications for complex, critical, or shared components; lighter approaches for simple, exploratory, or rapidly changing work.

In Chapter 11, you will learn about iterative refinement -- how to take AI-generated code and progressively improve it through focused follow-up prompts. The specifications you learned to write in this chapter will serve as the foundation against which you measure each iteration's progress.


Key Terms

  • Specification-driven prompting: Providing AI with formal or semi-formal specifications instead of conversational descriptions
  • Functional requirements: Statements of what a system must do
  • Non-functional requirements: Quality attributes (performance, security, reliability)
  • Acceptance criteria: Specific conditions that must be met for a user story to be considered complete
  • OpenAPI specification: Industry standard format for describing REST APIs
  • Schema-driven development: Using data schemas as the primary specification for code generation
  • Test-first prompting: Writing tests before asking AI to write implementation code
  • Design by contract: Specifying preconditions, postconditions, and invariants for operations
  • Interface specification: Defining the boundaries and contracts between system components
  • MoSCoW method: Prioritization technique using Must, Should, Could, and Won't categories
  • Incremental specification: Progressively adding formality to specifications as the system matures