feat(witness): implement epic child filtering for auto-spawn
Add isChildOfEpic() function that checks if an issue is a child of the configured epic by verifying the issue has the epic in its dependents with dependency_type="blocks". When EpicID is configured in witness config, only issues that block that epic will be considered for auto-spawning. Issues that cannot be verified are safely skipped. Closes gt-zhm5. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -685,8 +685,14 @@ func (m *Manager) autoSpawnForReadyWork(w *Witness) error {
|
||||
|
||||
// Filter by epic if configured
|
||||
if w.Config.EpicID != "" {
|
||||
// TODO: Check if issue is a child of the configured epic
|
||||
// For now, we skip this filter
|
||||
isChild, err := m.isChildOfEpic(issue.ID, w.Config.EpicID)
|
||||
if err != nil {
|
||||
// Skip issues we can't verify - safer than including unknown work
|
||||
continue
|
||||
}
|
||||
if !isChild {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by prefix if configured
|
||||
@@ -783,6 +789,50 @@ func (m *Manager) getReadyIssues() ([]ReadyIssue, error) {
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
// issueDependency represents a dependency from bd show --json output.
|
||||
type issueDependency struct {
|
||||
ID string `json:"id"`
|
||||
DependencyType string `json:"dependency_type"`
|
||||
}
|
||||
|
||||
// issueWithDeps represents an issue with its dependencies from bd show --json.
|
||||
type issueWithDeps struct {
|
||||
ID string `json:"id"`
|
||||
Dependents []issueDependency `json:"dependents"`
|
||||
}
|
||||
|
||||
// isChildOfEpic checks if an issue blocks (is a child of) the given epic.
|
||||
func (m *Manager) isChildOfEpic(issueID, epicID string) (bool, error) {
|
||||
cmd := exec.Command("bd", "show", issueID, "--json")
|
||||
cmd.Dir = m.workDir
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false, fmt.Errorf("%s", stderr.String())
|
||||
}
|
||||
|
||||
var issues []issueWithDeps
|
||||
if err := json.Unmarshal(stdout.Bytes(), &issues); err != nil {
|
||||
return false, fmt.Errorf("parsing issue: %w", err)
|
||||
}
|
||||
|
||||
if len(issues) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Check if the epic is in the dependents with type "blocks"
|
||||
for _, dep := range issues[0].Dependents {
|
||||
if dep.ID == epicID && dep.DependencyType == "blocks" {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// isAlreadySpawned checks if an issue has already been spawned.
|
||||
func (m *Manager) isAlreadySpawned(w *Witness, issueID string) bool {
|
||||
for _, id := range w.SpawnedIssues {
|
||||
|
||||
Reference in New Issue
Block a user