feat(epic): add Working Model as required section for epics

Epics now require a "Working Model" section in their description,
in addition to "Success Criteria". This provides clear guidance on
HOW the epic will be executed:

- Owner role: Coordinator vs Implementer
- Delegation target: Polecats, crew, external
- Review process: Approval gates

Closes gt-0lp

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
diesel
2026-01-22 17:32:38 -08:00
committed by John Ogle
parent 6e82d1e2ee
commit 5483ecf437
4 changed files with 80 additions and 42 deletions

View File

@@ -24,3 +24,25 @@ Manage epics (large features composed of multiple issues).
4. Auto-close when done: `bd epic close-eligible`
Epics use parent-child dependencies to track subtasks.
## Epic Template Sections
Epics require two sections in their description:
### Success Criteria
Define high-level criteria for epic completion. What does "done" look like?
### Working Model
Define HOW the epic will be executed:
- **Owner role**: Is the assignee a Coordinator (decomposes and delegates) or Implementer (does hands-on work)?
- **Delegation target**: Who does the work? Polecats? Other crew? External contributors?
- **Review process**: Who approves completed subtasks? What gates must pass?
Example:
```markdown
## Working Model
- **Owner role**: Coordinator - decompose into subtasks and sling to polecats
- **Delegation target**: Polecat swarm (3-5 workers)
- **Review process**: Refinery MQ for each subtask, owner approval for epic closure
```

View File

@@ -34,9 +34,9 @@ type Issue struct {
EstimatedMinutes *int `json:"estimated_minutes,omitempty"`
// ===== Timestamps =====
CreatedAt time.Time `json:"created_at"`
CreatedBy string `json:"created_by,omitempty"` // Who created this issue (GH#748)
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
CreatedBy string `json:"created_by,omitempty"` // Who created this issue (GH#748)
UpdatedAt time.Time `json:"updated_at"`
ClosedAt *time.Time `json:"closed_at,omitempty"`
CloseReason string `json:"close_reason,omitempty"` // Reason provided when closing
ClosedBySession string `json:"closed_by_session,omitempty"` // Claude Code session that closed this issue
@@ -560,6 +560,7 @@ func (t IssueType) RequiredSections() []RequiredSection {
case TypeEpic:
return []RequiredSection{
{Heading: "## Success Criteria", Hint: "Define high-level success criteria"},
{Heading: "## Working Model", Hint: "Owner role (coordinator/implementer), delegation target, review process"},
}
default:
// Chore and custom types have no required sections
@@ -667,7 +668,7 @@ type IssueWithCounts struct {
// Used for JSON serialization in bd show and RPC responses.
type IssueDetails struct {
Issue
Labels []string `json:"labels,omitempty"`
Labels []string `json:"labels,omitempty"`
Dependencies []*IssueWithDependencyMetadata `json:"dependencies,omitempty"`
Dependents []*IssueWithDependencyMetadata `json:"dependents,omitempty"`
Comments []*Comment `json:"comments,omitempty"`
@@ -690,10 +691,10 @@ const (
DepDiscoveredFrom DependencyType = "discovered-from"
// Graph link types
DepRepliesTo DependencyType = "replies-to" // Conversation threading
DepRelatesTo DependencyType = "relates-to" // Loose knowledge graph edges
DepDuplicates DependencyType = "duplicates" // Deduplication link
DepSupersedes DependencyType = "supersedes" // Version chain link
DepRepliesTo DependencyType = "replies-to" // Conversation threading
DepRelatesTo DependencyType = "relates-to" // Loose knowledge graph edges
DepDuplicates DependencyType = "duplicates" // Deduplication link
DepSupersedes DependencyType = "supersedes" // Version chain link
// Entity types (HOP foundation - Decision 004)
DepAuthoredBy DependencyType = "authored-by" // Creator relationship
@@ -820,14 +821,14 @@ type Comment struct {
// Event represents an audit trail entry
type Event struct {
ID int64 `json:"id"`
IssueID string `json:"issue_id"`
EventType EventType `json:"event_type"`
Actor string `json:"actor"`
OldValue *string `json:"old_value,omitempty"`
NewValue *string `json:"new_value,omitempty"`
Comment *string `json:"comment,omitempty"`
CreatedAt time.Time `json:"created_at"`
ID int64 `json:"id"`
IssueID string `json:"issue_id"`
EventType EventType `json:"event_type"`
Actor string `json:"actor"`
OldValue *string `json:"old_value,omitempty"`
NewValue *string `json:"new_value,omitempty"`
Comment *string `json:"comment,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
// EventType categorizes audit trail events
@@ -878,17 +879,17 @@ type MoleculeProgressStats struct {
// Statistics provides aggregate metrics
type Statistics struct {
TotalIssues int `json:"total_issues"`
OpenIssues int `json:"open_issues"`
InProgressIssues int `json:"in_progress_issues"`
ClosedIssues int `json:"closed_issues"`
BlockedIssues int `json:"blocked_issues"`
DeferredIssues int `json:"deferred_issues"` // Issues on ice
ReadyIssues int `json:"ready_issues"`
TombstoneIssues int `json:"tombstone_issues"` // Soft-deleted issues
PinnedIssues int `json:"pinned_issues"` // Persistent issues
EpicsEligibleForClosure int `json:"epics_eligible_for_closure"`
AverageLeadTime float64 `json:"average_lead_time_hours"`
TotalIssues int `json:"total_issues"`
OpenIssues int `json:"open_issues"`
InProgressIssues int `json:"in_progress_issues"`
ClosedIssues int `json:"closed_issues"`
BlockedIssues int `json:"blocked_issues"`
DeferredIssues int `json:"deferred_issues"` // Issues on ice
ReadyIssues int `json:"ready_issues"`
TombstoneIssues int `json:"tombstone_issues"` // Soft-deleted issues
PinnedIssues int `json:"pinned_issues"` // Persistent issues
EpicsEligibleForClosure int `json:"epics_eligible_for_closure"`
AverageLeadTime float64 `json:"average_lead_time_hours"`
}
// IssueFilter is used to filter issue queries
@@ -897,18 +898,18 @@ type IssueFilter struct {
Priority *int
IssueType *IssueType
Assignee *string
Labels []string // AND semantics: issue must have ALL these labels
LabelsAny []string // OR semantics: issue must have AT LEAST ONE of these labels
Labels []string // AND semantics: issue must have ALL these labels
LabelsAny []string // OR semantics: issue must have AT LEAST ONE of these labels
TitleSearch string
IDs []string // Filter by specific issue IDs
IDPrefix string // Filter by ID prefix (e.g., "bd-" to match "bd-abc123")
IDs []string // Filter by specific issue IDs
IDPrefix string // Filter by ID prefix (e.g., "bd-" to match "bd-abc123")
Limit int
// Pattern matching
TitleContains string
DescriptionContains string
NotesContains string
// Date ranges
CreatedAfter *time.Time
CreatedBefore *time.Time
@@ -916,12 +917,12 @@ type IssueFilter struct {
UpdatedBefore *time.Time
ClosedAfter *time.Time
ClosedBefore *time.Time
// Empty/null checks
EmptyDescription bool
NoAssignee bool
NoLabels bool
// Numeric ranges
PriorityMin *int
PriorityMax *int
@@ -990,12 +991,12 @@ func (s SortPolicy) IsValid() bool {
// WorkFilter is used to filter ready work queries
type WorkFilter struct {
Status Status
Type string // Filter by issue type (task, bug, feature, epic, merge-request, etc.)
Type string // Filter by issue type (task, bug, feature, epic, merge-request, etc.)
Priority *int
Assignee *string
Unassigned bool // Filter for issues with no assignee
Labels []string // AND semantics: issue must have ALL these labels
LabelsAny []string // OR semantics: issue must have AT LEAST ONE of these labels
Unassigned bool // Filter for issues with no assignee
Labels []string // AND semantics: issue must have ALL these labels
LabelsAny []string // OR semantics: issue must have AT LEAST ONE of these labels
Limit int
SortPolicy SortPolicy

View File

@@ -579,7 +579,7 @@ func TestIssueTypeRequiredSections(t *testing.T) {
{TypeBug, 2, "## Steps to Reproduce"},
{TypeFeature, 1, "## Acceptance Criteria"},
{TypeTask, 1, "## Acceptance Criteria"},
{TypeEpic, 1, "## Success Criteria"},
{TypeEpic, 2, "## Success Criteria"},
{TypeChore, 0, ""},
// Gas Town types are now custom and have no required sections
{IssueType("message"), 0, ""},

View File

@@ -97,19 +97,34 @@ Widget displays correctly`,
// Epic type tests
{
name: "epic with success criteria",
name: "epic with all sections",
issueType: types.TypeEpic,
description: `Big project
## Success Criteria
- Project ships
- Users happy`,
- Users happy
## Working Model
- Owner role: Coordinator
- Delegation target: Polecats
- Review process: Refinery MQ`,
wantErr: false,
},
{
name: "epic missing success criteria",
name: "epic missing all sections",
issueType: types.TypeEpic,
description: "Do everything",
wantErr: true,
wantMissing: 2,
},
{
name: "epic missing working model",
issueType: types.TypeEpic,
description: `Big project
## Success Criteria
- Project ships`,
wantErr: true,
wantMissing: 1,
},