1. Introduction

In an era where AI agents like Claude Code and GitHub Copilot have become commonplace, the development landscape has undergone a dramatic transformation. Once you issue an instruction, code flows out effortlessly. However, isn't there a lingering issue: do you find yourself repeatedly explaining to the AI—verbally or via chat—what you want built?

This is akin to giving driving directions to a driver (AI) without ever telling them the final destination. While the code you get might work in the moment, after some time passes, you inevitably wonder: "Why is the implementation structured this way?" and "What was the original specification again?" As code and specs gradually diverge, many teams face this problem regardless of project size.

In reality, situations like these are commonplace:

  • "Specifications don't exist in the first place"
  • "Specs exist but are outdated and no longer reflect the current state"
  • "Nobody can track the big picture anymore; it's become a black box and we have no idea how anything works"

Specs tend to be "write once and forget" documents. As implementation progresses, discrepancies between code and specs accumulate, ultimately leaving teams in a state where "the code is the specification."

In this article, I'll introduce a combined approach: Spec-Driven Development (where you write a specification first and use it to guide AI implementation), paired with Code-First Spec-Backfill Development (where you implement code changes first, then update the spec to match). Together, these approaches keep both code and specifications up-to-date as a single, continuously aligned source of truth.

Note: "Code-First Spec-Backfill Development" is a term I've coined for this article. If a similar methodology already exists under a different name, I'd love to hear about it in the comments!

This article is aimed at readers like you:

  • You use Claude Code or GitHub Copilot daily for development
  • You often find that code gets written before the spec is ready
  • Writing specs feels tedious and tends to be deprioritized
  • You've thought, "I haven't updated the spec in ages..."
  • Specs don't exist or are hopelessly out of date
  • Code and specs have become disconnected; the system is a black box
  • You want to establish traceability between code and specifications
  • You're new to development and unsure how to write specs in the first place

2. Two Approaches: Spec-Driven Development vs. Code-First Spec-Backfill Development

You may have heard of "Vibe Coding"—feeding AI loose requirements and letting it write code based on intuition. While this approach excels at rapid prototyping, it struggles with large-scale applications. This article takes the opposite stance: running AI development on the foundation of a specification. However, this approach actually has two distinct directions.

2.1. Spec-Driven Development: Specs Guide Code

The first is Spec-Driven Development (SDD)—a workflow where you prepare detailed specifications upfront (requirements, design, detailed specs, implementation plans) and then use those to drive AI agents.

The logic is straightforward:

By preparing detailed specs in advance, you can control AI behavior precisely.

While Vibe Coding lets "AI figure it out," Spec-Driven Development "guides the AI with a design document." This approach is most powerful when you're building an app from scratch with no existing codebase.

2.2. Code-First Spec-Backfill Development: Code Updates Specs

On the other hand, in real-world development, strictly following "update the spec first, then implement" for every small change feels unnecessarily restrictive. For minor modifications, it's often more practical to adjust the code directly, verify it works, and then update the spec afterward—the reverse workflow.

This is Code-First Spec-Backfill Development (CFSD), the second axis for this article. The idea is to work in the "code → spec" order—opposite to Spec-Driven Development—while ultimately ensuring that code and specs remain aligned.

Note: "Code-First Spec-Backfill Development" is a term I've coined for this article. If a similar methodology already exists under a different name, I'd love to hear about it in the comments!

2.3. Using Both Approaches Based on Project Phase

These aren't contradictory; rather, choose which to use based on your project's situation and phase:

  • Building an app from scratchSpec-Driven Development (specs → code)
  • Working on an existing app but lacking, incomplete, or outdated specsFirst, analyze the code and generate a spec file (spec.yaml) (code → specs)
  • Gradually refining and improving an existing appCode-First Spec-Backfill Development (code → specs)

For existing applications where specs are missing or out of sync with implementation, start by having AI analyze the current code and generate a spec.yaml as a starting point. After human review and corrections, transition to Spec-Driven Development or Code-First Spec-Backfill Development.

Regardless of approach, the ultimate goal is the same: keep code and specs perpetually in sync.

2.4. Use YAML for Spec Format

This article uses YAML as the specification format.

Recent research emphasizes providing LLMs with information in structured formats like JSON or YAML. Elnashar et al. (2025) compared three prompt styles (JSON, YAML, and Hybrid CSV/Prefix) using GPT-4o, demonstrating that prompt format influences output quality, token cost, and processing time. They found YAML offers excellent balance between readability and efficiency.

We adopt YAML for our spec format because:

  • Simple structure that clearly represents hierarchies
  • Supports comments (unlike JSON)
  • Easy for humans to read and maintain
  • Straightforward to pass structured information to AI

3. Step 1: Write Your Spec Document (spec.yaml) in YAML

First, use Claude Code or GitHub Copilot to create a spec.yaml design document.

The critical aspect here is granularity. Write this specification in enough detail that an AI can implement the entire app just from reading it. Avoid vague language; aim for a spec that leaves no room for AI confusion.

Beyond just APIs and database design, I recommend including controller/service/repository responsibilities, class names, and function/method names in the spec. This level of detail prevents AI from inventing its own naming conventions and architecture, and makes traceability much easier to maintain.

That said, writing such detailed specs from scratch is genuinely challenging. Here's my recommendation: co-create the spec interactively with AI. Start by conveying a rough outline of the app, then work through each necessary component one by one, writing YAML as you go. This seems time-consuming upfront, but it pays dividends later—once AI has a clear spec, implementation instructions become simple and rework diminishes significantly.

The same approach applies to existing applications. If specs are missing or outdated, ask AI to analyze the current code and generate a spec.yaml starting point. However, don't consider it done after one generation. Instead, engage in repeated dialogue: "What's the spec for this feature?" "What's this API's responsibility?" "Is this class structure sound?" Iteratively refine the spec through conversation until it reflects actual requirements.

In other words, whether starting fresh or working with legacy code, don't aim for a perfect spec on day one. Refine it iteratively through dialogue with AI. Higher-spec quality directly translates to better implementation quality and maintainability.

When deciding on language, frameworks, and databases, consult AI—but don't accept its suggestions uncritically. Investigate and verify: "Is this architecture truly sound?" "Are there alternatives?" AI is a design partner, not a decision-maker. Responsibility for final design and tech choices rests with you.

At minimum, document these items:

  • Purpose and overview of the app
  • Languages (frontend/backend)
  • Frameworks to use
  • Database (type, table structure, etc.)
  • API design (endpoints, request/response schemas)
  • Screen layouts and feature list
  • Class design (Controllers/Services/Repositories)
  • Function names (method names) and responsibilities
  • Directory structure
  • Authentication/authorization approach
  • Environment variables
  • External service/API integrations
  • Testing strategy (Unit/Integration/E2E)
  • Coding standards and naming conventions
  • Constraints (non-functional, performance, security requirements)
  • Changelog
  • Unimplemented features / future work (TODOs/Backlog)

3.1. Example spec.yaml

project:
  name: TaskManagerApp
  version: 1.0.0
  purpose: "A simple task management app for individuals and teams"
tech_stack:
  frontend:
    language: TypeScript
    framework: Next.js
    styling: Tailwind CSS
  backend:
    language: TypeScript
    framework: NestJS
  database:
    type: PostgreSQL
    orm: Prisma
architecture:
  pattern: MVC
  directories:
    controllers: src/controllers
    services: src/services
    repositories: src/repositories
    models: src/models
    tests: tests
database_schema:
  tables:
    - name: users
      columns:
        - { name: id, type: uuid, primary_key: true }
        - { name: email, type: string, unique: true }
        - { name: password_hash, type: string }
    - name: tasks
      columns:
        - { name: id, type: uuid, primary_key: true }
        - { name: user_id, type: uuid, foreign_key: users.id }
        - { name: title, type: string }
        - { name: status, type: enum, values: [todo, in_progress, done] }
controllers:
  AuthController:
    methods:
      - login
      - register
  TaskController:
    methods:
      - list
      - create
      - update
      - delete
services:
  AuthService:
    methods:
      - authenticate
      - createUser
  TaskService:
    methods:
      - getTasks
      - createTask
      - updateTask
      - deleteTask
repositories:
  UserRepository:
    methods:
      - findByEmail
      - create
  TaskRepository:
    methods:
      - findAllByUserId
      - create
      - update
      - delete
api:
  - method: POST
    path: /auth/login
    controller: AuthController
    action: login
    service: AuthService.authenticate
  - method: GET
    path: /tasks
    controller: TaskController
    action: list
    service: TaskService.getTasks
  - method: POST
    path: /tasks
    controller: TaskController
    action: create
    service: TaskService.createTask
dto:
  LoginRequest:
    email: string
    password: string
  LoginResponse:
    accessToken: string
  CreateTaskRequest:
    title: string
  TaskResponse:
    id: uuid
    title: string
    status: string
features:
  - User registration
  - JWT login
  - Task CRUD
  - Task status management
coding_rules:
  naming:
    controller: PascalCase
    service: PascalCase
    repository: PascalCase
    method: camelCase
  comments:
    language: English
testing:
  framework: Jest
  unit:
    - AuthService
    - TaskService
  integration:
    - AuthController
    - TaskController
constraints:
  - Passwords must be hashed with bcrypt
  - APIs must be implemented as REST
  - Controllers must not contain business logic
  - Services must be called before Repositories
ai_rules:
  - spec.yaml is the single source of truth
  - Don't change naming conventions
  - Generate test code alongside implementation
  - Report differences between spec.yaml and code after implementation
changelog:
  - version: 1.0.1
    date: "2026-07-20"
    author: yamada
    summary: "Added priority field"
  - version: 1.0.0
    date: "2026-07-15"
    author: suzuki
    summary: "Initial version"
backlog:
  - id: TASK-101
    title: Task search feature
    priority: High
  - id: TASK-102
    title: Email notifications
    priority: Low

Enter fullscreen mode Exit fullscreen mode

This YAML is just an example. If you have ideas for clearer spec structures or additional useful sections, please share in the comments!

The key is meticulously documenting "what," "in what language/tech," and "in what structure" you're building. With a spec like this, you never need to repeat explanations to AI—which also saves tokens.

4. Step 2: Set Up Your Development Environment

Once you have your spec, prepare the actual development environment.

4.1. Set Up Git

First, adopt Git to track and manage code changes. GitHub Desktop or similar GUIs make this convenient.

  1. Create a GitHub account
  2. Create a repository (strongly recommend making it Private)

For detailed setup instructions, countless excellent guides exist online, so I'll skip those details here.

4.2. Set Up an Editor

Next, install an editor like Visual Studio Code and the Claude Code or GitHub Copilot extensions/CLIs. Refer to official documentation for installation:

5. Step 3: Have AI Implement Based on Your Spec

Once your environment is ready, it's time for AI to implement your app. Here's where Spec-Driven Development truly shines. Note: This step assumes you're building a new app. If you already have an application, skip to Step 4: "Checking for Discrepancies Between Spec and Implementation."

5.1. Start by Creating the App

@spec.yaml
Read the entire contents and implement the app without omission.

Enter fullscreen mode Exit fullscreen mode

5.2. Have AI Write Tests Alongside Code

@spec.yaml
Read the entire contents and write test code as well.

Enter fullscreen mode Exit fullscreen mode

Once you submit this prompt, wait for results. Depending on scope, it may take considerable time—be patient.

6. Step 4: Check for Discrepancies Between Spec and Implementation

After implementation completes, have AI verify that the spec and actual code align perfectly. This is a critical step for ensuring traceability.

@spec.yaml
Report any discrepancies between the current code and the spec.

Enter fullscreen mode Exit fullscreen mode

When discrepancies are reported, assess whether the implementation or spec is problematic, then decide which to correct.

If the implementation is the problem (code doesn't follow spec.yaml):

Fix the discrepancies you just identified.
Align the code with @spec.yaml.

Enter fullscreen mode Exit fullscreen mode

If the spec is the problem (spec.yaml differs from implementation):

Reflect the changes you mentioned in the following document.
Update @spec.yaml accordingly.

Enter fullscreen mode Exit fullscreen mode

By constantly synchronizing code and specs this way, you're prepared for the next phase.

7. Step 5: The Review → Fix or Spec Update Loop

The finished app almost always differs slightly from your original vision. This is where you enter a loop:

7.1. Commit First

Record the current state with a commit.

7.2. If It Doesn't Match Your Vision → Fix via Prompt

Modify the code to match (desired changes).

Enter fullscreen mode Exit fullscreen mode

If the fix looks good, request tests for that change:

Read this change and write test code for it.

Enter fullscreen mode Exit fullscreen mode

7.3. If It Matches Your Vision → Update the Spec

If the implementation is correct (code is right, spec needs updating), update the spec side:

Read this change and update @spec.yaml accordingly.

Enter fullscreen mode Exit fullscreen mode

If you're satisfied, commit this update too.

7.4. Repeat

Continue cycling through:

  • Modify/add code
  • Write test code
  • Update spec (if satisfied)
  • Commit

By repeating this cycle, code and specs stay perpetually synchronized and up-to-date.

7.5. Future Features and Revisions Use the Same Loop

While the above describes the initial app launch, all subsequent feature additions and revisions follow the exact same cycle. This is where Code-First Spec-Backfill Development truly shines.

When adding features or making improvements, start with code:

Modify the code to match (desired changes).

Enter fullscreen mode Exit fullscreen mode

If the fix looks good, request tests:

Read this change and write test code for it.

Enter fullscreen mode Exit fullscreen mode

If the implementation is correct, update the spec:

Read this change and update @spec.yaml accordingly.

Enter fullscreen mode Exit fullscreen mode

Commit and you're done.

This way, you try things in code first, then reflect them in specs if satisfied—practicing Code-First Spec-Backfill Development repeatedly. For those who found spec-writing tedious before, this framework naturally keeps specs updated with every feature addition or fix. There's no separate "manually update spec" task anymore. Every change flows into both code and spec simultaneously, keeping them always aligned.

8. Applying to Team Development

For multi-person teams, document which prompts to use in your README.md:

## AI Instruction Templates

### For Feature Addition/Fixes
Modify the code to match (desired changes).

### For Test Code Creation
Read this change and write test code for it.

### For Spec Updates (when implementation exceeds spec.yaml)
Read this change and update @spec.yaml accordingly.

### For Discrepancy Checks
@spec.yaml
Report any discrepancies between the current code and the spec.

Enter fullscreen mode Exit fullscreen mode

This ensures every team member follows the same procedure at the same quality level. Whoever's working on what, you'll always maintain current specs and traceable code. This methodology's greatest benefit is consistency across all phases—new development, features, and fixes. Overall project quality improves dramatically.

9. Conclusion

Here's a summary:

  • Spec-Driven Development (SDD): Building new apps with AI guided by spec.yaml, using a "spec → code" flow
  • Code-First Spec-Backfill Development (CFSD): A practical approach for features/fixes using "code → spec"
  • YAML specs should detail purpose, languages, frontend/backend, database, APIs, class design, function names, testing strategy, etc.
  • For existing apps, have AI analyze code and generate a spec.yaml foundation
  • Set up with Git/GitHub + VS Code + Claude Code (or Copilot)
  • Initial phase: Use SDD to build a solid foundation
  • Operations/maintenance phase: Use CFSD in a "code → test → spec" cycle
  • Leverage spec.yaml to track who changed what, when, and at what functional level
  • Summarize prompts in README so all team members develop at consistent quality
  • By combining both approaches, specs automatically follow code changes, staying perpetually current.

Closing Thoughts

Many engineers privately sense something: we understand the importance of specs and keeping them current. Yet, in practice, maintaining specs is tedious and joyless. The reason is simple: code's behavior is the truth; specs merely retroactively describe it. Intuitively, we feel specs should conform to code, not vice versa. Traditional development methods never resolved this paradox.

Additionally, team development surfaced another issue: "Without X, we can't understand the whole picture"—a concerning dependency. Specs are incomplete or nobody truly understands them. These problems plague organizations regardless of size.

Our development landscape shifted dramatically. AI now generates precise, voluminous code quickly. Human roles necessarily evolved. Projects once requiring large engineer teams can now proceed with smaller groups plus AI. Blindly following existing methodologies won't keep pace with this rapid change.

That's not to say traditional methods are wrong—decades of practice accumulated vast knowledge and case studies. But the "optimal method now" and "optimal method for the future" may differ.

Interestingly, agile development initially faced similar criticism ("excessive," "lacks discipline"). Today it's mainstream, spawning variants like Scrum and Kanban. I expect similar evolution for Code-First Spec-Backfill Development.

Some readers will embrace "Code-First Spec-Backfill Development" enthusiastically; others will skeptically object. That's natural—this methodology has weak points and room for improvement. No perfect development method exists.

If this article helps engineers facing the challenges described earlier, I'd be delighted.

10. References