TUTORIAL 03

Build a Feature with TDD

Implement a user story end-to-end: plan, branch, test-drive, review, and accept

โฑ ~25 min ยท Hands-on

What You'll Learn

You'll take a single user story from sprint backlog to accepted โ€” using test-driven development, continuous integration, structured commits, pull requests, and formal acceptance.

/agile-code-branch /agile-story-plan /agile-code-tdd /agile-code-ci /agile-code-commit /agile-code-pr /agile-code-pr-review /agile-code-merge /agile-story-dod /agile-story-accept
Prerequisites

The Scenario

Sprint 1 is planned. You're starting with US-001: Add a new task. The acceptance criteria are clear: users can create tasks with a title and optional description, validation rejects empty titles, and each task gets a unique ID. You'll build this feature using the full TDD workflow.

Step 1: Create the Feature Branch with /agile-code-branch

Before anything else, create a properly named feature branch linked to the story:

/agile-code-branch
๐ŸŒฟ Branch created: feature/US-001-add-task

Source: main
Convention: feature/[STORY-ID]-[short-description]
Story link: US-001 โ€” Add a new task

Switched to branch 'feature/US-001-add-task'
Why use /agile-code-branch?

Instead of manually running git checkout -b, /agile-code-branch automatically applies your project's branch naming convention (detected during setup), links the branch to the story, and ensures you're branching from the correct base branch. No typos, no convention drift.

Step 2: Plan the Story with /agile-story-plan

Before writing any code, break the story into implementation phases:

/agile-story-plan
๐Ÿ“‹ Implementation Plan: US-001 โ€” Add a new task
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Phase 1: Data Model + Repository
  โ€ข Create Task model (id, title, description, status, createdAt)
  โ€ข Create TaskRepository with insert() and findById()
  โ€ข Migration: create tasks table
  Files: src/models/task.js, src/repositories/task-repository.js, migrations/

Phase 2: Service Layer
  โ€ข Create TaskService with createTask()
  โ€ข Input validation (title required, max 200 chars)
  โ€ข Business logic: set status="pending", generate timestamp
  Files: src/services/task-service.js, src/validators/task-validator.js

Phase 3: API Endpoint
  โ€ข POST /api/v1/tasks endpoint
  โ€ข Request/response serialization
  โ€ข Error handling integration
  Files: src/controllers/task-controller.js, src/routes/task-routes.js

Estimated phases: 3
Confidence: 9/10
Risk: Low โ€” standard CRUD, well-known patterns

โœ… Plan approved. Ready to implement.
Why plan confidence matters

The plan must reach 9/10 confidence before implementation starts. If confidence is below 9, the AI re-plans with more context โ€” it never proceeds on hope. Here, 9/10 means the scope is clear, the patterns are known, and there are no unknowns.

Step 3: Test-Driven Development with /agile-code-tdd

This is the heart of the implementation. Run TDD for each phase:

/agile-code-tdd

The AI follows the Red-Green-Refactor cycle:

๐Ÿ”ด RED โ€” Writing failing test...

// test/services/task-service.test.js
describe('TaskService', () => {
  describe('createTask', () => {
    it('should_create_task_when_valid_input', async () => {
      const input = { title: 'Buy groceries', description: 'Milk, eggs, bread' };
      const task = await taskService.createTask(input);

      expect(task.id).toBeDefined();
      expect(task.title).toBe('Buy groceries');
      expect(task.description).toBe('Milk, eggs, bread');
      expect(task.status).toBe('pending');
      expect(task.createdAt).toBeInstanceOf(Date);
    });

    it('should_reject_when_title_missing', async () => {
      await expect(taskService.createTask({ description: 'no title' }))
        .rejects.toThrow('Title is required');
    });

    it('should_reject_when_title_empty_string', async () => {
      await expect(taskService.createTask({ title: '' }))
        .rejects.toThrow('Title is required');
    });
  });
});

Running tests...
  โœ— should_create_task_when_valid_input โ€” FAILED (TaskService not found)
  โœ— should_reject_when_title_missing โ€” FAILED (TaskService not found)
  โœ— should_reject_when_title_empty_string โ€” FAILED (TaskService not found)

3 failing โ† This is expected. Tests define what we want to build.
๐ŸŸข GREEN โ€” Implementing to make tests pass...

// src/models/task.js
class Task {
  constructor({ id, title, description, status, createdAt }) {
    this.id = id;
    this.title = title;
    this.description = description || null;
    this.status = status || 'pending';
    this.createdAt = createdAt || new Date();
  }
}

// src/validators/task-validator.js
function validateTaskInput(data) {
  if (!data.title || data.title.trim() === '') {
    throw new AppError('Title is required', 400);
  }
  if (data.title.length > 200) {
    throw new AppError('Title must be 200 characters or less', 400);
  }
}

// src/services/task-service.js
class TaskService {
  constructor(taskRepository) {
    this.taskRepository = taskRepository;
  }

  async createTask(data) {
    validateTaskInput(data);
    const task = new Task({ title: data.title, description: data.description });
    return this.taskRepository.insert(task);
  }
}

Running tests...
  โœ“ should_create_task_when_valid_input โ€” PASSED
  โœ“ should_reject_when_title_missing โ€” PASSED
  โœ“ should_reject_when_title_empty_string โ€” PASSED

3 passing โ† All green!
๐Ÿ”ต REFACTOR โ€” Improving structure without changing behavior...

Refactoring: Extracted validation logic to TaskValidator class
  โ€ข Moved from inline function to class with static methods
  โ€ข Added validate() entry point for extensibility
  โ€ข No test changes needed โ€” all 3 still pass โœ“

Running tests after refactor...
  โœ“ should_create_task_when_valid_input โ€” PASSED
  โœ“ should_reject_when_title_missing โ€” PASSED
  โœ“ should_reject_when_title_empty_string โ€” PASSED

โœ… TDD cycle complete for Phase 1+2. 3 tests passing.
The TDD rhythm

RED: Write a test that describes what you want. It fails because the code doesn't exist yet. GREEN: Write the minimum code to make the test pass. REFACTOR: Improve the code structure while keeping all tests green. This cycle repeats for every behavior.

Step 4: Run CI Checks with /agile-code-ci

Before committing, verify everything passes:

/agile-code-ci
๐Ÿ”„ Running CI pipeline locally...

Build:
  โœ… Compilation successful (0 errors, 0 warnings)

Tests:
  โœ… 12 tests passing (3 unit + 5 integration + 4 edge cases)
  โœ… Coverage: 94% lines, 100% branches (threshold: 80%)

Lint:
  โœ… ESLint: 0 errors, 0 warnings
  โœ… Prettier: all files formatted

Security:
  โœ… npm audit: 0 vulnerabilities
  โœ… No hardcoded secrets detected

โœ… All CI checks pass. Safe to commit.
Never skip CI

Always run /agile-code-ci before committing. Catching a lint error or failing test here saves a failed CI build, a broken PR, and wasted review time. The command runs the same checks your CI pipeline runs.

Step 5: Commit with /agile-code-commit

Create a conventional commit with a clear message:

/agile-code-commit
๐Ÿ“ฆ Commit created:

feat(tasks): add task creation with validation

- Task model with id, title, description, status, createdAt
- TaskService.createTask() with input validation
- TaskRepository.insert() with PostgreSQL integration
- POST /api/v1/tasks endpoint
- 12 tests covering happy path and edge cases

Refs: US-001

Step 6: Create a Pull Request with /agile-code-pr

Open a PR linked to the story:

/agile-code-pr
๐Ÿ”€ Pull Request created: #14

Title: feat(tasks): add task creation with validation
Branch: feature/US-001-add-task โ†’ main
Story: US-001

Description:
  Implements the "Add a new task" user story. Users can create tasks
  via POST /api/v1/tasks with title (required) and description (optional).
  Input validation rejects empty/missing titles.

Changes:
  +6 files, 247 additions
  src/models/task.js
  src/validators/task-validator.js
  src/services/task-service.js
  src/repositories/task-repository.js
  src/controllers/task-controller.js
  src/routes/task-routes.js

Test coverage: 94%
CI status: โœ… All checks passing

Reviewers: @lead (auto-assigned)

Step 7: Code Review with /agile-code-pr-review

The reviewer examines the PR and provides structured feedback:

/agile-code-pr-review
๐Ÿ‘€ Code Review โ€” PR #14
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Findings:
  ๐ŸŸก S2 โ€” task-service.js:15 โ€” Parameter naming
     Rename `data` to `taskPayload` for clarity
     Suggestion: async createTask(taskPayload) { ... }

  ๐Ÿ”ต S3 โ€” task-controller.js:8 โ€” Documentation
     Add JSDoc to the controller method describing
     request body shape and response format

  โœ… Architecture: Follows 3-layer pattern correctly
  โœ… Tests: Comprehensive, covers edge cases
  โœ… Security: No injection risks, input validated
  โœ… Conventions: Naming, imports, error handling all match project style

Verdict: CHANGES REQUESTED (2 minor items)

---

After fixes applied:

๐Ÿ‘€ Re-review โ€” PR #14
  โœ… S2 fix verified โ€” parameter renamed to taskPayload
  โœ… S3 fix verified โ€” JSDoc added

Verdict: โœ… APPROVED by @lead
Severity levels

๐Ÿ”ด S1: Blocker โ€” must fix (security, data loss, crashes). ๐ŸŸก S2: Should fix โ€” code quality issues. ๐Ÿ”ต S3: Nice to have โ€” style, documentation. S1 always blocks merge. S2 and S3 are at the author's discretion but should be addressed.

Step 8: Merge the Branch with /agile-code-merge

With the PR approved, merge the feature branch into main:

/agile-code-merge
๐Ÿ”€ Merge โ€” PR #14
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Pre-merge checks:
  โœ… PR approved by @lead
  โœ… CI passing on branch
  โœ… No merge conflicts
  โœ… Branch up to date with main

Merging: feature/US-001-add-task โ†’ main
Strategy: squash merge (project convention)

Merge complete.
Branch feature/US-001-add-task deleted (was 3a7f2c1).

โœ… PR #14 merged to main.
Why use /agile-code-merge?

/agile-code-merge verifies all pre-merge conditions (approval, CI status, conflicts), applies the merge strategy configured for your project, and cleans up the feature branch. It prevents merging a PR that hasn't passed review or CI.

Step 9: Definition of Done with /agile-story-dod

Walk through every DoD criterion to confirm the story is truly done:

/agile-story-dod
๐Ÿ“ Definition of Done Checklist โ€” US-001
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

  โœ… All acceptance criteria met (3/3 scenarios pass)
  โœ… Code reviewed and approved by @lead
  โœ… All tests passing (12/12)
  โœ… Test coverage above threshold (94% > 80%)
  โœ… No lint errors or warnings
  โœ… No security vulnerabilities
  โœ… API documentation updated
  โœ… Conventional commit message
  โœ… PR linked to story US-001
  โœ… Branch merged to main

DoD result: โœ… ALL CRITERIA MET (10/10)

Step 10: Accept the Story with /agile-story-accept

The product owner verifies each acceptance criterion against the working software:

/agile-story-accept
๐ŸŽฏ Story Acceptance โ€” US-001: Add a new task
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

AC 1: Given valid title and description โ†’ task created with status "pending"
  Test: POST /api/v1/tasks {"title":"Buy groceries","description":"Milk, eggs"}
  Result: 201 Created, status="pending", ID assigned
  โœ… PASS

AC 2: Given missing title โ†’ 400 error returned
  Test: POST /api/v1/tasks {"description":"no title"}
  Result: 400 Bad Request, message="Title is required"
  โœ… PASS

AC 3: Given empty string title โ†’ 400 error returned
  Test: POST /api/v1/tasks {"title":""}
  Result: 400 Bad Request, message="Title is required"
  โœ… PASS

Verdict: โœ… ACCEPTED by @po
Story status: US-001 โ†’ DONE
The complete feature flow

You've just taken a story from backlog to accepted using 10 commands. Every step has a clear purpose: create the branch, plan the work, prove it works (TDD), verify quality (CI + review), merge cleanly, confirm completeness (DoD), and get sign-off (accept).

The Feature Development Flow

๐ŸŒฟ
Branch /agile-code-branch
๐Ÿ“‹
Plan /agile-story-plan
๐Ÿงช
TDD /agile-code-tdd
โœ…
CI /agile-code-ci
๐Ÿ“ฆ
Commit /agile-code-commit
๐Ÿ”€
PR /agile-code-pr
๐Ÿ‘€
Review /agile-code-pr-review
๐Ÿ”€
Merge /agile-code-merge
๐Ÿ“
DoD /agile-story-dod
๐ŸŽฏ
Accept /agile-story-accept

Knowledge Check

In TDD, what do you write FIRST?

What You Learned