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
|
// Filter by epic if configured
|
||||||
if w.Config.EpicID != "" {
|
if w.Config.EpicID != "" {
|
||||||
// TODO: Check if issue is a child of the configured epic
|
isChild, err := m.isChildOfEpic(issue.ID, w.Config.EpicID)
|
||||||
// For now, we skip this filter
|
if err != nil {
|
||||||
|
// Skip issues we can't verify - safer than including unknown work
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !isChild {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter by prefix if configured
|
// Filter by prefix if configured
|
||||||
@@ -783,6 +789,50 @@ func (m *Manager) getReadyIssues() ([]ReadyIssue, error) {
|
|||||||
return issues, nil
|
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.
|
// isAlreadySpawned checks if an issue has already been spawned.
|
||||||
func (m *Manager) isAlreadySpawned(w *Witness, issueID string) bool {
|
func (m *Manager) isAlreadySpawned(w *Witness, issueID string) bool {
|
||||||
for _, id := range w.SpawnedIssues {
|
for _, id := range w.SpawnedIssues {
|
||||||
|
|||||||
Reference in New Issue
Block a user