examples/library-usage/README.md
This example demonstrates using Beads as a Go library in external projects (like VC).
Instead of spawning bd CLI processes:
In your Go project:
go get github.com/steveyegge/beads@latest
package main
import (
"context"
"log"
"github.com/steveyegge/beads"
)
func main() {
ctx := context.Background()
// Find the workspace and open its configured Dolt implementation
beadsDir := beads.FindBeadsDir()
store, err := beads.OpenBestAvailable(ctx, beadsDir)
if err != nil {
log.Fatal(err)
}
defer store.Close()
// Get ready work
ready, err := store.GetReadyWork(ctx, beads.WorkFilter{
Status: beads.StatusOpen,
Limit: 10,
})
if err != nil {
log.Fatal(err)
}
// Process ready issues...
}
WorkFilter offers two status fields:
Status filters to exactly one status.Statuses filters to any of the given statuses (OR semantics) in a single
query, so multi-status callers avoid one GetReadyWork round trip per
status. Custom statuses participate like built-ins.Status takes precedence: when it is set, Statuses is ignored. When both
are empty, the default of ('open', 'in_progress') applies.
// One query for open OR blocked ready work
ready, err := store.GetReadyWork(ctx, beads.WorkFilter{
Statuses: []beads.Status{beads.StatusOpen, beads.StatusBlocked},
Limit: 10,
})
# From this directory
cd examples/library-usage
# Make sure there's a Beads database
bd init --prefix demo
# Run the example
go run main.go
The beads.Storage interface provides:
CreateIssue(ctx, issue, actor) - Create a new issueCreateIssues(ctx, issues, actor) - Batch create issuesGetIssue(ctx, id) - Get issue by IDUpdateIssue(ctx, id, updates, actor) - Update issue fieldsUpdateIssueChecked(ctx, id, updates, actor, opts) - Update with an optional
optimistic-concurrency (CAS) precondition (opts.ExpectedVersion); see
Concurrency belowCloseIssue(ctx, id, reason, actor) - Close an issueSearchIssues(ctx, query, filter) - Search with filtersAddDependency(ctx, dep, actor) - Add dependency between issuesRemoveDependency(ctx, issueID, dependsOnID, actor) - Remove dependencyGetDependencies(ctx, issueID) - Get what this issue depends onGetDependents(ctx, issueID) - Get what depends on this issueGetDependencyTree(ctx, issueID, maxDepth, showAllPaths) - Visualize treeAddLabel(ctx, issueID, label, actor) - Add label to issueRemoveLabel(ctx, issueID, label, actor) - Remove labelGetLabels(ctx, issueID) - Get all labels for an issueGetIssuesByLabel(ctx, label) - Find issues with labelGetReadyWork(ctx, filter) - Find issues with no blockersGetBlockedIssues(ctx) - Find blocked issues with blocker infoGetEpicsEligibleForClosure(ctx) - Find completable epicsAddIssueComment(ctx, issueID, author, text) - Add commentGetIssueComments(ctx, issueID) - Get all commentsGetEvents(ctx, issueID, limit) - Get audit trailGetStatistics(ctx) - Get aggregate metricsUpdateIssueChecked(ctx, id, updates, actor, opts) - Like UpdateIssue, but
when opts.ExpectedVersion is set the update proceeds only if the issue's
current RowVersion still matches, else it refuses with
beads.ErrVersionMismatch. The version read and the write share one
transaction (a true compare-and-swap). RowVersion is surfaced read-only on
Issue reads.MergeMetadata(ctx, issueID, key, value, actor) - Atomically merge a single
key into an issue's metadata JSON. Two concurrent merges of different keys
both survive rather than clobbering each other.Implementer note (interface change):
UpdateIssueCheckedandMergeMetadataare now required methods on thebeads.Storageinterface. Code that only consumesbeads.Storageneeds no change, but any external type that implementsbeads.Storage(a custom store, mock, or proxy) must add these two methods to compile. If you only need the base behavior,UpdateIssueCheckedwith a nilopts.ExpectedVersionis identical toUpdateIssue, andMergeMetadatacan be layered over a read-modify-write of the issue's metadata.
All types are exported via the beads package:
// Core types
beads.Issue
beads.Status (Open, InProgress, Closed, Blocked)
beads.IssueType (Bug, Feature, Task, Epic, Chore)
beads.Priority (0-4)
// Relationships
beads.Dependency
beads.DependencyType (Blocks, Related, ParentChild, DiscoveredFrom)
// Metadata
beads.Label
beads.Comment
beads.Event
// Queries
beads.IssueFilter
beads.WorkFilter
beads.BlockedIssue
beads.EpicStatus
beads.Statistics
For VC (VibeCoder), the integration would look like:
// In VC's storage layer
type VCStorage struct {
beads beads.Storage
}
func NewVCStorage(ctx context.Context, beadsDir string) (*VCStorage, error) {
store, err := beads.OpenBestAvailable(ctx, beadsDir)
if err != nil {
return nil, err
}
return &VCStorage{beads: store}, nil
}
// Claim ready work for executor
func (s *VCStorage) ClaimWork(ctx context.Context, executorID string) (*beads.Issue, error) {
ready, err := s.beads.GetReadyWork(ctx, beads.WorkFilter{
Status: beads.StatusOpen,
Limit: 1,
})
if err != nil {
return nil, err
}
if len(ready) == 0 {
return nil, nil // No work available
}
issue := ready[0]
// Claim it
updates := map[string]interface{}{
"status": beads.StatusInProgress,
"assignee": executorID,
}
if err := s.beads.UpdateIssue(ctx, issue.ID, updates, executorID); err != nil {
return nil, err
}
return issue, nil
}
context.Context for cancellation supportdefer store.Close() after opening