beads: reduce context overflow, simplify filters, atomic blockers
- list_beads.sh/list_ready_beads.sh: filter to id,priority,title,status - Add read_bead_comments.sh tool and Comments view in Assist - Simplify Assist filters to All/Ready (was 6 status filters) - Server-side filtering: list excludes closed at DB, ready uses GetReadyWork - Default limit 100, DB-level filtering - Atomic blockers= support in new command
This commit is contained in:
parent
3896b3db43
commit
39b49e83ce
|
|
@ -1870,10 +1870,9 @@ func parseBeadEdits(content string, beads []Bead) []beadEdit {
|
|||
return edits
|
||||
}
|
||||
|
||||
// applyBeadEdits applies a batch of bead edits, then refreshes the tasks window.
|
||||
func applyBeadEdits(w *acme.Win, edits []beadEdit, beads *[]Bead, statusFilter string, mount string) {
|
||||
// applyBeadEdits applies a batch of bead edits.
|
||||
func applyBeadEdits(w *acme.Win, edits []beadEdit, beads *[]Bead, filter string, mount string) {
|
||||
if len(edits) == 0 {
|
||||
refreshTasksWindowWithBeads(w, *beads, mount)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1909,9 +1908,8 @@ func applyBeadEdits(w *acme.Win, edits []beadEdit, beads *[]Bead, statusFilter s
|
|||
for _, e := range errs {
|
||||
fmt.Fprintf(os.Stderr, "beadEdit error: %s\n", e)
|
||||
}
|
||||
refreshed, _ := listBeadsWithFilter(statusFilter, mount)
|
||||
refreshed, _ := listBeadsWithFilter(filter, mount)
|
||||
*beads = refreshed
|
||||
refreshTasksWindowWithBeads(w, *beads, mount)
|
||||
}
|
||||
|
||||
func openTasksWindow() error {
|
||||
|
|
@ -1931,10 +1929,10 @@ func handleTasksWindow(w *acme.Win) {
|
|||
defer w.CloseFiles()
|
||||
|
||||
var beads []Bead
|
||||
statusFilter := ""
|
||||
windowMount := "" // Start with no mount
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
filter := "ready"
|
||||
windowMount := ""
|
||||
beads, _ = listBeadsWithFilter(filter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount, filter)
|
||||
|
||||
for e := range w.EventChan() {
|
||||
switch e.C2 {
|
||||
|
|
@ -1943,8 +1941,8 @@ func handleTasksWindow(w *acme.Win) {
|
|||
arg := strings.TrimSpace(string(e.Arg))
|
||||
switch cmd {
|
||||
case "Get":
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
beads, _ = listBeadsWithFilter(filter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount, filter)
|
||||
case "Put":
|
||||
body, err := w.ReadAll("body")
|
||||
if err != nil {
|
||||
|
|
@ -1952,7 +1950,7 @@ func handleTasksWindow(w *acme.Win) {
|
|||
continue
|
||||
}
|
||||
edits := parseBeadEdits(string(body), beads)
|
||||
applyBeadEdits(w, edits, &beads, statusFilter, windowMount)
|
||||
applyBeadEdits(w, edits, &beads, filter, windowMount)
|
||||
case "New":
|
||||
if err := openNewBeadWindow("", windowMount); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error opening new bead window: %v\n", err)
|
||||
|
|
@ -1963,8 +1961,8 @@ func handleTasksWindow(w *acme.Win) {
|
|||
} else if err := deleteBead(arg, windowMount); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error deleting bead: %v\n", err)
|
||||
} else {
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
beads, _ = listBeadsWithFilter(filter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount, filter)
|
||||
}
|
||||
case "Init":
|
||||
prefix := arg
|
||||
|
|
@ -1974,8 +1972,8 @@ func handleTasksWindow(w *acme.Win) {
|
|||
if err := initBeads(prefix, windowMount); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error initializing beads: %v\n", err)
|
||||
} else {
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
beads, _ = listBeadsWithFilter(filter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount, filter)
|
||||
}
|
||||
case "Mount":
|
||||
cwd := strings.TrimSpace(arg)
|
||||
|
|
@ -1990,10 +1988,9 @@ func handleTasksWindow(w *acme.Win) {
|
|||
if err := mountProject(cwd, name); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error mounting: %v\n", err)
|
||||
} else {
|
||||
// "" = name
|
||||
windowMount = name
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
beads, _ = listBeadsWithFilter(filter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount, filter)
|
||||
}
|
||||
}
|
||||
case "Umount":
|
||||
|
|
@ -2003,7 +2000,6 @@ func handleTasksWindow(w *acme.Win) {
|
|||
} else if err := umountProject(name); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error unmounting: %v\n", err)
|
||||
} else {
|
||||
// If we unmounted the current window mount, pick first available mount
|
||||
if windowMount == name {
|
||||
data, _ := readFile("beads/mtab")
|
||||
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
|
|
@ -2013,27 +2009,24 @@ func handleTasksWindow(w *acme.Win) {
|
|||
parts := strings.Split(line, "\t")
|
||||
if len(parts) > 0 {
|
||||
windowMount = parts[0]
|
||||
// "" = parts[0]
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// No mounts left, clear the window
|
||||
windowMount = ""
|
||||
beads = nil
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount, filter)
|
||||
continue
|
||||
}
|
||||
}
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
beads, _ = listBeadsWithFilter(filter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount, filter)
|
||||
}
|
||||
case "Select":
|
||||
name := strings.TrimSpace(arg)
|
||||
if name == "" {
|
||||
// Display mtab
|
||||
data, err := readFile("beads/mtab")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading mtab: %v\n", err)
|
||||
|
|
@ -2042,7 +2035,6 @@ func handleTasksWindow(w *acme.Win) {
|
|||
fmt.Fprintf(os.Stderr, "Usage: select mount name, then Select\n")
|
||||
}
|
||||
} else {
|
||||
// Verify mount exists
|
||||
data, err := readFile("beads/mtab")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading mtab: %v\n", err)
|
||||
|
|
@ -2057,37 +2049,20 @@ func handleTasksWindow(w *acme.Win) {
|
|||
if !found {
|
||||
fmt.Fprintf(os.Stderr, "Mount %s not found\n", name)
|
||||
} else {
|
||||
// "" = name
|
||||
windowMount = name
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
beads, _ = listBeadsWithFilter(filter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount, filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "Open":
|
||||
statusFilter = "open"
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
case "InProgress":
|
||||
statusFilter = "in_progress"
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
case "Closed":
|
||||
statusFilter = "closed"
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
case "Failed":
|
||||
statusFilter = "failed"
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
case "PendingReview":
|
||||
statusFilter = "pending_review"
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
case "PendingApproval":
|
||||
statusFilter = "pending_approval"
|
||||
beads, _ = listBeadsWithFilter(statusFilter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount)
|
||||
case "All":
|
||||
filter = "all"
|
||||
beads, _ = listBeadsWithFilter(filter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount, filter)
|
||||
case "Ready":
|
||||
filter = "ready"
|
||||
beads, _ = listBeadsWithFilter(filter, windowMount)
|
||||
refreshTasksWindowWithBeads(w, beads, windowMount, filter)
|
||||
default:
|
||||
w.WriteEvent(e)
|
||||
}
|
||||
|
|
@ -2106,7 +2081,7 @@ func handleTasksWindow(w *acme.Win) {
|
|||
}
|
||||
}
|
||||
|
||||
func refreshTasksWindowWithBeads(w *acme.Win, beads []Bead, mount string) {
|
||||
func refreshTasksWindowWithBeads(w *acme.Win, beads []Bead, mount string, filter string) {
|
||||
if mount == "" {
|
||||
w.Name("/AnviLLM/Tasks")
|
||||
} else {
|
||||
|
|
@ -2121,7 +2096,7 @@ func refreshTasksWindowWithBeads(w *acme.Win, beads []Bead, mount string) {
|
|||
buf.WriteString(" 1. Select a directory path, then click Mount\n")
|
||||
buf.WriteString(" 2. Or click Select to see available mounts\n\n")
|
||||
} else {
|
||||
buf.WriteString("[Open] [InProgress] [Closed] [Failed] [PendingReview] [PendingApproval]\n\n")
|
||||
buf.WriteString("[All] [Ready]\n\n")
|
||||
buf.WriteString(fmt.Sprintf("%-4s %-12s %-12s %-4s %-8s %s\n", "#", "ID", "Status", "Blk", "Assignee", "Title"))
|
||||
buf.WriteString(fmt.Sprintf("%-4s %-12s %-12s %-4s %-8s %s\n", "----", "------------", "------------", "----", "--------", strings.Repeat("-", 50)))
|
||||
|
||||
|
|
@ -2146,12 +2121,23 @@ func refreshTasksWindowWithBeads(w *acme.Win, beads []Bead, mount string) {
|
|||
w.Ctl("show")
|
||||
}
|
||||
|
||||
func listBeadsWithFilter(statusFilter string, mount string) ([]Bead, error) {
|
||||
func listBeadsWithFilter(filter string, mount string) ([]Bead, error) {
|
||||
if !isConnected() {
|
||||
return nil, fmt.Errorf("not connected to anvilsrv")
|
||||
}
|
||||
|
||||
data, err := readFile(filepath.Join("beads", mount, "list"))
|
||||
if mount == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var endpoint string
|
||||
if filter == "ready" {
|
||||
endpoint = filepath.Join("beads", mount, "ready")
|
||||
} else {
|
||||
endpoint = filepath.Join("beads", mount, "list")
|
||||
}
|
||||
|
||||
data, err := readFile(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -2160,38 +2146,7 @@ func listBeadsWithFilter(statusFilter string, mount string) ([]Bead, error) {
|
|||
if err := json.Unmarshal(data, &beads); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply status filter
|
||||
if statusFilter == "" {
|
||||
// Default: filter out closed beads
|
||||
var openBeads []Bead
|
||||
for _, b := range beads {
|
||||
if b.Status != "closed" {
|
||||
openBeads = append(openBeads, b)
|
||||
}
|
||||
}
|
||||
return openBeads, nil
|
||||
}
|
||||
|
||||
// Special case: "failed" means closed beads whose close_reason is not "completed"
|
||||
if statusFilter == "failed" {
|
||||
var filtered []Bead
|
||||
for _, b := range beads {
|
||||
if b.Status == "closed" && b.CloseReason != "completed" {
|
||||
filtered = append(filtered, b)
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// Filter by specific status (covers open, in_progress, closed, pending_review, pending_approval)
|
||||
var filtered []Bead
|
||||
for _, b := range beads {
|
||||
if b.Status == statusFilter {
|
||||
filtered = append(filtered, b)
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
return beads, nil
|
||||
}
|
||||
|
||||
func openNewBeadWindow(parentID string, mount string) error {
|
||||
|
|
@ -2296,42 +2251,16 @@ func createBeadFromMarkdown(content, parentID string, mount string) error {
|
|||
return fmt.Errorf("title is required")
|
||||
}
|
||||
|
||||
// Build command: new 'title' 'description' [parent-id]
|
||||
// Build command: new 'title' 'description' [parent-id] [blockers=...]
|
||||
cmd := fmt.Sprintf("new '%s' '%s'", title, description)
|
||||
if parentID != "" {
|
||||
cmd = fmt.Sprintf("%s %s", cmd, parentID)
|
||||
}
|
||||
|
||||
if err := writeFile(filepath.Join("beads", mount, "ctl"), []byte(cmd)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add blockers if specified
|
||||
if blockers != "" {
|
||||
// Need to find the newly created bead ID
|
||||
beads, err := listBeadsWithFilter("", mount)
|
||||
if err != nil {
|
||||
return nil // bead created, deps failed
|
||||
}
|
||||
var newID string
|
||||
for _, b := range beads {
|
||||
if b.Title == title {
|
||||
newID = b.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
if newID != "" {
|
||||
for _, blockerID := range strings.Split(blockers, ",") {
|
||||
blockerID = strings.TrimSpace(blockerID)
|
||||
if blockerID != "" {
|
||||
// blockerID blocks newID
|
||||
addBlocksDep(blockerID, newID, mount)
|
||||
}
|
||||
}
|
||||
}
|
||||
cmd = fmt.Sprintf("%s blockers=%s", cmd, blockers)
|
||||
}
|
||||
|
||||
return nil
|
||||
return writeFile(filepath.Join("beads", mount, "ctl"), []byte(cmd))
|
||||
}
|
||||
|
||||
func openViewBeadWindow(beadID string, mount string) error {
|
||||
|
|
|
|||
|
|
@ -231,9 +231,21 @@ func (b *BeadsFS) readFromMount(m *MountedProject, endpoint string) ([]byte, err
|
|||
parts := strings.Split(endpoint, "/")
|
||||
switch {
|
||||
case len(parts) == 1 && parts[0] == "list":
|
||||
return b.readListFromStore(m.store)
|
||||
return b.readListFromStore(m.store, 100)
|
||||
case len(parts) == 2 && parts[0] == "list":
|
||||
limit, _ := strconv.Atoi(parts[1])
|
||||
if limit == 0 {
|
||||
limit = 100
|
||||
}
|
||||
return b.readListFromStore(m.store, limit)
|
||||
case len(parts) == 1 && parts[0] == "ready":
|
||||
return b.readReadyFromStore(m.store)
|
||||
return b.readReadyFromStore(m.store, 100)
|
||||
case len(parts) == 2 && parts[0] == "ready":
|
||||
limit, _ := strconv.Atoi(parts[1])
|
||||
if limit == 0 {
|
||||
limit = 100
|
||||
}
|
||||
return b.readReadyFromStore(m.store, limit)
|
||||
case len(parts) == 1 && parts[0] == "pending":
|
||||
return b.readPendingFromStore(m.store)
|
||||
case len(parts) == 1 && parts[0] == "blocked":
|
||||
|
|
@ -338,8 +350,12 @@ func (b *BeadsFS) readMtab() ([]byte, error) {
|
|||
return []byte(strings.Join(lines, "\n")), nil
|
||||
}
|
||||
|
||||
func (b *BeadsFS) readListFromStore(store bd.Storage) ([]byte, error) {
|
||||
issues, err := store.SearchIssues(b.ctx, "", bd.IssueFilter{})
|
||||
func (b *BeadsFS) readListFromStore(store bd.Storage, limit int) ([]byte, error) {
|
||||
filter := bd.IssueFilter{
|
||||
ExcludeStatus: []bd.Status{bd.StatusClosed},
|
||||
Limit: limit,
|
||||
}
|
||||
issues, err := store.SearchIssues(b.ctx, "", filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -358,7 +374,7 @@ func (b *BeadsFS) readListFromStore(store bd.Storage) ([]byte, error) {
|
|||
CapabilityLevel string `json:"capability_level,omitempty"`
|
||||
CommentCount int `json:"comment_count,omitempty"`
|
||||
}
|
||||
result := make([]BeadWithBlockers, len(issues))
|
||||
items := make([]BeadWithBlockers, len(issues))
|
||||
for i, issue := range issues {
|
||||
// Fetch labels if not already populated
|
||||
if issue.Labels == nil {
|
||||
|
|
@ -367,26 +383,26 @@ func (b *BeadsFS) readListFromStore(store bd.Storage) ([]byte, error) {
|
|||
issue.Labels = labels
|
||||
}
|
||||
}
|
||||
result[i].Issue = issue
|
||||
items[i].Issue = issue
|
||||
blockers, err := b.getBlockersFromStore(store, issue.ID)
|
||||
if err != nil {
|
||||
logging.Logger().Warn("failed to get blockers", zap.String("issue", issue.ID), zap.Error(err))
|
||||
} else if len(blockers) > 0 {
|
||||
result[i].Blockers = blockers
|
||||
items[i].Blockers = blockers
|
||||
}
|
||||
result[i].CapabilityLevel = extractCapabilityLevel(issue.Labels)
|
||||
items[i].CapabilityLevel = extractCapabilityLevel(issue.Labels)
|
||||
comments, err := store.GetIssueComments(b.ctx, issue.ID)
|
||||
if err == nil && len(comments) > 0 {
|
||||
result[i].CommentCount = len(comments)
|
||||
items[i].CommentCount = len(comments)
|
||||
}
|
||||
}
|
||||
|
||||
return json.MarshalIndent(result, "", " ")
|
||||
return json.MarshalIndent(items, "", " ")
|
||||
}
|
||||
|
||||
|
||||
func (b *BeadsFS) readReadyFromStore(store bd.Storage) ([]byte, error) {
|
||||
filter := bd.WorkFilter{}
|
||||
func (b *BeadsFS) readReadyFromStore(store bd.Storage, limit int) ([]byte, error) {
|
||||
filter := bd.WorkFilter{Limit: limit}
|
||||
issues, err := store.GetReadyWork(b.ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -397,7 +413,6 @@ func (b *BeadsFS) readReadyFromStore(store bd.Storage) ([]byte, error) {
|
|||
// Filter out blocked issues - ready means unblocked
|
||||
ready := []*bd.Issue{}
|
||||
for _, issue := range issues {
|
||||
// Fetch labels if not already populated
|
||||
if issue.Labels == nil {
|
||||
labels, err := store.GetLabels(b.ctx, issue.ID)
|
||||
if err == nil {
|
||||
|
|
@ -409,6 +424,7 @@ func (b *BeadsFS) readReadyFromStore(store bd.Storage) ([]byte, error) {
|
|||
ready = append(ready, issue)
|
||||
}
|
||||
}
|
||||
|
||||
return json.MarshalIndent(ready, "", " ")
|
||||
}
|
||||
|
||||
|
|
@ -695,18 +711,25 @@ func (b *BeadsFS) executeCtlOnStore(store bd.Storage, cmd string) error {
|
|||
|
||||
case "new", "create":
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("usage: new 'title' ['description'] [parent-id] [--no-lint] [capability=low|standard|high]")
|
||||
return fmt.Errorf("usage: new 'title' ['description'] [parent-id] [--no-lint] [capability=low|standard|high] [blockers=id1,id2,...]")
|
||||
}
|
||||
|
||||
// Strip --no-lint flag and capability= option from args before positional parsing.
|
||||
// Strip flags and options from args before positional parsing.
|
||||
noLint := false
|
||||
capLevel := ""
|
||||
var blockerIDs []string
|
||||
filtered := args[:0]
|
||||
for _, a := range args {
|
||||
if a == "--no-lint" {
|
||||
noLint = true
|
||||
} else if level, ok := strings.CutPrefix(a, "capability="); ok {
|
||||
capLevel = level
|
||||
} else if ids, ok := strings.CutPrefix(a, "blockers="); ok {
|
||||
for _, id := range strings.Split(ids, ",") {
|
||||
if id = strings.TrimSpace(id); id != "" {
|
||||
blockerIDs = append(blockerIDs, id)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filtered = append(filtered, a)
|
||||
}
|
||||
|
|
@ -724,8 +747,6 @@ func (b *BeadsFS) executeCtlOnStore(store bd.Storage, cmd string) error {
|
|||
}
|
||||
|
||||
// Emit lint warnings unless --no-lint or issue_type=idea.
|
||||
// The new command always creates TypeTask; idea exemption applies
|
||||
// when the title starts with "IDEA:" as a convention.
|
||||
isIdea := strings.HasPrefix(strings.ToUpper(title), "IDEA:")
|
||||
if !noLint && !isIdea && description != "" {
|
||||
for _, w := range lintDescription(description) {
|
||||
|
|
@ -733,29 +754,45 @@ func (b *BeadsFS) executeCtlOnStore(store bd.Storage, cmd string) error {
|
|||
}
|
||||
}
|
||||
|
||||
var newID string
|
||||
if parentID != "" {
|
||||
childID, err := b.createSubtaskOnStore(store, parentID, title, description, actor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Apply capability label if provided.
|
||||
newID = childID
|
||||
if capLevel != "" {
|
||||
_ = store.AddLabel(b.ctx, childID, capabilityPrefix+capLevel, actor)
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
issue := &bd.Issue{
|
||||
Title: title,
|
||||
Description: description,
|
||||
Status: bd.StatusOpen,
|
||||
IssueType: bd.TypeTask,
|
||||
Priority: 2,
|
||||
}
|
||||
if capLevel != "" {
|
||||
issue.Labels = []string{capabilityPrefix + capLevel}
|
||||
}
|
||||
if err := store.CreateIssue(b.ctx, issue, actor); err != nil {
|
||||
return err
|
||||
}
|
||||
newID = issue.ID
|
||||
}
|
||||
|
||||
issue := &bd.Issue{
|
||||
Title: title,
|
||||
Description: description,
|
||||
Status: bd.StatusOpen,
|
||||
IssueType: bd.TypeTask,
|
||||
Priority: 2,
|
||||
// Add blockers
|
||||
for _, blockerID := range blockerIDs {
|
||||
dep := &bd.Dependency{
|
||||
IssueID: newID,
|
||||
DependsOnID: blockerID,
|
||||
Type: bd.DepBlocks,
|
||||
}
|
||||
if err := store.AddDependency(b.ctx, dep, actor); err != nil {
|
||||
logging.Logger().Warn("failed to add blocker", zap.String("bead", newID), zap.String("blocker", blockerID), zap.Error(err))
|
||||
}
|
||||
}
|
||||
if capLevel != "" {
|
||||
issue.Labels = []string{capabilityPrefix + capLevel}
|
||||
}
|
||||
return store.CreateIssue(b.ctx, issue, actor)
|
||||
return nil
|
||||
|
||||
case "claim":
|
||||
if len(args) < 1 {
|
||||
|
|
|
|||
Reference in New Issue