Functional Scope (In-Scope)
- Backlog & Sprint Allocations: Support Project, Sprint, and Task hierarchical trees.
- State Machine Transitions (State Pattern): Enforce valid task status migrations using distinct TaskState pattern entities.
- Sprint Velocity warnings: Calculate sprint story point loads, firing warnings if capacity limits are breached.
- Observer Updates: Enable subscribers to receive log updates upon task state transitions.
- Polymorphic Task Types: Factory resolution for Feature and Bug tasks.
Explicit Boundaries (Out-of-Scope)
- No Automatic Agile Release Planning AI: Bypasses sophisticated auto-balancing resources or burn-down predictions.
- No Rich Document Comments editor: File system lacks complex collaborative text threads, file attachment uploads, or Markdown styling.
Clean reference designs detailing sprint capacities in Java and Python:
// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
enum TaskStatus { TODO, IN_PROGRESS, REVIEW, DONE }
enum TaskType { BUG, FEATURE, STORY }
interface TaskState {
boolean transitionTo(Task task, TaskStatus nextStatus);
TaskStatus getStatus();
}
class TodoState implements TaskState {
@Override
public boolean transitionTo(Task task, TaskStatus nextStatus) {
if (nextStatus == TaskStatus.IN_PROGRESS) {
task.setState(new InProgressState());
return true;
}
return false;
}
@Override
public TaskStatus getStatus() { return TaskStatus.TODO; }
}
class InProgressState implements TaskState {
@Override
public boolean transitionTo(Task task, TaskStatus nextStatus) {
if (nextStatus == TaskStatus.REVIEW) {
task.setState(new ReviewState());
return true;
} else if (nextStatus == TaskStatus.TODO) {
task.setState(new TodoState());
return true;
}
return false;
}
@Override
public TaskStatus getStatus() { return TaskStatus.IN_PROGRESS; }
}
class ReviewState implements TaskState {
@Override
public boolean transitionTo(Task task, TaskStatus nextStatus) {
if (nextStatus == TaskStatus.DONE) {
task.setState(new DoneState());
return true;
} else if (nextStatus == TaskStatus.IN_PROGRESS) {
task.setState(new InProgressState());
return true;
}
return false;
}
@Override
public TaskStatus getStatus() { return TaskStatus.REVIEW; }
}
class DoneState implements TaskState {
@Override
public boolean transitionTo(Task task, TaskStatus nextStatus) {
return false; // Done is terminal
}
@Override
public TaskStatus getStatus() { return TaskStatus.DONE; }
}
interface TaskObserver {
void onTaskStatusChanged(String taskId, TaskStatus oldStatus, TaskStatus newStatus);
}
class ConsoleTaskLogger implements TaskObserver {
@Override
public void onTaskStatusChanged(String taskId, TaskStatus oldStatus, TaskStatus newStatus) {
System.out.println(String.format("[Observer Log] Task: %s transitioned from %s to %s", taskId, oldStatus, newStatus));
}
}
abstract class Task {
private final String id;
private final String title;
private final int storyPoints;
private TaskState state;
private String assignee;
private final List<TaskObserver> observers = new CopyOnWriteArrayList<>();
private final ReentrantLock lock = new ReentrantLock();
public Task(String id, String title, int storyPoints) {
this.id = id;
this.title = title;
this.storyPoints = storyPoints;
this.state = new TodoState();
}
public String getId() { return id; }
public String getTitle() { return title; }
public int getStoryPoints() { return storyPoints; }
public TaskStatus getStatus() {
lock.lock();
try { return state.getStatus(); } finally { lock.unlock(); }
}
public void setAssignee(String assignee) {
lock.lock();
try { this.assignee = assignee; } finally { lock.unlock(); }
}
public String getAssignee() {
lock.lock();
try { return assignee; } finally { lock.unlock(); }
}
public void setState(TaskState state) {
this.state = state;
}
public void addObserver(TaskObserver observer) { observers.add(observer); }
public void removeObserver(TaskObserver observer) { observers.remove(observer); }
public boolean transitionTo(TaskStatus nextStatus) {
lock.lock();
try {
TaskStatus oldStatus = state.getStatus();
if (state.transitionTo(this, nextStatus)) {
notifyObservers(oldStatus, nextStatus);
return true;
}
return false;
} finally {
lock.unlock();
}
}
private void notifyObservers(TaskStatus oldStatus, TaskStatus newStatus) {
for (TaskObserver obs : observers) {
obs.onTaskStatusChanged(id, oldStatus, newStatus);
}
}
}
class BugTask extends Task {
public BugTask(String id, String title, int storyPoints) { super(id, title, storyPoints); }
}
class FeatureTask extends Task {
public FeatureTask(String id, String title, int storyPoints) { super(id, title, storyPoints); }
}
class TaskFactory {
public static Task createTask(TaskType type, String id, String title, int storyPoints) {
switch (type) {
case BUG: return new BugTask(id, title, storyPoints);
case FEATURE: return new FeatureTask(id, title, storyPoints);
default: throw new IllegalArgumentException("Unsupported type: " + type);
}
}
}
class Sprint {
private final String name;
private final int capacity;
private final List<Task> tasks = new CopyOnWriteArrayList<>();
private final ReentrantLock lock = new ReentrantLock();
public Sprint(String name, int capacity) {
this.name = name;
this.capacity = capacity;
}
public boolean addTask(Task task) {
lock.lock();
try {
int currentSP = tasks.stream().mapToInt(Task::getStoryPoints).sum();
if (currentSP + task.getStoryPoints() > capacity) {
System.out.println(String.format("[Warning] Adding Task %s (Points: %d) breaches capacity in Sprint: %s (Max: %d)",
task.getId(), task.getStoryPoints(), name, capacity));
}
tasks.add(task);
return true;
} finally {
lock.unlock();
}
}
public List<Task> getTasks() { return Collections.unmodifiableList(tasks); }
}
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Task Planner Simulation ===");
Sprint sprint = new Sprint("Sprint-01", 10);
Task t1 = TaskFactory.createTask(TaskType.FEATURE, "JIRA-101", "Implement Google OAuth", 5);
Task t2 = TaskFactory.createTask(TaskType.BUG, "JIRA-102", "Fix session timeout crash", 3);
Task t3 = TaskFactory.createTask(TaskType.FEATURE, "JIRA-103", "Setup telemetry sinks", 5);
TaskObserver logger = new ConsoleTaskLogger();
t1.addObserver(logger);
t2.addObserver(logger);
sprint.addTask(t1);
sprint.addTask(t2);
sprint.addTask(t3); // Triggers capacity check log
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> t1.transitionTo(TaskStatus.IN_PROGRESS));
executor.submit(() -> t2.transitionTo(TaskStatus.IN_PROGRESS));
executor.shutdown();
executor.awaitTermination(1, TimeUnit.SECONDS);
}
}