feat(types): add RequiredSections() method to IssueType (bd-v2mr)

Foundation for opt-in template validation (bd-ou35). Returns recommended
markdown sections per issue type:
- Bug: Steps to Reproduce, Acceptance Criteria
- Task/Feature: Acceptance Criteria
- Epic: Success Criteria
- Others: no requirements

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beads/crew/grip
2026-01-01 11:28:34 -08:00
committed by Steve Yegge
parent a9e70e3fe5
commit e6ee7a6912
2 changed files with 67 additions and 0 deletions

View File

@@ -420,6 +420,37 @@ func (t IssueType) IsValid() bool {
return false
}
// RequiredSection describes a recommended section for an issue type.
// Used by bd lint and bd create --validate for template validation.
type RequiredSection struct {
Heading string // Markdown heading, e.g., "## Steps to Reproduce"
Hint string // Guidance for what to include
}
// RequiredSections returns the recommended sections for this issue type.
// Returns nil for types with no specific section requirements.
func (t IssueType) RequiredSections() []RequiredSection {
switch t {
case TypeBug:
return []RequiredSection{
{Heading: "## Steps to Reproduce", Hint: "Describe how to reproduce the bug"},
{Heading: "## Acceptance Criteria", Hint: "Define criteria to verify the fix"},
}
case TypeTask, TypeFeature:
return []RequiredSection{
{Heading: "## Acceptance Criteria", Hint: "Define criteria to verify completion"},
}
case TypeEpic:
return []RequiredSection{
{Heading: "## Success Criteria", Hint: "Define high-level success criteria"},
}
default:
// Chore, message, molecule, gate, agent, role, convoy, event, merge-request
// have no required sections
return nil
}
}
// AgentState represents the self-reported state of an agent
type AgentState string

View File

@@ -414,6 +414,42 @@ func TestIssueTypeIsValid(t *testing.T) {
}
}
func TestIssueTypeRequiredSections(t *testing.T) {
tests := []struct {
issueType IssueType
expectCount int
expectHeading string // First heading if any
}{
{TypeBug, 2, "## Steps to Reproduce"},
{TypeFeature, 1, "## Acceptance Criteria"},
{TypeTask, 1, "## Acceptance Criteria"},
{TypeEpic, 1, "## Success Criteria"},
{TypeChore, 0, ""},
{TypeMessage, 0, ""},
{TypeMolecule, 0, ""},
{TypeGate, 0, ""},
{TypeAgent, 0, ""},
{TypeRole, 0, ""},
{TypeConvoy, 0, ""},
{TypeEvent, 0, ""},
{TypeMergeRequest, 0, ""},
}
for _, tt := range tests {
t.Run(string(tt.issueType), func(t *testing.T) {
sections := tt.issueType.RequiredSections()
if len(sections) != tt.expectCount {
t.Errorf("IssueType(%q).RequiredSections() returned %d sections, want %d",
tt.issueType, len(sections), tt.expectCount)
}
if tt.expectCount > 0 && sections[0].Heading != tt.expectHeading {
t.Errorf("IssueType(%q).RequiredSections()[0].Heading = %q, want %q",
tt.issueType, sections[0].Heading, tt.expectHeading)
}
})
}
}
func TestAgentStateIsValid(t *testing.T) {
cases := []struct {
name string