Phase 1 delivers the complete durability and storage layer:
- WAL with crash recovery: Append-only journal with BLAKE3 checksums,
fsync guarantees, and proper seek-to-EOF on reopen
- Storage engine: sled-backed KVStore with scan_prefix for range queries
- Content-addressed storage: H:{hash}, V:{hash}, E:{hash} key patterns
- Ingestor: Background worker tailing WAL, writing to KV with 8-byte
aligned record headers for rkyv zero-copy deserialization
- Comprehensive tests: 31 tests covering crash recovery, round-trips,
and multi-cycle durability
New crates: stemedb-wal, stemedb-storage, stemedb-ingest
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
755 lines
22 KiB
Markdown
755 lines
22 KiB
Markdown
# Building a Sprint Planning Meeting Facilitator with ADK-Go
|
|
|
|
This guide shows how to create a multi-agent system that facilitates project planning and sprint planning meetings, helping teams break down work, estimate effort, and create actionable plans.
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The sprint planning facilitator uses specialized agents for each phase:
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────────────┐
|
|
│ SPRINT PLANNING FLOW │
|
|
├──────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ 1. INTAKE 2. BREAKDOWN 3. ESTIMATION 4. PLANNING │
|
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
│ │ Project │───▶│ Story │──────▶│Estimator │───▶│ Sprint │ │
|
|
│ │ Analyzer │ │ Creator │ │ │ │ Planner │ │
|
|
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
|
│ │ │ │ │ │
|
|
│ Understands Creates user Estimates Organizes │
|
|
│ goals & stories & points & into sprints │
|
|
│ requirements subtasks identifies with goals │
|
|
│ risks │
|
|
└──────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Project Setup
|
|
|
|
```bash
|
|
mkdir sprint-planner && cd sprint-planner
|
|
go mod init sprint-planner
|
|
go get google.golang.org/adk
|
|
```
|
|
|
|
---
|
|
|
|
## Data Models
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"time"
|
|
|
|
"google.golang.org/adk/agent"
|
|
"google.golang.org/adk/agent/llmagent"
|
|
"google.golang.org/adk/agent/sequentialagent"
|
|
"google.golang.org/adk/cmd/launcher/adk"
|
|
"google.golang.org/adk/cmd/launcher/full"
|
|
"google.golang.org/adk/model/gemini"
|
|
"google.golang.org/adk/server/restapi/services"
|
|
"google.golang.org/adk/tool"
|
|
"google.golang.org/adk/tool/functiontool"
|
|
"google.golang.org/genai"
|
|
)
|
|
|
|
// Data structures for planning artifacts
|
|
|
|
type UserStory struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
AcceptCrit []string `json:"acceptance_criteria"`
|
|
Priority string `json:"priority"` // must-have, should-have, nice-to-have
|
|
StoryPoints int `json:"story_points,omitempty"`
|
|
Subtasks []Task `json:"subtasks,omitempty"`
|
|
Risks []string `json:"risks,omitempty"`
|
|
Sprint int `json:"sprint,omitempty"`
|
|
}
|
|
|
|
type Task struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
Hours int `json:"estimated_hours"`
|
|
Type string `json:"type"` // frontend, backend, design, testing, devops
|
|
Blocked bool `json:"blocked,omitempty"`
|
|
BlockedBy string `json:"blocked_by,omitempty"`
|
|
}
|
|
|
|
type Sprint struct {
|
|
Number int `json:"number"`
|
|
Goal string `json:"goal"`
|
|
StartDate string `json:"start_date"`
|
|
EndDate string `json:"end_date"`
|
|
Stories []UserStory `json:"stories"`
|
|
TotalPoints int `json:"total_points"`
|
|
Capacity int `json:"capacity"`
|
|
}
|
|
|
|
type ProjectPlan struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Goals []string `json:"goals"`
|
|
Stories []UserStory `json:"stories"`
|
|
Sprints []Sprint `json:"sprints"`
|
|
Risks []Risk `json:"risks"`
|
|
Timeline string `json:"timeline"`
|
|
}
|
|
|
|
type Risk struct {
|
|
Description string `json:"description"`
|
|
Impact string `json:"impact"` // high, medium, low
|
|
Likelihood string `json:"likelihood"` // high, medium, low
|
|
Mitigation string `json:"mitigation"`
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Planning Tools
|
|
|
|
```go
|
|
// Tool: Create User Story
|
|
type CreateStoryInput struct {
|
|
Title string `json:"title" jsonschema:"Clear, concise story title"`
|
|
Description string `json:"description" jsonschema:"As a [user], I want [feature] so that [benefit]"`
|
|
AcceptCrit []string `json:"acceptance_criteria" jsonschema:"List of acceptance criteria"`
|
|
Priority string `json:"priority" jsonschema:"Priority: must-have, should-have, nice-to-have"`
|
|
}
|
|
|
|
type CreateStoryOutput struct {
|
|
StoryID string `json:"story_id"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
var storyCounter int
|
|
var stories []UserStory
|
|
|
|
func createStory(ctx tool.Context, input CreateStoryInput) CreateStoryOutput {
|
|
storyCounter++
|
|
id := fmt.Sprintf("US-%03d", storyCounter)
|
|
|
|
story := UserStory{
|
|
ID: id,
|
|
Title: input.Title,
|
|
Description: input.Description,
|
|
AcceptCrit: input.AcceptCrit,
|
|
Priority: input.Priority,
|
|
}
|
|
stories = append(stories, story)
|
|
|
|
// Save to state
|
|
ctx.Session().State().Set("stories", stories)
|
|
|
|
return CreateStoryOutput{
|
|
StoryID: id,
|
|
Message: fmt.Sprintf("Created story %s: %s", id, input.Title),
|
|
}
|
|
}
|
|
|
|
// Tool: Add Subtask to Story
|
|
type AddSubtaskInput struct {
|
|
StoryID string `json:"story_id" jsonschema:"Parent story ID (e.g., US-001)"`
|
|
Title string `json:"title" jsonschema:"Task title"`
|
|
Description string `json:"description" jsonschema:"Task description"`
|
|
Hours int `json:"estimated_hours" jsonschema:"Estimated hours to complete"`
|
|
Type string `json:"type" jsonschema:"Type: frontend, backend, design, testing, devops, documentation"`
|
|
}
|
|
|
|
type AddSubtaskOutput struct {
|
|
TaskID string `json:"task_id"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
var taskCounter int
|
|
|
|
func addSubtask(ctx tool.Context, input AddSubtaskInput) AddSubtaskOutput {
|
|
taskCounter++
|
|
taskID := fmt.Sprintf("T-%03d", taskCounter)
|
|
|
|
task := Task{
|
|
ID: taskID,
|
|
Title: input.Title,
|
|
Description: input.Description,
|
|
Hours: input.Hours,
|
|
Type: input.Type,
|
|
}
|
|
|
|
// Find and update story
|
|
for i := range stories {
|
|
if stories[i].ID == input.StoryID {
|
|
stories[i].Subtasks = append(stories[i].Subtasks, task)
|
|
ctx.Session().State().Set("stories", stories)
|
|
return AddSubtaskOutput{
|
|
TaskID: taskID,
|
|
Message: fmt.Sprintf("Added task %s to %s", taskID, input.StoryID),
|
|
}
|
|
}
|
|
}
|
|
|
|
return AddSubtaskOutput{
|
|
TaskID: "",
|
|
Message: fmt.Sprintf("Story %s not found", input.StoryID),
|
|
}
|
|
}
|
|
|
|
// Tool: Estimate Story Points
|
|
type EstimateStoryInput struct {
|
|
StoryID string `json:"story_id" jsonschema:"Story ID to estimate"`
|
|
Points int `json:"points" jsonschema:"Story points (1, 2, 3, 5, 8, 13)"`
|
|
Rationale string `json:"rationale" jsonschema:"Brief explanation for estimate"`
|
|
}
|
|
|
|
type EstimateStoryOutput struct {
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func estimateStory(ctx tool.Context, input EstimateStoryInput) EstimateStoryOutput {
|
|
for i := range stories {
|
|
if stories[i].ID == input.StoryID {
|
|
stories[i].StoryPoints = input.Points
|
|
ctx.Session().State().Set("stories", stories)
|
|
return EstimateStoryOutput{
|
|
Message: fmt.Sprintf("Estimated %s at %d points: %s", input.StoryID, input.Points, input.Rationale),
|
|
}
|
|
}
|
|
}
|
|
return EstimateStoryOutput{Message: "Story not found"}
|
|
}
|
|
|
|
// Tool: Add Risk
|
|
type AddRiskInput struct {
|
|
Description string `json:"description" jsonschema:"Risk description"`
|
|
Impact string `json:"impact" jsonschema:"Impact level: high, medium, low"`
|
|
Likelihood string `json:"likelihood" jsonschema:"Likelihood: high, medium, low"`
|
|
Mitigation string `json:"mitigation" jsonschema:"Mitigation strategy"`
|
|
StoryID string `json:"story_id,omitempty" jsonschema:"Related story ID (optional)"`
|
|
}
|
|
|
|
type AddRiskOutput struct {
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
var risks []Risk
|
|
|
|
func addRisk(ctx tool.Context, input AddRiskInput) AddRiskOutput {
|
|
risk := Risk{
|
|
Description: input.Description,
|
|
Impact: input.Impact,
|
|
Likelihood: input.Likelihood,
|
|
Mitigation: input.Mitigation,
|
|
}
|
|
risks = append(risks, risk)
|
|
|
|
// Also add to story if specified
|
|
if input.StoryID != "" {
|
|
for i := range stories {
|
|
if stories[i].ID == input.StoryID {
|
|
stories[i].Risks = append(stories[i].Risks, input.Description)
|
|
}
|
|
}
|
|
}
|
|
|
|
ctx.Session().State().Set("risks", risks)
|
|
ctx.Session().State().Set("stories", stories)
|
|
|
|
return AddRiskOutput{Message: fmt.Sprintf("Added risk: %s", input.Description)}
|
|
}
|
|
|
|
// Tool: Assign to Sprint
|
|
type AssignSprintInput struct {
|
|
StoryID string `json:"story_id" jsonschema:"Story ID to assign"`
|
|
SprintNum int `json:"sprint_number" jsonschema:"Sprint number (1, 2, 3, etc.)"`
|
|
}
|
|
|
|
type AssignSprintOutput struct {
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func assignSprint(ctx tool.Context, input AssignSprintInput) AssignSprintOutput {
|
|
for i := range stories {
|
|
if stories[i].ID == input.StoryID {
|
|
stories[i].Sprint = input.SprintNum
|
|
ctx.Session().State().Set("stories", stories)
|
|
return AssignSprintOutput{
|
|
Message: fmt.Sprintf("Assigned %s to Sprint %d", input.StoryID, input.SprintNum),
|
|
}
|
|
}
|
|
}
|
|
return AssignSprintOutput{Message: "Story not found"}
|
|
}
|
|
|
|
// Tool: Generate Plan Summary
|
|
type GeneratePlanInput struct {
|
|
SprintLength int `json:"sprint_length_days" jsonschema:"Sprint length in days (default: 14)"`
|
|
Velocity int `json:"velocity" jsonschema:"Team velocity (story points per sprint)"`
|
|
}
|
|
|
|
type GeneratePlanOutput struct {
|
|
Summary string `json:"summary"`
|
|
Plan json.RawMessage `json:"plan"`
|
|
}
|
|
|
|
func generatePlan(ctx tool.Context, input GeneratePlanInput) GeneratePlanOutput {
|
|
sprintLength := input.SprintLength
|
|
if sprintLength == 0 {
|
|
sprintLength = 14
|
|
}
|
|
|
|
// Group stories by sprint
|
|
sprintMap := make(map[int][]UserStory)
|
|
totalPoints := 0
|
|
for _, s := range stories {
|
|
sprintMap[s.Sprint] = append(sprintMap[s.Sprint], s)
|
|
totalPoints += s.StoryPoints
|
|
}
|
|
|
|
// Build sprints
|
|
var sprints []Sprint
|
|
startDate := time.Now()
|
|
for num := 1; num <= len(sprintMap); num++ {
|
|
sprintStories := sprintMap[num]
|
|
points := 0
|
|
for _, s := range sprintStories {
|
|
points += s.StoryPoints
|
|
}
|
|
|
|
sprint := Sprint{
|
|
Number: num,
|
|
StartDate: startDate.Format("2006-01-02"),
|
|
EndDate: startDate.AddDate(0, 0, sprintLength).Format("2006-01-02"),
|
|
Stories: sprintStories,
|
|
TotalPoints: points,
|
|
Capacity: input.Velocity,
|
|
}
|
|
sprints = append(sprints, sprint)
|
|
startDate = startDate.AddDate(0, 0, sprintLength)
|
|
}
|
|
|
|
plan := ProjectPlan{
|
|
Stories: stories,
|
|
Sprints: sprints,
|
|
Risks: risks,
|
|
}
|
|
|
|
planJSON, _ := json.MarshalIndent(plan, "", " ")
|
|
|
|
summary := fmt.Sprintf(
|
|
"Plan: %d stories, %d total points, %d sprints, %d risks identified",
|
|
len(stories), totalPoints, len(sprints), len(risks),
|
|
)
|
|
|
|
return GeneratePlanOutput{
|
|
Summary: summary,
|
|
Plan: planJSON,
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Building the Planning Pipeline
|
|
|
|
```go
|
|
func main() {
|
|
ctx := context.Background()
|
|
|
|
model, err := gemini.NewModel(ctx, "gemini-3-flash-preview", &genai.ClientConfig{
|
|
APIKey: os.Getenv("GOOGLE_API_KEY"),
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
planner, err := buildPlanningPipeline(model)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
l := full.NewLauncher()
|
|
cfg := &adk.Config{AgentLoader: services.NewSingleAgentLoader(planner)}
|
|
if err := l.Execute(ctx, cfg, os.Args[1:]); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func buildPlanningPipeline(model *gemini.Model) (agent.Agent, error) {
|
|
tools, err := createPlanningTools()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Stage 1: Project Analyzer
|
|
analyzer, err := llmagent.New(llmagent.Config{
|
|
Name: "project_analyzer",
|
|
Model: model,
|
|
Description: "Analyzes project requirements and extracts goals",
|
|
Instruction: `You are a senior product manager analyzing a project.
|
|
|
|
Given the project description, identify:
|
|
1. **Core Goals**: What are the main objectives? (3-5 goals)
|
|
2. **User Types**: Who are the users/stakeholders?
|
|
3. **Key Features**: What are the must-have features?
|
|
4. **Constraints**: Timeline, technical, or resource constraints mentioned
|
|
5. **Success Metrics**: How will success be measured?
|
|
|
|
Be thorough but concise. Output a structured analysis.`,
|
|
OutputKey: "project_analysis",
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Stage 2: Story Creator
|
|
storyCreator, err := llmagent.New(llmagent.Config{
|
|
Name: "story_creator",
|
|
Model: model,
|
|
Description: "Creates user stories from requirements",
|
|
Instruction: `You are an agile coach creating user stories.
|
|
|
|
PROJECT ANALYSIS:
|
|
{project_analysis}
|
|
|
|
Create user stories for each feature identified. For each story:
|
|
1. Use the create_story tool with proper format
|
|
2. Write clear acceptance criteria (testable conditions)
|
|
3. Assign appropriate priority (must-have, should-have, nice-to-have)
|
|
4. Use the add_subtask tool to break down into technical tasks
|
|
|
|
Story format: "As a [user type], I want [feature] so that [benefit]"
|
|
|
|
Create stories for ALL identified features. Be comprehensive.
|
|
Break down large features into multiple smaller stories.`,
|
|
Tools: tools,
|
|
OutputKey: "stories_created",
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Stage 3: Estimator
|
|
estimator, err := llmagent.New(llmagent.Config{
|
|
Name: "estimator",
|
|
Model: model,
|
|
Description: "Estimates story points and identifies risks",
|
|
Instruction: `You are a tech lead estimating work and identifying risks.
|
|
|
|
STORIES CREATED:
|
|
{stories_created}
|
|
|
|
For each story:
|
|
1. Use estimate_story to assign story points (Fibonacci: 1, 2, 3, 5, 8, 13)
|
|
- 1-2: Simple, well-understood
|
|
- 3-5: Medium complexity
|
|
- 8: Complex, some unknowns
|
|
- 13: Very complex, needs breakdown
|
|
|
|
2. Use add_risk for any risks you identify:
|
|
- Technical risks (new technology, integration challenges)
|
|
- Dependency risks (external teams, third-party services)
|
|
- Scope risks (unclear requirements, likely changes)
|
|
|
|
Consider:
|
|
- Task complexity from subtasks
|
|
- Dependencies between stories
|
|
- Team's likely familiarity with the tech
|
|
- Uncertainty and unknowns`,
|
|
Tools: tools,
|
|
OutputKey: "estimates_complete",
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Stage 4: Sprint Planner
|
|
sprintPlanner, err := llmagent.New(llmagent.Config{
|
|
Name: "sprint_planner",
|
|
Model: model,
|
|
Description: "Organizes stories into sprints",
|
|
Instruction: `You are a scrum master organizing the sprint plan.
|
|
|
|
ESTIMATES:
|
|
{estimates_complete}
|
|
|
|
Team capacity: Assume 30-40 story points per 2-week sprint.
|
|
|
|
Organize stories into sprints:
|
|
1. Use assign_sprint to place each story in a sprint
|
|
2. Prioritize must-have stories in early sprints
|
|
3. Consider dependencies (blocked stories go later)
|
|
4. Balance sprint workloads (don't exceed capacity)
|
|
5. Create logical sprint goals (each sprint delivers value)
|
|
|
|
After assigning all stories, use generate_plan to create the final summary.
|
|
|
|
Sprint planning principles:
|
|
- Sprint 1: Foundation and core features
|
|
- Middle sprints: Build on foundation
|
|
- Final sprint: Polish, testing, nice-to-haves`,
|
|
Tools: tools,
|
|
OutputKey: "sprint_plan",
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Stage 5: Plan Presenter
|
|
presenter, err := llmagent.New(llmagent.Config{
|
|
Name: "presenter",
|
|
Model: model,
|
|
Description: "Presents the final plan in readable format",
|
|
Instruction: `Present the sprint plan in a clear, actionable format.
|
|
|
|
SPRINT PLAN:
|
|
{sprint_plan}
|
|
|
|
Create a presentation that includes:
|
|
|
|
## Executive Summary
|
|
- Total scope and timeline
|
|
- Key risks and mitigations
|
|
|
|
## Sprint Breakdown
|
|
For each sprint:
|
|
- Sprint goal (one sentence)
|
|
- Stories included with points
|
|
- Key deliverables
|
|
|
|
## Risk Register
|
|
- High priority risks with mitigation plans
|
|
|
|
## Recommendations
|
|
- Any suggestions for the team
|
|
- Dependencies to resolve early
|
|
- Decisions needed
|
|
|
|
Format for easy reading and sharing with stakeholders.`,
|
|
OutputKey: "final_presentation",
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Assemble pipeline
|
|
return sequentialagent.New(sequentialagent.Config{
|
|
AgentConfig: agent.Config{
|
|
Name: "sprint_planning_facilitator",
|
|
Description: "Facilitates complete sprint planning from requirements to actionable plan",
|
|
SubAgents: []agent.Agent{analyzer, storyCreator, estimator, sprintPlanner, presenter},
|
|
},
|
|
})
|
|
}
|
|
|
|
func createPlanningTools() ([]tool.Tool, error) {
|
|
var tools []tool.Tool
|
|
|
|
createStoryTool, _ := functiontool.New(
|
|
functiontool.Config{
|
|
Name: "create_story",
|
|
Description: "Create a new user story",
|
|
},
|
|
createStory,
|
|
)
|
|
tools = append(tools, createStoryTool)
|
|
|
|
addSubtaskTool, _ := functiontool.New(
|
|
functiontool.Config{
|
|
Name: "add_subtask",
|
|
Description: "Add a technical subtask to a user story",
|
|
},
|
|
addSubtask,
|
|
)
|
|
tools = append(tools, addSubtaskTool)
|
|
|
|
estimateTool, _ := functiontool.New(
|
|
functiontool.Config{
|
|
Name: "estimate_story",
|
|
Description: "Assign story points to a user story",
|
|
},
|
|
estimateStory,
|
|
)
|
|
tools = append(tools, estimateTool)
|
|
|
|
riskTool, _ := functiontool.New(
|
|
functiontool.Config{
|
|
Name: "add_risk",
|
|
Description: "Document a project risk with mitigation",
|
|
},
|
|
addRisk,
|
|
)
|
|
tools = append(tools, riskTool)
|
|
|
|
sprintTool, _ := functiontool.New(
|
|
functiontool.Config{
|
|
Name: "assign_sprint",
|
|
Description: "Assign a story to a specific sprint",
|
|
},
|
|
assignSprint,
|
|
)
|
|
tools = append(tools, sprintTool)
|
|
|
|
planTool, _ := functiontool.New(
|
|
functiontool.Config{
|
|
Name: "generate_plan",
|
|
Description: "Generate the final sprint plan summary",
|
|
},
|
|
generatePlan,
|
|
)
|
|
tools = append(tools, planTool)
|
|
|
|
return tools, nil
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Interactive Planning Mode
|
|
|
|
For collaborative sessions where the user provides input at each stage:
|
|
|
|
```go
|
|
func buildInteractivePlanner(model *gemini.Model) (agent.Agent, error) {
|
|
tools, _ := createPlanningTools()
|
|
|
|
return llmagent.New(llmagent.Config{
|
|
Name: "interactive_planner",
|
|
Model: model,
|
|
Instruction: `You are an agile coach facilitating a sprint planning session.
|
|
|
|
CURRENT STATE:
|
|
Stories: {stories}
|
|
Risks: {risks}
|
|
|
|
Guide the user through planning:
|
|
|
|
1. **If no stories exist**: Ask about the project and help create stories
|
|
2. **If stories need breakdown**: Help add subtasks
|
|
3. **If stories need estimates**: Facilitate estimation discussion
|
|
4. **If stories need sprint assignment**: Help prioritize and assign
|
|
5. **If plan is complete**: Offer to present or refine
|
|
|
|
Available commands the user might give:
|
|
- "Let's plan [project description]" → Start fresh
|
|
- "Add a story for [feature]" → Create specific story
|
|
- "Break down [story ID]" → Add subtasks
|
|
- "Estimate [story ID]" → Discuss and set points
|
|
- "Show the plan" → Generate current plan
|
|
- "What's in sprint [N]?" → Show sprint details
|
|
|
|
Be collaborative. Ask clarifying questions. Suggest improvements.
|
|
Use tools to track everything properly.`,
|
|
Tools: tools,
|
|
})
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Running the Planner
|
|
|
|
```bash
|
|
# Run sprint planning
|
|
go run main.go
|
|
|
|
# Example prompts:
|
|
# > Plan a mobile app for food delivery with user ordering, restaurant management, and driver tracking
|
|
# > We're building an internal dashboard for sales analytics with charts, reports, and alerts
|
|
# > Create a sprint plan for migrating our monolith to microservices
|
|
|
|
# Web UI for visual debugging
|
|
go run main.go web api webui
|
|
```
|
|
|
|
---
|
|
|
|
## Sample Output
|
|
|
|
**User**: Plan a task management app with projects, tasks, due dates, and team collaboration
|
|
|
|
**Output**:
|
|
|
|
```markdown
|
|
# Sprint Plan: Task Management App
|
|
|
|
## Executive Summary
|
|
|
|
- **Scope**: 12 user stories, 47 story points
|
|
- **Timeline**: 3 sprints (6 weeks)
|
|
- **Key Risks**: Real-time sync complexity, notification deliverability
|
|
|
|
## Sprint 1: Foundation (Weeks 1-2)
|
|
|
|
**Goal**: Core task and project management
|
|
|
|
| ID | Story | Points |
|
|
| ------ | ------------------------------------ | ------ |
|
|
| US-001 | User registration and authentication | 5 |
|
|
| US-002 | Create and manage projects | 3 |
|
|
| US-003 | Create, edit, delete tasks | 5 |
|
|
| US-004 | Set due dates with calendar picker | 3 |
|
|
|
|
**Total**: 16 points | **Deliverable**: Users can create projects and tasks
|
|
|
|
## Sprint 2: Collaboration (Weeks 3-4)
|
|
|
|
**Goal**: Team features and organization
|
|
|
|
| ID | Story | Points |
|
|
| ------ | ------------------------------- | ------ |
|
|
| US-005 | Invite team members to projects | 5 |
|
|
| US-006 | Assign tasks to team members | 3 |
|
|
| US-007 | Task comments and activity feed | 5 |
|
|
| US-008 | Task labels and filtering | 3 |
|
|
|
|
**Total**: 16 points | **Deliverable**: Teams can collaborate on projects
|
|
|
|
## Sprint 3: Polish (Weeks 5-6)
|
|
|
|
**Goal**: Notifications and UX improvements
|
|
|
|
| ID | Story | Points |
|
|
| ------ | --------------------------------- | ------ |
|
|
| US-009 | Email notifications for due dates | 5 |
|
|
| US-010 | Push notifications (mobile) | 5 |
|
|
| US-011 | Dashboard with task overview | 3 |
|
|
| US-012 | Search across all tasks | 2 |
|
|
|
|
**Total**: 15 points | **Deliverable**: Complete MVP ready for beta
|
|
|
|
## Risk Register
|
|
|
|
| Risk | Impact | Likelihood | Mitigation |
|
|
| ---------------------------- | ------ | ---------- | --------------------------------------------------------------- |
|
|
| Real-time sync conflicts | High | Medium | Implement optimistic locking, conflict resolution UI |
|
|
| Push notification delivery | Medium | Medium | Use established service (Firebase), implement fallback to email |
|
|
| Scope creep on collaboration | Medium | High | Strict MVP definition, defer advanced features |
|
|
|
|
## Recommendations
|
|
|
|
1. **Decide early**: Authentication provider (build vs. Auth0/Firebase)
|
|
2. **Spike needed**: Real-time sync approach (WebSockets vs. polling)
|
|
3. **Dependency**: Mobile notification setup requires Apple/Google developer accounts
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
1. **Start with clear requirements**: The better the input, the better the plan
|
|
2. **Review intermediate outputs**: Check stories before estimation
|
|
3. **Adjust velocity**: Set realistic team capacity based on actual data
|
|
4. **Iterate**: Use interactive mode to refine the plan collaboratively
|
|
5. **Export and track**: Copy the plan to your actual project management tool
|
|
6. **Re-plan as needed**: Run again when scope changes significantly
|