cli

package
v0.2.4 Latest Latest
Warning

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

Go to latest
Published: Feb 18, 2026 License: MIT Imports: 34 Imported by: 0

Documentation

Index

Constants

View Source
const (
	StateActive = "active" // Claude is executing (planning/working)
	StateIdle   = "idle"   // Polling for tasks, no work available
)

Lock states for auto mode tracking

View Source
const LockFileName = ".agent.lock"

LockFileName is the name of the lock file in each worktree

View Source
const NeedsRevisionLabel = "needs-revision"

NeedsRevisionLabel is added when a plan review is rejected. Issues with this label are treated as needing re-planning even if they have a design.

Variables

View Source
var (
	Version = "dev"
	Build   = "unknown"
)

Version information (set at build time via ldflags)

View Source
var IsTmuxAvailable = func() bool {
	return exec.Command("tmux", "-V").Run() == nil
}

IsTmuxAvailable checks if tmux is installed and available. It is a variable so tests can override it.

Functions

func AcquireLock

func AcquireLock(worktreePath, command, agentName string) error

AcquireLock attempts to acquire an agent lock for the worktree Returns an error if an agent is already running Uses atomic file creation (O_EXCL) to prevent race conditions

func BranchExistsLocally

func BranchExistsLocally(dir, branch string) (bool, error)

BranchExistsLocally checks if a branch exists as a local ref.

func ClearLockTaskID

func ClearLockTaskID(worktreePath string) error

ClearLockTaskID resets the TaskID, TaskTitle, and TaskStartedAt in the lock file. Called before each agent session in auto mode so we can detect whether the new session claims a task (used for no-progress detection).

func DetectIntegrationBranch

func DetectIntegrationBranch(worktrees []WorktreeInfo) string

DetectIntegrationBranch analyzes worktree branches to find a common integration branch that is closer than main. Returns empty string if no such branch is found.

func EnsureBdDaemonRunning

func EnsureBdDaemonRunning(timeout time.Duration) (bool, error)

EnsureBdDaemonRunning checks if the bd daemon is running and starts it if not. Returns (true, nil) if we started the daemon, (false, nil) if it was already running, or (false, err) if the daemon could not be started or did not become ready in time.

func EnsureEpicPR

func EnsureEpicPR(worktreePath, epicID string) error

EnsureEpicPR checks if a PR exists for the epic branch and creates one if needed. This is non-fatal — errors are returned but should not block agent restarts.

func EnsureWorktreeBranch

func EnsureWorktreeBranch(worktreePath, targetBranch, fallbackRef string) error

EnsureWorktreeBranch switches the worktree to the target branch. If already on the target branch, it's a no-op. If the working tree is dirty, a WIP commit is created before switching. The fallbackRef (e.g. "origin/main") is used when creating a brand-new branch.

func Execute

func Execute() error

Execute runs the root command

func FilterEnv

func FilterEnv(env []string) []string

FilterEnv filters the given environment variable slice through the allowlist. Only variables whose names match an exact allowlist entry or an allowed prefix are included. Malformed entries (missing '=') are excluded.

func FilteredEnv

func FilteredEnv() []string

FilteredEnv returns os.Environ() filtered through the allowlist. Use this instead of os.Environ() when setting up subprocess environments.

func GenerateConflictResolutionPrompt

func GenerateConflictResolutionPrompt(sourceBranch, targetBranch string, conflicts []string) string

GenerateConflictResolutionPrompt creates the prompt for merge conflict resolution

func GenerateFleetPlanningPrompt

func GenerateFleetPlanningPrompt(agentName, taskID string, workspace *WorkspaceConfig) string

GenerateFleetPlanningPrompt creates the prompt for a fleet planning agent with a pre-assigned task. Fleet workers receive their task from the Fleet API and skip task selection/claiming.

func GenerateFleetTaskPrompt

func GenerateFleetTaskPrompt(agentName, taskID string, workspace *WorkspaceConfig, backendName string) string

GenerateFleetTaskPrompt creates the prompt for a fleet implementation agent with a pre-assigned task. Fleet workers receive their task from the Fleet API and skip task selection/claiming.

func GenerateLeadPrompt

func GenerateLeadPrompt() string

GenerateLeadPrompt creates the prompt for the interactive lead/manager mode

func GeneratePlanningPrompt

func GeneratePlanningPrompt(agentName string, workspace *WorkspaceConfig, parentID string) string

GeneratePlanningPrompt creates the prompt for the planning agent. If workspace is non-nil, workspace context is injected into the prompt. If parentID is non-empty, the prompt scopes task discovery to that epic. SYNC: The jq filters below must match taskfilter.go NeedsPlan() criteria:

planning: design empty OR has "needs-revision" label

func GenerateTaskPrompt

func GenerateTaskPrompt(agentName string, workspace *WorkspaceConfig, parentID string, backendName string) string

GenerateTaskPrompt creates the prompt for the implementation agent. If workspace is non-nil, workspace context is injected into the prompt. If parentID is non-empty, the prompt scopes task discovery to that epic. SYNC: The jq filters below must match taskfilter.go ReadyToImplement() criteria:

implementation: design non-empty AND no "needs-revision" label

func GetBackendName

func GetBackendName() string

GetBackendName returns the name of the currently active backend.

func GetBeadsDir

func GetBeadsDir() string

GetBeadsDir returns the directory where .beads/ lives. In workspace mode, this is the workspace root path (shared across repos). In legacy mode, this returns "." (current directory). The result is cached for the lifetime of the process.

func GetConfigDir

func GetConfigDir() string

GetConfigDir returns the loom config directory path. Respects LOOM_CONFIG_DIR env var, otherwise defaults to ~/.loom.

func GetConfigPath

func GetConfigPath() string

GetConfigPath returns the full path to the loom config file.

func GetConflictedFiles

func GetConflictedFiles(dir string) ([]string, error)

GetConflictedFiles returns a list of files with merge conflicts

func GetCurrentBranch

func GetCurrentBranch(path string) (string, error)

GetCurrentBranch returns the current branch for a git directory

func GetDefaultBranch

func GetDefaultBranch() string

GetDefaultBranch returns the default integration branch. Resolution order: LOOM_DEFAULT_BRANCH env var > auto-detected from worktree topology > "main" This is a convenience wrapper that discovers worktrees automatically. When worktrees are already available, use GetDefaultBranchForWorktrees instead.

func GetDefaultBranchForWorktrees

func GetDefaultBranchForWorktrees(worktrees []WorktreeInfo) string

GetDefaultBranchForWorktrees returns the default integration branch using pre-discovered worktrees to avoid redundant filesystem/git operations.

func GetGitBranches

func GetGitBranches() ([]string, error)

GetGitBranches returns all local and remote branch names

func GetLockStatus

func GetLockStatus(worktreePath string) string

GetLockStatus returns a human-readable status for a worktree's lock Uses explicit state words: planning, working, done, review, idle

func GetScriptDir

func GetScriptDir() (string, error)

GetScriptDir returns the directory where loom is run from

func GetSignalFilePath

func GetSignalFilePath(worktreePath string) string

GetSignalFilePath returns the path to the signal file for a given worktree. The signal file is stored in a temporary directory to avoid being deleted by git clean operations.

func GetWorkspaceDir

func GetWorkspaceDir(name string) string

GetWorkspaceDir returns the directory path for a named workspace.

func GetWorktreeName

func GetWorktreeName(path string) string

GetWorktreeName extracts the worktree name from a path

func GetWorktreesDir

func GetWorktreesDir() string

GetWorktreesDir returns the worktrees directory path Priority: --worktrees flag > LOOM_WORKTREES_DIR env var > default "worktrees"

func GitCheckout

func GitCheckout(dir, branch string) error

GitCheckout checks out a branch

func GitCheckoutDetached

func GitCheckoutDetached(dir, ref string) error

GitCheckoutDetached checks out a ref in detached HEAD mode

func GitCheckoutNewFromRef

func GitCheckoutNewFromRef(dir, branch, startPoint string) error

GitCheckoutNewFromRef creates a new local branch at the given starting point.

func GitClean

func GitClean(dir string) error

GitClean removes untracked files and directories

func GitCleanDryRun

func GitCleanDryRun(dir string) (string, error)

GitCleanDryRun returns the list of untracked files that would be removed by git clean

func GitCreateBranchFromHead

func GitCreateBranchFromHead(dir, name string) error

GitCreateBranchFromHead creates a new branch at the current HEAD and switches to it

func GitDeleteBranch

func GitDeleteBranch(dir, name string, force bool) error

GitDeleteBranch deletes a local branch. Use force=true for -D (force delete).

func GitFetch

func GitFetch(dir string) error

GitFetch fetches from origin

func GitFetchRemote

func GitFetchRemote(dir, remote string) error

GitFetchRemote fetches from the specified remote

func GitMerge

func GitMerge(dir, branch, message string) error

GitMerge attempts to merge a branch

func GitMergeOrigin

func GitMergeOrigin(dir, branch, message string) error

GitMergeOrigin attempts to merge origin/branch

func GitMergeRemote

func GitMergeRemote(dir, remote, branch, message string) error

GitMergeRemote attempts to merge remote/branch

func GitPull

func GitPull(dir, branch string) error

GitPull pulls from origin for the current branch

func GitPullRemote

func GitPullRemote(dir, remote, branch string) error

GitPullRemote pulls from the specified remote for the given branch

func GitPush

func GitPush(dir, branch string) error

GitPush pushes to origin

func GitPushForce

func GitPushForce(dir, branch string) error

GitPushForce force pushes to origin

func GitPushRefspec

func GitPushRefspec(dir, remote, localRef, remoteRef string) error

GitPushRefspec pushes a local ref to a different remote ref using a refspec

func GitPushRemote

func GitPushRemote(dir, remote, branch string) error

GitPushRemote pushes to the specified remote

func GitReset

func GitReset(dir, ref string) error

GitReset performs a hard reset to a ref

func GitStash

func GitStash(dir string) (bool, error)

GitStash stashes local changes. Returns true if changes were actually stashed, false if nothing was stashed (e.g. only untracked files, or clean tree).

func GitStashPop

func GitStashPop(dir string) error

GitStashPop pops the most recent stash entry

func HasAnyAvailableTasks

func HasAnyAvailableTasks(parentID string) (bool, error)

HasAnyAvailableTasks checks if there are any ready tasks regardless of design status. Used by custom roles with task_filter=any.

func HasAvailableImplementationTasks

func HasAvailableImplementationTasks(parentID string) (bool, error)

HasAvailableImplementationTasks checks if there are tasks ready for implementation (ready tasks WITH an approved design, excluding tasks with needs-revision label and epics)

func HasAvailablePlanningTasks

func HasAvailablePlanningTasks(parentID string) (bool, error)

HasAvailablePlanningTasks checks if there are tasks that need planning (ready tasks without a design OR with needs-revision label, excluding epics)

func HasCommitsBetween

func HasCommitsBetween(dir, target, source string) (bool, error)

HasCommitsBetween checks if source has commits not in target

func HasCommitsBetweenRemote

func HasCommitsBetweenRemote(dir, remote, target, source string) (bool, error)

HasCommitsBetweenRemote checks if source has commits not in target using a specific remote

func HasDesign

func HasDesign(issue BdIssue) bool

HasDesign returns true if the issue has a non-empty design field.

func HasNeedsRevision

func HasNeedsRevision(issue BdIssue) bool

HasNeedsRevision returns true if the issue has the needs-revision label.

func HasUnclosedBlockers

func HasUnclosedBlockers(deps []Dependency, unclosedIDs map[string]bool) bool

HasUnclosedBlockers returns true if any blocking dependency is still unclosed. unclosedIDs is a set of issue IDs that have NOT been closed yet. A blocker is only considered resolved when its issue is closed.

func HasUnmergedFiles

func HasUnmergedFiles(dir string) (bool, error)

HasUnmergedFiles checks if there are unmerged files in the working tree

func InvokeAgent

func InvokeAgent(workDir, prompt, agentName string) error

InvokeAgent dispatches an interactive invocation to the active backend.

func InvokeAgentForConflicts

func InvokeAgentForConflicts(workDir, sourceBranch, targetBranch string, conflicts []string) error

InvokeAgentForConflicts runs the active backend to resolve merge conflicts.

func InvokeAgentNonInteractive

func InvokeAgentNonInteractive(workDir, prompt, agentName string, shutdown <-chan struct{}) error

InvokeAgentNonInteractive dispatches a non-interactive invocation to the active backend.

func IsAvailableForAny

func IsAvailableForAny(issue BdIssue, unclosedIDs map[string]bool) bool

IsAvailableForAny returns true if the issue can be picked up by any agent regardless of design status: workable and no unclosed blockers.

func IsAvailableForImplementation

func IsAvailableForImplementation(issue BdIssue, unclosedIDs map[string]bool) bool

IsAvailableForImplementation returns true if the issue should be picked up by an implementation agent: workable, no unclosed blockers, and has an approved design.

func IsAvailableForPlanning

func IsAvailableForPlanning(issue BdIssue, unclosedIDs map[string]bool) bool

IsAvailableForPlanning returns true if the issue should be picked up by a planning agent: workable, no unclosed blockers, and needs a plan.

func IsCleanWorkingTree

func IsCleanWorkingTree(dir string) (bool, error)

IsCleanWorkingTree checks if the working tree is clean

func IsEpic

func IsEpic(issue BdIssue) bool

IsEpic returns true if the issue is an epic. Agents never work on epics directly.

func IsOpen

func IsOpen(issue BdIssue) bool

IsOpen returns true if the issue has status "open".

func IsRefCheckedOutInWorktree

func IsRefCheckedOutInWorktree(dir, branch string) (bool, string, error)

IsRefCheckedOutInWorktree checks if a branch is checked out in any worktree. Returns (isCheckedOut, worktreePath, error).

func IsWorkableTask

func IsWorkableTask(issue BdIssue) bool

IsWorkableTask returns true if the issue can be picked up by an agent: status is open and not an epic.

func IsWorkspaceMode

func IsWorkspaceMode() bool

IsWorkspaceMode returns true if a config file exists with at least one workspace defined.

func ListBackends

func ListBackends() []string

ListBackends returns a sorted list of registered backend names.

func LoadPromptTemplate

func LoadPromptTemplate(path string, data PromptData) (string, error)

LoadPromptTemplate reads a prompt template file and executes it with the given data. Returns the rendered prompt string.

func NeedsPlan

func NeedsPlan(issue BdIssue) bool

NeedsPlan returns true if the issue needs planning: either no design, or has the needs-revision label (plan was rejected). SYNC: Must match issueCategory.ts getOpenStatus()

func ReadyToImplement

func ReadyToImplement(issue BdIssue) bool

ReadyToImplement returns true if the issue has an approved design ready for implementation (has design AND no needs-revision label). SYNC: Must match issueCategory.ts getOpenStatus() returning 'ready'

func RecoverWorktree

func RecoverWorktree(worktreePath, agentName string, exitCode int) error

RecoverWorktree provides a programmatic, non-interactive recovery path for daemon use. It wraps the existing recovery helpers with force=true and analyze=false semantics: force-release any stale lock, kill running processes, reset orphaned tasks to open, and clean untracked files without prompting.

exitCode informs recovery behavior: on clean exit (0) tasks that are still in_progress are likely mid-completion (e.g. the agent signaled done but the status update hasn't landed yet), so we log but still run the status-aware resetTask which preserves review/closed tasks. On non-zero exit the task is more likely genuinely orphaned.

func RegisterBackend

func RegisterBackend(b Backend)

RegisterBackend adds a backend to the registry by its Name(). Panics if b is nil (programming error).

func ReleaseLock

func ReleaseLock(worktreePath string) error

ReleaseLock releases the agent lock for the worktree

func RemoteBranchExists

func RemoteBranchExists(dir, remote, branch string) (bool, error)

RemoteBranchExists checks if a branch exists on the specified remote.

func ResetBeadsDirCache

func ResetBeadsDirCache()

ResetBeadsDirCache clears the cached beads directory value. For testing only.

func ResolveAndSetBackend

func ResolveAndSetBackend() error

ResolveAndSetBackend resolves the backend name from the precedence chain and sets it as the active backend. Returns an error if the resolved name is not a registered backend.

func ResolveBackendName

func ResolveBackendName() string

ResolveBackendName returns the backend name using the precedence chain: --backend flag > LOOM_BACKEND env > project-local loom.yaml > global config > default ("claude").

func ResolveDaemonStatePath

func ResolveDaemonStatePath(projectDir string) string

ResolveDaemonStatePath returns the path to daemon-agents.json for the given project directory. It loads the daemon config to determine the PID file location, then returns the state file path adjacent to the PID file. On config load error, falls back to <projectDir>/.loom/daemon-agents.json.

func ResolveLockDir

func ResolveLockDir(path string) string

ResolveLockDir determines the correct directory for the lock file. If the path is inside a workspace (matches a workspace path or repo path), returns the workspace root so all repos share one lock. Otherwise returns the path unchanged (legacy mode).

func ResolveWorktreePath

func ResolveWorktreePath(name string) (string, error)

ResolveWorktreePath converts a worktree name to its full path Accepts:

  • A worktree name (e.g., "falcon") -> ./worktrees/falcon
  • An absolute path (e.g., /path/to/worktree) -> as-is
  • Empty string -> current directory

func ResolveWorktreesDir

func ResolveWorktreesDir() (string, error)

ResolveWorktreesDir returns the absolute path to the worktrees directory If the configured path is absolute, use it directly; otherwise join with scriptDir

func RunAutoModeLoop

func RunAutoModeLoop(opts AutoModeOptions, shutdown chan struct{})

RunAutoModeLoop runs the auto mode loop for either plan or task agents

func RunAutoModeTmux

func RunAutoModeTmux(opts AutoModeOptions, shutdown chan struct{})

RunAutoModeTmux runs auto mode with tmux session management and live streaming

func RunGitCommand

func RunGitCommand(dir string, args ...string) (string, error)

RunGitCommand executes a git command in the specified directory

func RunGitCommandWithOutput

func RunGitCommandWithOutput(dir string, args ...string) error

RunGitCommandWithOutput executes a git command and streams output to stdout/stderr

func SaveConfig

func SaveConfig(cfg *LoomConfig) error

SaveConfig writes the loom config to the config file. Creates the config directory if it doesn't exist.

func SetBackend

func SetBackend(name string) error

SetBackend validates and switches the active backend.

func SetupSignalHandler

func SetupSignalHandler() chan struct{}

SetupSignalHandler sets up graceful shutdown on SIGINT/SIGTERM Returns a channel that will be CLOSED when shutdown is requested (closed channel pattern allows multiple goroutines to detect shutdown)

func UpdateLockState

func UpdateLockState(worktreePath, state string) error

UpdateLockState updates the lock file with current execution state Used by auto mode to distinguish idle (polling) from active (executing Claude)

func UpdateLockTask

func UpdateLockTask(worktreePath, taskID, taskTitle string) error

UpdateLockTask updates the lock file with task information This is called by Claude after picking a task to work on

func ValidBackendNames

func ValidBackendNames() string

ValidBackendNames returns a formatted string of valid backend names for help text.

func ValidateRemoteName

func ValidateRemoteName(name string) error

ValidateRemoteName checks if a remote name is safe for use in git commands. Empty is allowed (resolveRemote defaults it to "origin").

Types

type AgentEntry

type AgentEntry struct {
	Worktree string `yaml:"worktree"`
	Role     string `yaml:"role"`
	Auto     bool   `yaml:"auto,omitempty"`
	Backend  string `yaml:"backend,omitempty"`
}

AgentEntry defines a single agent assignment.

type AgentProcess

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

AgentProcess tracks a single supervised agent subprocess.

type AgentStatus

type AgentStatus struct {
	Name          string         `json:"name"`
	Branch        string         `json:"branch"`
	Status        string         `json:"status"`                   // "ready", "3 changes", "running (plan, 5m ago)"
	Ahead         int            `json:"ahead"`                    // commits ahead of integration branch
	Behind        int            `json:"behind"`                   // commits behind integration branch
	Role          string         `json:"role,omitempty"`           // role from daemon config (e.g., "plan", "task")
	Workspace     string         `json:"workspace"`                // workspace name (empty in legacy mode)
	DaemonManaged bool           `json:"daemon_managed,omitempty"` // true if under daemon supervision
	Commits       []CommitDetail `json:"commits,omitempty"`        // recent commits ahead of integration branch
	Changes       []FileChange   `json:"changes,omitempty"`        // uncommitted file changes
}

AgentStatus represents a single agent/worktree status

func CollectAgentStatusOnly

func CollectAgentStatusOnly() []AgentStatus

CollectAgentStatusOnly returns just agent status without task context. Exported for use by the HTTP server.

type AgentsResponse

type AgentsResponse struct {
	Workspace   WorkspaceInfo            `json:"workspace"`              // workspace mode info
	Agents      []AgentStatus            `json:"agents"`                 // flat list (existing)
	ByWorkspace map[string][]AgentStatus `json:"by_workspace,omitempty"` // grouped by workspace
	Timestamp   time.Time                `json:"timestamp"`
}

AgentsResponse wraps the agents list with optional workspace grouping.

type AutoModeOptions

type AutoModeOptions struct {
	Interval        int    // Polling interval in seconds when no tasks available
	MaxTasks        int    // Maximum tasks to process before exiting (0 = unlimited)
	IdleTimeout     int    // Exit after N minutes with no available tasks (0 = no timeout)
	AgentType       string // "plan" or "task"
	AgentName       string
	WorktreePath    string
	ParentID        string                                // Epic ID to scope task discovery to (empty = all tasks)
	CustomPromptGen func(string, *WorkspaceConfig) string // Custom prompt generator (overrides AgentType selection)
	CustomTaskCheck func() (bool, error)                  // Custom task availability check (overrides AgentType selection)
	BackoffBase     time.Duration                         // Base backoff duration for no-progress retries (default 30s)
}

AutoModeOptions holds configuration for auto mode

type AutoModeState

type AutoModeState struct {
	TasksCompleted        int
	ConsecutiveErrors     int
	ConsecutiveNoProgress int // sessions that completed without claiming a task
	LastTaskTime          time.Time
	IdleStartTime         time.Time
	ShouldExit            bool
	ExitReason            string
}

AutoModeState tracks the current state of auto mode execution

type Backend

type Backend interface {
	Name() string
	InvokeInteractive(workDir, prompt, agentName string) error
	InvokeNonInteractive(workDir, prompt, agentName string, shutdown <-chan struct{}) error
}

Backend is the interface that all AI coding agent backends must implement.

Name returns a unique identifier for the backend (e.g. "claude", "codex"). InvokeInteractive starts a live, interactive agent session in the terminal. InvokeNonInteractive runs a headless agent session that can be canceled via the shutdown channel.

type BdIssue

type BdIssue struct {
	ID           string       `json:"id"`
	Title        string       `json:"title"`
	Status       string       `json:"status"`
	Priority     int          `json:"priority"`
	IssueType    string       `json:"issue_type"`
	Design       string       `json:"design"`
	Assignee     string       `json:"assignee"`
	Labels       []string     `json:"labels"`
	Dependencies []Dependency `json:"dependencies"`
}

BdIssue represents an issue from bd list --json

func GetAnyAvailableTasks

func GetAnyAvailableTasks(parentID string) ([]BdIssue, error)

GetAnyAvailableTasks returns any ready tasks regardless of design status. Used by custom roles with task_filter=any. When parentID is non-empty, only tasks under that epic are returned.

func GetAvailableImplementationTasks

func GetAvailableImplementationTasks(parentID string) ([]BdIssue, error)

GetAvailableImplementationTasks returns tasks ready for implementation (ready tasks WITH an approved design, excluding tasks with needs-revision label and epics) When parentID is non-empty, only tasks under that epic are returned.

func GetAvailablePlanningTasks

func GetAvailablePlanningTasks(parentID string) ([]BdIssue, error)

GetAvailablePlanningTasks returns tasks that need planning (ready tasks without a design OR with needs-revision label, excluding epics) When parentID is non-empty, only tasks under that epic are returned.

type BdStats

type BdStats struct {
	Summary struct {
		TotalIssues      int `json:"total_issues"`
		OpenIssues       int `json:"open_issues"`
		ClosedIssues     int `json:"closed_issues"`
		InProgressIssues int `json:"in_progress_issues"`
		BlockedIssues    int `json:"blocked_issues"`
		DeferredIssues   int `json:"deferred_issues"`
		TombstoneIssues  int `json:"tombstone_issues"`
		PinnedIssues     int `json:"pinned_issues"`
	} `json:"summary"`
}

BdStats represents output from bd stats --json

type ClaudeBackend

type ClaudeBackend struct{}

ClaudeBackend implements the Backend interface for the Claude CLI.

func (*ClaudeBackend) InvokeInteractive

func (c *ClaudeBackend) InvokeInteractive(workDir, prompt, agentName string) error

func (*ClaudeBackend) InvokeNonInteractive

func (c *ClaudeBackend) InvokeNonInteractive(workDir, prompt, agentName string, shutdown <-chan struct{}) error

func (*ClaudeBackend) Name

func (c *ClaudeBackend) Name() string

type CodexBackend

type CodexBackend struct{}

CodexBackend implements the Backend interface for the OpenAI Codex CLI.

func (*CodexBackend) InvokeInteractive

func (c *CodexBackend) InvokeInteractive(workDir, prompt, agentName string) error

func (*CodexBackend) InvokeNonInteractive

func (c *CodexBackend) InvokeNonInteractive(workDir, prompt, agentName string, shutdown <-chan struct{}) error

func (*CodexBackend) Name

func (c *CodexBackend) Name() string

type CommandResult

type CommandResult struct {
	Stdout string
	Stderr string
	Err    error
}

CommandResult represents the output of a command execution

type CommitDetail

type CommitDetail struct {
	Hash    string `json:"hash"`
	Message string `json:"message"`
	URL     string `json:"url,omitempty"` // GitHub commit URL if remote available
}

CommitDetail represents a single commit with hash, message, and optional GitHub URL.

type ContentBlock

type ContentBlock struct {
	Type  string                 `json:"type"`
	Text  string                 `json:"text,omitempty"`
	Name  string                 `json:"name,omitempty"`
	Input map[string]interface{} `json:"input,omitempty"`
}

type Daemon

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

Daemon coordinates multiple supervised agents.

func NewDaemon

func NewDaemon(config *DaemonConfig, projectDir string) (*Daemon, error)

NewDaemon creates a daemon from the loaded config.

func (*Daemon) AgentCount

func (d *Daemon) AgentCount() int

AgentCount returns the number of configured agents.

func (*Daemon) Agents

func (d *Daemon) Agents() []SupervisedAgentStatus

Agents returns a snapshot of all agent statuses for inspection. The returned SupervisedAgentStatus structs are safe to use without synchronization.

func (*Daemon) Start

func (d *Daemon) Start() error

Start launches supervisor goroutines for all configured agents.

func (*Daemon) Stop

func (d *Daemon) Stop()

Stop gracefully shuts down all agents. Safe to call multiple times.

type DaemonAgentInfo

type DaemonAgentInfo struct {
	Managed bool
	Role    string
}

DaemonAgentInfo carries daemon supervision metadata for a worktree.

type DaemonAgentState

type DaemonAgentState struct {
	PID    int                     `json:"pid"`
	Agents []DaemonAgentStateEntry `json:"agents"`
}

DaemonAgentState represents the daemon-agents.json file format. This matches the DaemonState written by daemon_cmd.go.

type DaemonAgentStateEntry

type DaemonAgentStateEntry struct {
	Worktree string `json:"worktree"`
	Status   string `json:"status"`
	Role     string `json:"role"`
}

DaemonAgentStateEntry represents a single agent in daemon-agents.json

type DaemonAgentStatus

type DaemonAgentStatus struct {
	Worktree     string    `json:"worktree"`
	Role         string    `json:"role"`
	PID          int       `json:"pid"`
	Status       string    `json:"status"` // "running", "starting", "stopped", "failed"
	TaskID       string    `json:"task_id,omitempty"`
	EpicID       string    `json:"epic_id,omitempty"`
	RestartCount int       `json:"restart_count"`
	LastStart    time.Time `json:"last_start,omitempty"`
	LastExit     time.Time `json:"last_exit,omitempty"`
	LastExitCode int       `json:"last_exit_code,omitempty"`
}

DaemonAgentStatus represents the status of a single supervised agent

type DaemonConfig

type DaemonConfig struct {
	Backend string
	Daemon  DaemonSettings
	Roles   map[string]RoleConfig
	Agents  []AgentEntry
}

DaemonConfig is the merged, resolved configuration used by callers.

func LoadDaemonConfig

func LoadDaemonConfig(projectDir string) (*DaemonConfig, error)

LoadDaemonConfig merges global (~/.loom/config.yaml) and local (loom.yaml) config. Local values override global. Returns defaults if neither file exists.

func (*DaemonConfig) ResolveRole

func (dc *DaemonConfig) ResolveRole(name string) (RoleConfig, bool)

ResolveRole looks up a role by name in the merged config. Returns the RoleConfig and true if found, zero value and false if not.

type DaemonSettings

type DaemonSettings struct {
	PIDFile       string        `yaml:"pid_file,omitempty"`
	LogDir        string        `yaml:"log_dir,omitempty"`
	RestartPolicy RestartPolicy `yaml:"restart_policy,omitempty"`
	MaxAgents     *int          `yaml:"max_agents,omitempty"`
}

DaemonSettings holds daemon-specific config fields.

type DaemonState

type DaemonState struct {
	PID       int                 `json:"pid"`
	StartedAt time.Time           `json:"started_at"`
	Agents    []DaemonAgentStatus `json:"agents"`
}

DaemonState represents the complete daemon state in daemon-agents.json

type Dependency

type Dependency struct {
	IssueID     string `json:"issue_id"`
	DependsOnID string `json:"depends_on_id"`
	Type        string `json:"type"` // "parent-child" or "blocks"
	CreatedAt   string `json:"created_at"`
	CreatedBy   string `json:"created_by"`
}

Dependency represents a dependency relationship from bd ready --json

type EpicAssigner

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

EpicAssigner manages epic-to-worktree assignments for the daemon. It queries open epics and assigns each available worktree to the highest-priority epic with ready tasks.

func NewEpicAssigner

func NewEpicAssigner() *EpicAssigner

NewEpicAssigner creates a new EpicAssigner.

func (*EpicAssigner) AssignWorktree

func (ea *EpicAssigner) AssignWorktree(worktreeName string) (string, error)

AssignWorktree queries open epics, skips already-assigned ones, and returns the highest-priority unassigned epic with ready tasks. Returns empty string if no epics are available (agent falls back to non-epic mode).

func (*EpicAssigner) Assignments

func (ea *EpicAssigner) Assignments() map[string]string

Assignments returns a copy of the full assignment map (for status/logging).

func (*EpicAssigner) GetAssignment

func (ea *EpicAssigner) GetAssignment(worktreeName string) string

GetAssignment returns the current epic ID for a worktree, or empty string.

func (*EpicAssigner) ReleaseWorktree

func (ea *EpicAssigner) ReleaseWorktree(worktreeName string)

ReleaseWorktree removes the assignment for a worktree, making its epic available for reassignment.

type EpicInfo

type EpicInfo struct {
	ID             string
	Priority       int
	ReadyTaskCount int
}

EpicInfo holds parsed epic data for assignment ranking.

type EventMessage

type EventMessage struct {
	Content []ContentBlock `json:"content,omitempty"`
}

type FileChange

type FileChange struct {
	Status string `json:"status"` // "M", "A", "D", "??", "R"
	Path   string `json:"path"`
}

FileChange represents a single file change from git status.

type HealthResponse

type HealthResponse struct {
	Status    string    `json:"status"`
	Timestamp time.Time `json:"timestamp"`
}

HealthResponse is the health check response.

type LockInfo

type LockInfo struct {
	PID           int       `json:"pid"`
	Command       string    `json:"command"`
	StartedAt     time.Time `json:"started_at"`
	AgentName     string    `json:"agent_name"`
	TaskID        string    `json:"task_id,omitempty"`
	TaskTitle     string    `json:"task_title,omitempty"`
	TaskStartedAt time.Time `json:"task_started_at,omitempty"` // Per-task timing (reset when new task claimed)
	State         string    `json:"state,omitempty"`           // Execution state (active/idle) for auto mode
	Workspace     string    `json:"workspace,omitempty"`       // Workspace name when in workspace mode
}

LockInfo holds information about a running agent

func CheckLock

func CheckLock(worktreePath string) (*LockInfo, bool, error)

CheckLock checks if a lock exists and if the process is still running Returns the lock info, whether the process is running, and any error

func ReadLockFile

func ReadLockFile(worktreePath string) (*LockInfo, error)

ReadLockFile reads and parses the lock file without modifying it

type LoomConfig

type LoomConfig struct {
	DefaultWorkspace string                     `yaml:"default_workspace,omitempty"`
	Backend          string                     `yaml:"backend,omitempty"`
	Workspaces       map[string]WorkspaceConfig `yaml:"workspaces"`
	Daemon           *DaemonSettings            `yaml:"daemon,omitempty"`
}

LoomConfig is the top-level configuration from ~/.loom/config.yaml

func LoadConfig

func LoadConfig() (*LoomConfig, error)

LoadConfig reads and parses the loom config file. Returns (nil, nil) if the config file does not exist. Returns (nil, error) on read or parse errors.

type MonitorData

type MonitorData struct {
	Timestamp          time.Time
	Agents             []AgentStatus
	Tasks              TaskSummary
	NeedsPlanningTasks []TaskInfo          // Ready tasks without design (top 5)
	ReadyToImplement   []TaskInfo          // Ready tasks with design (top 5)
	ReviewTasks        []TaskInfo          // top 5 need review tasks
	InProgressTasks    []TaskInfo          // all in_progress tasks
	BacklogTasks       []TaskInfo          // backlog tasks (top 20)
	AgentTasks         map[string]TaskInfo // agent name -> current task (from assignee)
	TaskConflicts      map[string][]string // TaskID -> agent names (if multiple agents claim same task)
	SyncStatus         SyncInfo
	Stats              MonitorStats
}

MonitorData holds all dashboard information

func CollectMonitorData

func CollectMonitorData() *MonitorData

CollectMonitorData gathers all dashboard data. Exported for use by the HTTP server.

type MonitorStats

type MonitorStats struct {
	Open       int     `json:"open"`
	Closed     int     `json:"closed"`
	Total      int     `json:"total"`
	Completion float64 `json:"completion"`
	Remaining  int     `json:"remaining"`
	InProgress int     `json:"in_progress"`
	Review     int     `json:"review"`
	Blocked    int     `json:"blocked"`
}

MonitorStats holds overall statistics

type OpenCodeBackend

type OpenCodeBackend struct{}

OpenCodeBackend implements the Backend interface for the OpenCode CLI.

func (*OpenCodeBackend) InvokeInteractive

func (o *OpenCodeBackend) InvokeInteractive(workDir, prompt, agentName string) error

func (*OpenCodeBackend) InvokeNonInteractive

func (o *OpenCodeBackend) InvokeNonInteractive(workDir, prompt, agentName string, shutdown <-chan struct{}) error

func (*OpenCodeBackend) Name

func (o *OpenCodeBackend) Name() string

type PaneState

type PaneState struct {
	Dead       bool
	ExitStatus int
	ExitSignal string
	PID        int
}

PaneState holds detailed information about a tmux pane

type ProjectFile

type ProjectFile struct {
	Backend string                `yaml:"backend,omitempty"`
	Daemon  *DaemonSettings       `yaml:"daemon,omitempty"`
	Roles   map[string]RoleConfig `yaml:"roles,omitempty"`
	Agents  []AgentEntry          `yaml:"agents,omitempty"`
}

ProjectFile represents the project-local loom.yaml.

func LoadProjectFile

func LoadProjectFile(dir string) (*ProjectFile, error)

LoadProjectFile reads and parses the project-local loom.yaml from dir. Returns (nil, nil) if the file does not exist.

type PromptData

type PromptData struct {
	AgentName    string
	WorktreeName string
	Role         string
	TaskID       string
}

PromptData is the template context for custom prompt files.

type RepoConfig

type RepoConfig struct {
	Name          string `yaml:"name" json:"name"`                                         // Display name / identifier
	Path          string `yaml:"path" json:"path"`                                         // Path to the repo (absolute or relative to workspace)
	DefaultBranch string `yaml:"default_branch,omitempty" json:"default_branch,omitempty"` // Override default branch (defaults to "main")
	Remote        string `yaml:"remote,omitempty" json:"remote,omitempty"`                 // Git remote name (defaults to "origin")
}

RepoConfig defines a single repository within a workspace

type ResolvedTarget

type ResolvedTarget struct {
	WorkDir   string // directory where Claude should run
	AgentName string // agent name for locks and prompts
}

ResolvedTarget holds the result of workspace-aware argument resolution.

func ResolveAgentTarget

func ResolveAgentTarget(name string) (ResolvedTarget, error)

ResolveAgentTarget resolves a CLI argument (workspace name, repo name, or worktree name) into the working directory and agent name. In workspace mode, Claude always runs from the workspace root so bd commands use the shared .beads/ directory.

type Resolver

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

Resolver abstracts worktree/repo discovery behind legacy and workspace modes.

func NewResolver

func NewResolver() (*Resolver, error)

NewResolver creates a Resolver, selecting workspace mode if a config with workspaces exists, otherwise falling back to legacy mode.

func (*Resolver) DiscoverWorktrees

func (r *Resolver) DiscoverWorktrees() ([]WorktreeInfo, error)

DiscoverWorktrees returns discovered worktrees using the resolver's mode.

func (*Resolver) GetDefaultBranch

func (r *Resolver) GetDefaultBranch() string

GetDefaultBranch returns the default integration branch using the resolver's mode. Resolution order: LOOM_DEFAULT_BRANCH env var > mode-specific logic > "main"

func (*Resolver) GetWorktreesDir

func (r *Resolver) GetWorktreesDir() string

GetWorktreesDir returns the worktrees directory path using the resolver's mode. In workspace mode, returns the active workspace's path.

func (*Resolver) Mode

func (r *Resolver) Mode() ResolverMode

Mode returns the resolver's current mode.

func (*Resolver) ResolveWorkspaceByName

func (r *Resolver) ResolveWorkspaceByName(name string) (string, bool)

ResolveWorkspaceByName checks if name matches a workspace name and returns the workspace root path. Returns (path, true) if found, ("", false) if not.

func (*Resolver) ResolveWorktreePath

func (r *Resolver) ResolveWorktreePath(name string) (string, error)

ResolveWorktreePath converts a worktree name to its full path using the resolver's mode.

func (*Resolver) SetWorkspace

func (r *Resolver) SetWorkspace(name string) error

SetWorkspace switches the active workspace. Returns an error if the workspace name is not found in the config.

func (*Resolver) WorkspaceName

func (r *Resolver) WorkspaceName() string

WorkspaceName returns the active workspace name (empty in legacy mode).

func (*Resolver) WorkspaceNames

func (r *Resolver) WorkspaceNames() []string

WorkspaceNames returns the names of all configured workspaces. Returns nil in legacy mode.

type ResolverMode

type ResolverMode int

ResolverMode indicates how the Resolver discovers worktrees

const (
	ModeLegacy    ResolverMode = iota // scan ./worktrees/ directory
	ModeWorkspace                     // read from ~/.loom/config.yaml
)

type RestartPolicy

type RestartPolicy struct {
	MaxRetries     *int `yaml:"max_retries,omitempty"`
	BackoffInitial *int `yaml:"backoff_initial,omitempty"` // seconds
	BackoffMax     *int `yaml:"backoff_max,omitempty"`     // seconds
	OutputTimeout  *int `yaml:"output_timeout,omitempty"`  // seconds; kill agent after this long with no output (0 = disabled)
}

RestartPolicy defines how the daemon restarts failed agents.

type RoleConfig

type RoleConfig struct {
	Description string `yaml:"description,omitempty"`
	PromptFile  string `yaml:"prompt_file,omitempty"`
	Model       string `yaml:"model,omitempty"`
	TaskFilter  string `yaml:"task_filter,omitempty"`
}

RoleConfig defines an agent role (built-in like "plan"/"task", or custom).

type StatsResponse

type StatsResponse struct {
	Stats     MonitorStats `json:"stats"`
	Timestamp time.Time    `json:"timestamp"`
}

StatsResponse wraps statistics.

type StatusResponse

type StatusResponse struct {
	Workspace      WorkspaceInfo       `json:"workspace"`
	Agents         []AgentStatus       `json:"agents"`
	Tasks          TaskSummary         `json:"tasks"`
	InProgressList []TaskInfo          `json:"in_progress_list"`
	AgentTasks     map[string]TaskInfo `json:"agent_tasks"`
	Stats          MonitorStats        `json:"stats"`
	Sync           SyncInfo            `json:"sync"`
	Timestamp      time.Time           `json:"timestamp"`
}

StatusResponse is the full status (like monitor dashboard).

type StreamEvent

type StreamEvent struct {
	Type    string        `json:"type"`
	Message *EventMessage `json:"message,omitempty"`
}

StreamEvent represents a Claude stream-json event

type SupervisedAgentStatus

type SupervisedAgentStatus struct {
	Worktree       string
	Role           string
	WorktreePath   string
	PID            int
	RestartCount   int
	LastStart      time.Time
	LastExit       time.Time
	LastExitCode   int
	AssignedEpicID string
}

SupervisedAgentStatus is a snapshot of a supervised agent's state for external inspection. This type is safe to copy and does not contain a mutex.

type SyncInfo

type SyncInfo struct {
	DBSynced       bool                 `json:"db_synced"`
	DBLastSync     string               `json:"db_last_sync"`
	DBError        string               `json:"db_error,omitempty"`
	GitNeedsPush   int                  `json:"git_needs_push"`
	GitNeedsPull   int                  `json:"git_needs_pull"`
	GitPushDetails []WorktreeSyncDetail `json:"git_push_details,omitempty"`
	GitPullDetails []WorktreeSyncDetail `json:"git_pull_details,omitempty"`
}

SyncInfo holds sync status information

type SyncResponse

type SyncResponse struct {
	Sync      SyncInfo  `json:"sync"`
	Timestamp time.Time `json:"timestamp"`
}

SyncResponse wraps sync status.

type TaskInfo

type TaskInfo struct {
	ID       string `json:"id"`
	Title    string `json:"title"`
	Priority int    `json:"priority"`
	Status   string `json:"status"` // "in_progress", "closed", "open"
}

TaskInfo represents a task with basic info

type TaskSummary

type TaskSummary struct {
	NeedsPlanning    int `json:"needs_planning"`     // Ready tasks without design
	ReadyToImplement int `json:"ready_to_implement"` // Ready tasks with approved design
	InProgress       int `json:"in_progress"`
	NeedReview       int `json:"need_review"`
	Backlog          int `json:"backlog"`
}

TaskSummary holds task counts by category

type TasksResponse

type TasksResponse struct {
	Summary          TaskSummary `json:"summary"`
	NeedsPlanning    []TaskInfo  `json:"needs_planning"`
	ReadyToImplement []TaskInfo  `json:"ready_to_implement"`
	NeedsReview      []TaskInfo  `json:"needs_review"`
	InProgress       []TaskInfo  `json:"in_progress"`
	Backlog          []TaskInfo  `json:"backlog"`
	Timestamp        time.Time   `json:"timestamp"`
}

TasksResponse wraps task information.

type WorkspaceConfig

type WorkspaceConfig struct {
	Path  string       `yaml:"path" json:"path"`   // Directory path for this workspace
	Repos []RepoConfig `yaml:"repos" json:"repos"` // Repositories in this workspace
}

WorkspaceConfig defines a named workspace containing multiple repos

func ResolveActiveWorkspace

func ResolveActiveWorkspace() (*WorkspaceConfig, error)

ResolveActiveWorkspace loads the config and returns the active workspace config. Returns (nil, nil) if not in workspace mode (no config or no workspaces defined). Uses DefaultWorkspace if set, otherwise uses the first workspace in the map.

type WorkspaceDetail

type WorkspaceDetail struct {
	Path  string   `json:"path"`  // workspace root path
	Repos []string `json:"repos"` // repo names in this workspace
}

WorkspaceDetail contains details about a single workspace.

type WorkspaceInfo

type WorkspaceInfo struct {
	Mode       string   `json:"mode"`                 // "workspace" or "legacy"
	Name       string   `json:"name,omitempty"`       // workspace name (workspace mode only)
	Workspaces []string `json:"workspaces,omitempty"` // all workspace names (workspace mode only)
}

WorkspaceInfo represents workspace metadata for API responses.

type WorkspacesResponse

type WorkspacesResponse struct {
	Mode       string                     `json:"mode"`       // "workspace" or "legacy"
	Default    string                     `json:"default"`    // default workspace name
	Workspaces map[string]WorkspaceDetail `json:"workspaces"` // workspace details
	Timestamp  time.Time                  `json:"timestamp"`
}

WorkspacesResponse lists all configured workspaces.

type WorktreeInfo

type WorktreeInfo struct {
	Name      string
	Path      string
	Branch    string
	Workspace string      // workspace name (empty in legacy mode)
	Repo      *RepoConfig // source repo config (nil in legacy mode)
}

WorktreeInfo holds information about a discovered worktree

func DiscoverWorktrees

func DiscoverWorktrees() ([]WorktreeInfo, error)

DiscoverWorktrees finds all worktrees in the worktrees directory

type WorktreeSyncDetail

type WorktreeSyncDetail struct {
	Name  string `json:"name"`
	Count int    `json:"count"`
}

WorktreeSyncDetail holds per-worktree sync detail (commits ahead or behind).

Jump to

Keyboard shortcuts

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