beads

package
v0.2.16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package beads is a read-only adapter over the `bd` (beads) issue-tracker CLI.

It shells `bd -C <root> … --json` for every `.beads/`-enabled repo and caches the priority-sorted ready queue, blocked items, and the dependency-graph edges so the dashboard can render a live Backlog view. It never reads the `.beads/` directory directly (that is a binary Dolt DB) — the CLI with --json is the only supported read path. Missing `bd`, a repo without `.beads/`, or a failing call all degrade to an empty/per-repo-error state rather than an error.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ValidID

func ValidID(id string) bool

ValidID reports whether id is a safe bd issue id to pass as an argv value: non-empty, not flag-like, and limited to the bd id charset [A-Za-z0-9._-]. Handlers use it to reject a hostile path param with 400 before shelling bd.

func ValidPriority

func ValidPriority(p string) bool

ValidPriority reports whether p is a single digit 0..4.

func ValidTitle

func ValidTitle(s string) bool

ValidTitle reports whether s is an acceptable single-line issue title: non-empty, <= 500 bytes, and free of control characters (which would corrupt every later `bd … --json` read or blow past ARG_MAX). The caller trims first.

func ValidType

func ValidType(t string) bool

ValidType reports whether t is a bd issue type accepted by create.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client shells the resolved bd binary. All reads go through it; the .beads/ directory is never touched directly. writeMu serializes mutations because bd's embedded Dolt is single-writer per repo; reads stay unlocked.

func New

func New() (*Client, bool)

New resolves the bd binary. ok is false when bd is not installed, in which case the beads feature stays dark (graceful degradation).

func (*Client) Blocked

func (c *Client) Blocked(ctx context.Context, root string) ([]Issue, error)

Blocked returns open issues with an open blocker (blocked_by populated).

func (*Client) Claim

func (c *Client) Claim(ctx context.Context, root, id string) error

Claim sets the issue in_progress + assigned to the caller (bd --claim is idempotent).

func (*Client) Close

func (c *Client) Close(ctx context.Context, root, id, reason string) error

Close closes the issue, recording an optional reason.

func (*Client) Comments

func (c *Client) Comments(ctx context.Context, root, id string) (string, error)

Comments returns this issue's comments (textual).

func (*Client) Create

func (c *Client) Create(ctx context.Context, root, title, itype, priority, description string) (string, error)

Create makes a new issue and returns its id. bd --silent prints only the id, so all values pass as --flag=value (equals form, argv-safe under exec.Command).

func (*Client) DepList

func (c *Client) DepList(ctx context.Context, root, id string) (string, error)

DepList returns the textual list of this issue's blockers.

func (*Client) DepTree

func (c *Client) DepTree(ctx context.Context, root, id, dir string) (string, error)

DepTree returns a mermaid flowchart of the dependency chain. dir is "down" (blockers), "up" (dependents), or "both".

func (*Client) List

func (c *Client) List(ctx context.Context, root string) ([]Issue, error)

List returns all non-closed issues (open, in_progress, blocked, deferred) — the source of graph node attributes + parent links. `bd list` returns every issue including closed; we drop closed so the graph doesn't sprout resolved nodes, but keep in_progress (a claimed issue must still render with its real title/priority, not as a stub).

func (*Client) Ready

func (c *Client) Ready(ctx context.Context, root string) ([]Issue, error)

Ready returns the priority-sorted, dependency-clear queue.

func (*Client) Show

func (c *Client) Show(ctx context.Context, root, id string) (Issue, error)

Show returns one issue's fields (no edges/comments — fetch those separately).

func (*Client) Status

func (c *Client) Status(ctx context.Context, root string) (Counts, error)

Status returns the per-repo counts summary.

type Counts

type Counts struct {
	Open       int `json:"open"`
	Ready      int `json:"ready"`
	Blocked    int `json:"blocked"`
	InProgress int `json:"in_progress"`
	Total      int `json:"total"`
}

Counts is the per-repo summary from `bd status --json`.

type Edge

type Edge struct {
	From string `json:"from"`
	To   string `json:"to"`
	Kind string `json:"kind"`
}

Edge is a directed dependency-graph edge. Kind is "blocks" (From blocks To) or "parent" (From is the parent of To).

type Issue

type Issue struct {
	ID              string   `json:"id"`
	Title           string   `json:"title"`
	Description     string   `json:"description,omitempty"`
	Status          string   `json:"status"`
	IssueType       string   `json:"issue_type"`
	Owner           string   `json:"owner,omitempty"`
	Priority        int      `json:"priority"`
	Labels          []string `json:"labels,omitempty"`
	DependencyCount int      `json:"dependency_count"`
	DependentCount  int      `json:"dependent_count"`
	CommentCount    int      `json:"comment_count"`
	Created         string   `json:"created_at,omitempty"`
	Updated         string   `json:"updated_at,omitempty"`
	Parent          string   `json:"parent,omitempty"`
	BlockedBy       []string `json:"blocked_by,omitempty"`
}

Issue is one bd issue. Only the fields we consume are declared; unknown JSON keys are ignored so a newer bd stays forward-compatible.

type Monitor

type Monitor struct {
	// contains filtered or unexported fields
}

Monitor polls beads repos on an interval and caches a Snapshot. It re-discovers repos each tick (via the repos thunk) so a newly-initialized repo appears live. When the snapshot's data fingerprint changes, onChange fires (the server wires it to an SSE broadcast). Safe for concurrent use.

func NewMonitor

func NewMonitor(f fetcher, repos func() []Repo, interval time.Duration, onChange func()) *Monitor

NewMonitor builds a Monitor. A non-positive interval defaults to 15s.

func (*Monitor) RefreshNow

func (m *Monitor) RefreshNow(ctx context.Context)

RefreshNow forces an immediate refresh so a mutation shows without waiting for the next tick. Safe on a nil Monitor.

func (*Monitor) Snapshot

func (m *Monitor) Snapshot() Snapshot

Snapshot returns the cached snapshot. Safe on a nil Monitor (empty snapshot).

func (*Monitor) Start

func (m *Monitor) Start(ctx context.Context)

Start refreshes once immediately, then on every interval until ctx is done. Non-blocking; safe on a nil Monitor (no-op).

type Repo

type Repo struct {
	Name string `json:"name"`
	Root string `json:"root"`
}

Repo is a discovered beads-enabled repository root.

func Discover

func Discover(scanRoots, explicit []string) []Repo

Discover finds beads repos: depth-1 children of scanRoots that hold a .beads/ directory, plus every explicit root. Deduped by absolute path. Keys on .beads/ (not .docs/ai) because greenfield repos have the former without the latter.

type RepoSnapshot

type RepoSnapshot struct {
	Name    string  `json:"name"`
	Root    string  `json:"root"`
	Ready   []Issue `json:"ready"`
	Blocked []Issue `json:"blocked"`
	All     []Issue `json:"all"`
	Edges   []Edge  `json:"edges"`
	Counts  Counts  `json:"counts"`
	Err     string  `json:"err,omitempty"`
}

RepoSnapshot is one repo's beads state for a refresh cycle. A non-empty Err isolates a repo whose `bd` calls failed; other repos still render.

type Snapshot

type Snapshot struct {
	Repos     []RepoSnapshot `json:"repos"`
	Updated   string         `json:"updated"`
	Available bool           `json:"available"`
}

Snapshot is the whole dashboard's cached beads state. Available is false when the `bd` binary is not found or the feature is disabled.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL