End-to-End Flow Walkthrough
Level: Intermediate Pre-reading: 06 · LangGraph Agent · 09 · HITL Design
This document provides complete sequence diagrams and expected outputs for both demo tickets — the bug fix (TASK-101) and the feature story (TASK-102).
TASK-101: Bug Fix Flow (NullPointerException)
Full Sequence Diagram
sequenceDiagram
participant U as User
participant CE as Chat Engine
participant LG as LangGraph Agent
participant JM as JIRA MCP
participant RAG as pgvector RAG
participant LLM as AWS Bedrock Claude
participant GM as GitHub MCP
participant JIRA as JIRA Cloud
participant GH as GitHub
U->>CE: "TASK-101" (WebSocket)
CE->>LG: graph.stream(initial_state)
Note over LG: Node: fetch_ticket
LG->>JM: get_ticket("TASK-101")
JM->>JIRA: GET /rest/api/3/issue/TASK-101
JIRA-->>JM: {summary, description, type=Bug}
JM-->>LG: ticket data
LG-->>CE: ✅ Fetched TASK-101
Note over LG: Node: classify_ticket
LG->>LLM: classify_ticket prompt
LLM-->>LG: {type: Bug, complexity: low}
LG-->>CE: 📋 Bug, low complexity
Note over LG: Node: identify_modules
LG->>LLM: which modules?
LLM-->>LG: ["taskmaster-core"]
LG-->>CE: 🎯 taskmaster-core
Note over LG: Node: retrieve_context
LG->>RAG: similarity search (top-5, module=taskmaster-core)
RAG-->>LG: [TaskService.java, Task.java, TaskServiceTest.java, ...]
LG-->>CE: 🔍 5 chunks retrieved
Note over LG: Node: generate_code_changes
LG->>LLM: fix the bug (with RAG context)
LLM-->>LG: {root_cause: "...", changes: [TaskService.java]}
LG-->>CE: 💡 1 file change
Note over LG: Node: generate_tests
LG->>LLM: write test for null assignee
LLM-->>LG: {tests: [TaskServiceTest.java]}
LG-->>CE: 🧪 1 test file
Note over LG: Node: prepare_diff_summary
LG-->>CE: diff summary (2 files, ~12 lines)
Note over LG: Node: human_review_gate (INTERRUPT)
LG-->>CE: ⏸ awaiting_approval (diff shown to user)
CE-->>U: {"type":"awaiting_approval","diff_summary":"..."}
U->>CE: POST /threads/{id}/resume {"response":"approve"}
CE->>LG: Command(resume="approve")
Note over LG: Node: apply_changes
LG->>GM: create_branch("ai/TASK-101-fix-npe-taskservice")
GM->>GH: POST /repos/.../git/refs
GH-->>GM: branch created
LG->>GM: commit_file(TaskService.java, new_content)
GM->>GH: PUT /repos/.../contents/...TaskService.java
LG->>GM: commit_file(TaskServiceTest.java, new_content)
GM->>GH: PUT /repos/.../contents/...TaskServiceTest.java
LG-->>CE: 📤 2 files pushed
Note over LG: Node: create_pull_request
LG->>GM: create_pr(branch, title, body)
GM->>GH: POST /repos/.../pulls
GH-->>GM: {pr_url: "https://github.com/.../pull/17"}
LG-->>CE: 🚀 PR #17 created
Note over LG: Node: post_jira_comment
LG->>JM: post_comment("TASK-101", "🤖 PR created: ...")
JM->>JIRA: POST /rest/api/3/issue/TASK-101/comment
LG-->>CE: 💬 JIRA updated
CE-->>U: {"type":"done","pr_url":"https://..."}
Expected Code Changes for TASK-101
taskmaster-core/src/main/java/com/demo/taskmaster/core/service/TaskService.java
- public String getSummary(Task task) {
- return task.getTitle() + " assigned to " + task.getAssignee().toUpperCase();
- }
+ public String getSummary(Task task) {
+ String assignee = task.getAssignee() != null
+ ? task.getAssignee().toUpperCase()
+ : "Unassigned";
+ return task.getTitle() + " assigned to " + assignee;
+ }
taskmaster-core/src/test/java/com/demo/taskmaster/core/service/TaskServiceTest.java
+ @Test
+ void getSummary_whenAssigneeIsNull_returnsUnassigned() {
+ Task task = new Task();
+ task.setTitle("Fix login");
+ // assignee deliberately not set
+ TaskService service = new TaskService(null);
+ assertThat(service.getSummary(task))
+ .isEqualTo("Fix login assigned to Unassigned");
+ }
Expected PR Output
## Summary
> ⚠️ AI-Generated PR — review carefully before merging.
**JIRA Ticket:** [TASK-101](https://yourname.atlassian.net/browse/TASK-101)
**Ticket Type:** Bug
## Root Cause
`TaskService.getSummary()` calls `task.getAssignee().toUpperCase()` directly
on line 23. When a `Task` has no assignee set, `getAssignee()` returns `null`,
causing a `NullPointerException`. The fix adds a null-check that returns
`"Unassigned"` as the fallback string.
## Changes Made
- `TaskService.java` — added null-check guard in `getSummary()`
- `TaskServiceTest.java` — added test case for null assignee
## Test Coverage
- [x] Existing test `getSummary_returnsFormattedString` still passes
- [x] New test `getSummary_whenAssigneeIsNull_returnsUnassigned` added
- [x] No API contract changes — taskmaster-api untouched
TASK-102: Story Flow (Add dueDate Field)
Full Sequence Diagram
sequenceDiagram
participant U as User
participant CE as Chat Engine
participant LG as LangGraph
participant RAG as pgvector
participant LLM as Bedrock Claude
participant GM as GitHub MCP
participant JM as JIRA MCP
U->>CE: "TASK-102"
CE->>LG: graph.stream(initial_state)
Note over LG: fetch · classify · identify_modules
LG-->>CE: Story · complexity:medium
LG-->>CE: Modules: taskmaster-core + taskmaster-api + taskmaster-e2e
Note over LG: retrieve_context
LG->>RAG: search (top-10, all 3 modules)
RAG-->>LG: Task.java, TaskController.java, TaskRequest/Response.java, task-create.spec.ts, ...
Note over LG: generate_code_changes (6 files)
LG->>LLM: implement dueDate (AC1-AC4)
LLM-->>LG: changes to Task.java, TaskRequest, TaskResponse, TaskController
Note over LG: generate_tests (AC5)
LG->>LLM: write Playwright E2E test
LLM-->>LG: updated task-create.spec.ts
Note over LG: INTERRUPT — large diff + multi-module
LG-->>CE: ⏸ diff (6 files, ~180 lines, 3 modules)
CE-->>U: show diff + AC coverage table
U->>CE: "The dueDate should reject past dates"
CE->>LG: Command(resume="The dueDate should reject...")
Note over LG: iteration 2: generate_code_changes
LG->>LLM: revise — add @FutureOrPresent validation
LLM-->>LG: updated Task.java + test for 400 response
Note over LG: INTERRUPT again (iteration 2)
LG-->>CE: revised diff
CE-->>U: show revised diff
U->>CE: "approve"
CE->>LG: Command(resume="approve")
Note over LG: apply_changes — 6 file commits
LG->>GM: create_branch("ai/TASK-102-add-duedate...")
loop 6 files
LG->>GM: commit_file(file, content)
end
Note over LG: create_pull_request
LG->>GM: create_pr(branch, title, body with AC table)
Note over LG: post_jira_comment
LG->>JM: post_comment("TASK-102", "PR #18 created")
CE-->>U: done, PR #18
Expected Code Changes for TASK-102
Task.java (core — entity field)
+import jakarta.validation.constraints.FutureOrPresent;
+import java.time.LocalDate;
public class Task {
// ... existing fields ...
+
+ @FutureOrPresent
+ private LocalDate dueDate;
+
+ public LocalDate getDueDate() { return dueDate; }
+ public void setDueDate(LocalDate dueDate) { this.dueDate = dueDate; }
}
TaskRequest.java (api — DTO)
+import java.time.LocalDate;
+
public class TaskRequest {
private String title;
private String description;
private String assignee;
+ private LocalDate dueDate;
+ public LocalDate getDueDate() { return dueDate; }
+ public void setDueDate(LocalDate dueDate) { this.dueDate = dueDate; }
}
TaskResponse.java (api — DTO)
+import java.time.LocalDate;
+
public class TaskResponse {
private Long id;
private String title;
private String assignee;
+ private LocalDate dueDate;
+ public LocalDate getDueDate() { return dueDate; }
+ public void setDueDate(LocalDate dueDate) { this.dueDate = dueDate; }
}
TaskController.java (api — mapping)
@PostMapping
public ResponseEntity<TaskResponse> createTask(@RequestBody @Valid TaskRequest request) {
Task task = new Task();
task.setTitle(request.getTitle());
task.setDescription(request.getDescription());
task.setAssignee(request.getAssignee());
+ 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());
+ resp.setDueDate(task.getDueDate());
return resp;
}
task-create.spec.ts (e2e — new test)
+ test('creates a task with dueDate and retrieves it', async ({ request }) => {
+ const tomorrow = new Date();
+ tomorrow.setDate(tomorrow.getDate() + 1);
+ const dueDateStr = tomorrow.toISOString().split('T')[0]; // YYYY-MM-DD
+
+ const createRes = await request.post('/api/tasks', {
+ data: { title: 'Task with due date', dueDate: dueDateStr }
+ });
+ expect(createRes.status()).toBe(200);
+ const created = await createRes.json();
+ expect(created.dueDate).toBe(dueDateStr);
+
+ const listRes = await request.get('/api/tasks');
+ const tasks = await listRes.json();
+ const found = tasks.find((t: any) => t.id === created.id);
+ expect(found.dueDate).toBe(dueDateStr);
+ });
+
+ test('rejects a task with a past dueDate', async ({ request }) => {
+ const yesterday = new Date();
+ yesterday.setDate(yesterday.getDate() - 1);
+ const pastDateStr = yesterday.toISOString().split('T')[0];
+
+ const res = await request.post('/api/tasks', {
+ data: { title: 'Past due task', dueDate: pastDateStr }
+ });
+ expect(res.status()).toBe(400);
+ });
Timing Breakdown
| Phase | TASK-101 (Bug) | TASK-102 (Story) |
|---|---|---|
| Ticket fetch + classify | ~10s | ~10s |
| Module identification | ~8s | ~8s |
| RAG retrieval | ~5s | ~8s |
| Code generation (LLM call) | ~25s | ~45s |
| Test generation | ~20s | ~30s |
| HITL gate (user response) | ~30s (user time) | ~2 min (user reads diff) |
| Revision round (if any) | — | ~50s |
| Branch + commits | ~15s | ~30s |
| PR creation + JIRA comment | ~5s | ~5s |
| Total (excl. user time) | ~1.5 min | ~3 min |
| Total (incl. user review) | ~3–5 min | ~7–12 min |
Why does TASK-102 require three modules but TASK-101 only one?
TASK-101 is a bug entirely within the service layer — only TaskService.getSummary() is broken, and only taskmaster-core contains that code. TASK-102 adds a new field that propagates from the database entity through the API layer to the E2E test — every layer of the stack is touched, which is expected for a data model change.
How does the agent decide the PR title format?
The title template is [{TICKET_KEY}] {ticket_summary} — matching the branch protection rule patterns at most companies. This makes PRs easy to find from JIRA and vice versa.
What if CI fails after the PR is created?
The agent's job ends at PR creation. CI feedback (red checks on the PR) is visible in GitHub. In a future iteration, the Playwright CI failure webhook would trigger the agent again with the CI error context — see 07.02 · Playwright RCA for that flow.