TaskMaster Repository Structure

Level: Beginner Pre-reading: 00 · Demo Overview

This document describes the taskmaster GitHub repository — the demo codebase the AI agent will read, modify, and commit to. It is a minimal but realistic multi-module project.


Repository Layout

taskmaster/
├── pom.xml                         ← Root Maven POM (multi-module parent)
├── .github/
│   └── pull_request_template.md   ← PR template for agent-generated PRs
├── taskmaster-core/                ← Module 1: Domain layer (Spring Boot library)
│   ├── pom.xml
│   └── src/
│       ├── main/java/com/demo/taskmaster/core/
│       │   ├── model/
│       │   │   └── Task.java
│       │   ├── repository/
│       │   │   └── TaskRepository.java
│       │   └── service/
│       │       └── TaskService.java
│       └── test/java/com/demo/taskmaster/core/
│           └── service/
│               └── TaskServiceTest.java
├── taskmaster-api/                 ← Module 2: REST API layer (Spring Boot web app)
│   ├── pom.xml
│   └── src/
│       ├── main/java/com/demo/taskmaster/api/
│       │   ├── TaskmasterApiApplication.java
│       │   ├── controller/
│       │   │   └── TaskController.java
│       │   └── dto/
│       │       ├── TaskRequest.java
│       │       └── TaskResponse.java
│       └── test/java/com/demo/taskmaster/api/
│           └── controller/
│               └── TaskControllerTest.java
└── taskmaster-e2e/                 ← Module 3: Playwright E2E tests (Node.js)
    ├── package.json
    ├── playwright.config.ts
    └── tests/
        ├── task-create.spec.ts
        ├── task-update.spec.ts
        └── task-list.spec.ts

Module 1: taskmaster-core

The domain layer — entities, repositories, and service logic. This is where the bug (TASK-101) and story (TASK-102) changes land.

Task.java — Entity

package com.demo.taskmaster.core.model;

import jakarta.persistence.*;
import java.time.Instant;

@Entity
@Table(name = "tasks")
public class Task {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String title;

    private String description;

    private String assignee;          // nullable — root cause of TASK-101

    // TASK-102 will add:
    // private LocalDate dueDate;

    @Column(nullable = false, updatable = false)
    private Instant createdAt = Instant.now();

    // --- getters / setters ---
    public Long getId() { return id; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public String getDescription() { return description; }
    public void setDescription(String description) { this.description = description; }
    public String getAssignee() { return assignee; }
    public void setAssignee(String assignee) { this.assignee = assignee; }
    public Instant getCreatedAt() { return createdAt; }
}

TaskService.java — Service (contains the TASK-101 bug)

package com.demo.taskmaster.core.service;

import com.demo.taskmaster.core.model.Task;
import com.demo.taskmaster.core.repository.TaskRepository;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class TaskService {

    private final TaskRepository repository;

    public TaskService(TaskRepository repository) {
        this.repository = repository;
    }

    public List<Task> getAllTasks() {
        return repository.findAll();
    }

    public Task createTask(Task task) {
        return repository.save(task);
    }

    // BUG: TASK-101 — throws NullPointerException when assignee is null
    public String getSummary(Task task) {
        return task.getTitle() + " assigned to " + task.getAssignee().toUpperCase();
        //                                                              ^^^^^^^^^^^
        //                                          NullPointerException if assignee is null
    }
}

TaskRepository.java

package com.demo.taskmaster.core.repository;

import com.demo.taskmaster.core.model.Task;
import org.springframework.data.jpa.repository.JpaRepository;

public interface TaskRepository extends JpaRepository<Task, Long> {}

TaskServiceTest.java — Existing unit tests

package com.demo.taskmaster.core.service;

import com.demo.taskmaster.core.model.Task;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;

class TaskServiceTest {

    @Test
    void getSummary_returnsFormattedString() {
        Task task = new Task();
        task.setTitle("Fix login");
        task.setAssignee("alice");
        TaskService service = new TaskService(null);
        assertThat(service.getSummary(task)).isEqualTo("Fix login assigned to ALICE");
    }

    // TASK-101 fix will add:
    // @Test
    // void getSummary_whenAssigneeIsNull_returnsUnassigned() { ... }
}

Module 2: taskmaster-api

The REST API layer. Calls into taskmaster-core. TASK-102 adds the dueDate field to the DTO and controller.

TaskController.java

package com.demo.taskmaster.api.controller;

import com.demo.taskmaster.api.dto.TaskRequest;
import com.demo.taskmaster.api.dto.TaskResponse;
import com.demo.taskmaster.core.model.Task;
import com.demo.taskmaster.core.service.TaskService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/tasks")
public class TaskController {

    private final TaskService taskService;

    public TaskController(TaskService taskService) {
        this.taskService = taskService;
    }

    @GetMapping
    public List<TaskResponse> listTasks() {
        return taskService.getAllTasks().stream()
                .map(this::toResponse)
                .collect(Collectors.toList());
    }

    @PostMapping
    public ResponseEntity<TaskResponse> createTask(@RequestBody TaskRequest request) {
        Task task = new Task();
        task.setTitle(request.getTitle());
        task.setDescription(request.getDescription());
        task.setAssignee(request.getAssignee());
        // TASK-102 will add: task.setDueDate(request.getDueDate());
        return ResponseEntity.ok(toResponse(taskService.createTask(task)));
    }

    private TaskResponse toResponse(Task task) {
        TaskResponse resp = new TaskResponse();
        resp.setId(task.getId());
        resp.setTitle(task.getTitle());
        resp.setAssignee(task.getAssignee());
        // TASK-102 will add: resp.setDueDate(task.getDueDate());
        return resp;
    }
}

TaskRequest.java / TaskResponse.java

// TaskRequest.java — TASK-102 will add LocalDate dueDate field
public class TaskRequest {
    private String title;
    private String description;
    private String assignee;
    // getters / setters omitted for brevity
}

// TaskResponse.java — TASK-102 will add LocalDate dueDate field
public class TaskResponse {
    private Long id;
    private String title;
    private String assignee;
    // getters / setters omitted for brevity
}

Module 3: taskmaster-e2e

Playwright smoke tests running against the live API.

playwright.config.ts

import { defineConfig } from '@playwright/test';

export default defineConfig({
    testDir: './tests',
    use: {
        baseURL: process.env.BASE_URL ?? 'http://localhost:8080',
    },
    reporter: [['html', { outputFolder: 'playwright-report' }], ['json', { outputFile: 'results.json' }]],
});

task-create.spec.ts — Smoke test (TASK-102 extends this)

import { test, expect } from '@playwright/test';

test.describe('Task Creation API', () => {

    test('creates a task without assignee', async ({ request }) => {
        const res = await request.post('/api/tasks', {
            data: { title: 'Test Task', description: 'A simple test task' }
        });
        expect(res.status()).toBe(200);
        const body = await res.json();
        expect(body.id).toBeDefined();
        expect(body.title).toBe('Test Task');
    });

    test('creates a task with assignee', async ({ request }) => {
        const res = await request.post('/api/tasks', {
            data: { title: 'Assigned Task', assignee: 'alice' }
        });
        expect(res.status()).toBe(200);
    });

    // TASK-102 will add:
    // test('creates a task with due date', async ({ request }) => { ... });
});

Root pom.xml — Multi-Module Parent

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.demo</groupId>
    <artifactId>taskmaster-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>taskmaster-core</module>
        <module>taskmaster-api</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
    </parent>
</project>

PR Template (.github/pull_request_template.md)

Agent-generated PRs automatically populate this template:

## Summary
<!-- AI-generated PR — review carefully before merging -->

**JIRA Ticket:** [TASK-XXX](https://yourname.atlassian.net/browse/TASK-XXX)
**Ticket Type:** Bug / Story

## Changes Made
<!-- Modules changed and why -->

## Root Cause (Bugs only)
<!-- What caused the bug -->

## Acceptance Criteria Coverage
<!-- Table mapping ACs to code/test locations -->

## Test Coverage
- [ ] Unit tests added/updated
- [ ] E2E test added/updated (if API contract changed)
- [ ] All existing tests pass

## ⚠️ AI-Generated Notice
This PR was created by the TaskMaster AI agent. A human engineer has approved the diff via the HITL gate before this PR was opened. Please review carefully.

How to Bootstrap the Repository

# Create the repo locally
mkdir taskmaster && cd taskmaster
git init

# Create directory structure
mkdir -p taskmaster-core/src/{main,test}/java/com/demo/taskmaster/core/{model,repository,service}
mkdir -p taskmaster-api/src/{main,test}/java/com/demo/taskmaster/api/{controller,dto}
mkdir -p taskmaster-e2e/tests
mkdir -p .github

# Copy all files shown above, then:
git add .
git commit -m "chore: initial TaskMaster project scaffold"

# Push to GitHub
gh repo create taskmaster --private --source=. --push

Why is the bug intentionally in the service layer and not the controller?

The agent must demonstrate it can trace a NullPointerException from a test failure back to the correct class, even when the error originates several call layers away from the API entry point. A service-layer bug tests the agent's code-reading depth, not just surface-level controller patching.

Why is taskmaster-e2e a Node.js module in a Java Maven project?

Real engineering teams often use different technology stacks for different concerns. The agent must recognise that taskmaster-e2e is a Playwright (Node.js) project and generate TypeScript test code — not Java — when the API contract changes.

What is the minimum change the agent should make for TASK-101?

Exactly two files: (1) add a null-check in TaskService.getSummary(), and (2) add a new @Test case in TaskServiceTest.java. The agent should NOT touch TaskController.java or any API layer files.