Documentation
¶
Overview ¶
Package board implements a Kanban board for orchestrating docker agents. Each card owns an agent running in a tmux session on an isolated git worktree; the board observes and drives the agent through the control plane its run exposes with --listen. Columns form a pipeline: moving a card forward sends the destination column's prompt to its agent.
Index ¶
- Constants
- Variables
- func StatePath() string
- func TmuxSocketPath() (string, error)
- type App
- func (a *App) AddProject(p Project) error
- func (a *App) AttachCommand(cardID string) (*exec.Cmd, error)
- func (a *App) Cards() []*Card
- func (a *App) ColumnIndex(colID string) int
- func (a *App) Columns() []Column
- func (a *App) CreateCard(project Project, prompt string) (card *Card, err error)
- func (a *App) DeleteCard(cardID string) error
- func (a *App) Diff(cardID string) (string, error)
- func (a *App) MoveCard(cardID, colID string) error
- func (a *App) OpenEditor(cardID string) error
- func (a *App) Projects() []Project
- func (a *App) RemoveProject(name string) error
- func (a *App) SetColumnPrompt(colID, prompt string) error
- type Card
- type CardStatus
- type Column
- type Project
- type Store
- func (s *Store) DeleteCard(id string) error
- func (s *Store) GetCard(id string) (*Card, error)
- func (s *Store) InsertCard(c *Card) error
- func (s *Store) ListCards() []*Card
- func (s *Store) MoveCard(id, column string, requireIdle bool) (*Card, error)
- func (s *Store) UpdateCardStatus(id string, status CardStatus) (bool, error)
- func (s *Store) UpdateCardTitle(id, title string) (bool, error)
Constants ¶
const DefaultAgent = "default"
DefaultAgent is the agent ref used for projects that do not set one.
Variables ¶
var DefaultColumns = []Column{
{ID: "dev", Name: "Dev", Emoji: "🔨"},
{ID: "review", Name: "Review", Emoji: "🔍", Prompt: "Review the local changes. Look for bugs, security issues, and code quality problems. Fix any issues you find."},
{ID: "push", Name: "Push", Emoji: "🚀", Prompt: "Start by committing any remaining uncommitted files. Then rebase on top of the upstream default branch and fix any test failures and linter issues. Finally, squash all commits on this branch into a single commit with a clear and concise commit message. Push the branch to your fork (or the appropriate remote). Then use gh to open a pull request."},
{ID: "done", Name: "Done", Emoji: "✅"},
}
DefaultColumns is the pipeline used when the user config defines none.
var ErrAgentStarting = errors.New("the agent is still starting")
ErrAgentStarting means the card's agent has not answered on its control plane yet, so there is no UI worth attaching to.
var ErrCardBusy = errors.New("cannot move a busy card forward")
ErrCardBusy rejects a forward move of a busy card. It is checked under the store lock so a watcher flipping the status concurrently cannot slip a running card past the caller's check.
var ErrCardNotFound = errors.New("card not found")
ErrCardNotFound reports a lookup of a card that does not exist (anymore).
Functions ¶
func StatePath ¶
func StatePath() string
StatePath returns the file the board persists its cards to.
func TmuxSocketPath ¶
TmuxSocketPath is the dedicated tmux socket the board runs its sessions on. The board shares the host's tmux binary but not its default server: a private socket keeps the board's server-wide options from leaking into the user's interactive tmux. The path is stable across board restarts so the controller can reattach to sessions left running on it.
Like tmux's own /tmp/tmux-<uid> convention, the socket lives in a per-user 0700 directory so other local users cannot pre-create or reach it. The directory is created and validated before tmux binds the socket.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is the board engine: it owns the cards, the per-card agent sessions, and the configuration (projects and columns) stored in the user's global config file.
func NewApp ¶
NewApp loads the board state and reattaches to any sessions still running in tmux. onChanged is called (from arbitrary goroutines) whenever a card changes, so the UI can refresh.
func (*App) AddProject ¶
AddProject validates and appends a project, persisting it to the user's global config file. The path is normalized to an absolute path (expanding a leading ~) so cards never depend on the board's working directory.
func (*App) AttachCommand ¶
AttachCommand returns the command that attaches the caller's terminal to the card's agent session. It fails with ErrAgentStarting until the agent's control plane answers, so the user never lands on a bare launch command. Before attaching, the session's header row is refreshed with the card's current title and project.
func (*App) ColumnIndex ¶
ColumnIndex returns the position of a column in the pipeline, or -1.
func (*App) CreateCard ¶
CreateCard creates a card in the first column and launches its agent session. docker agent creates the isolated git worktree (named after the card) on first launch and exposes its control plane on a per-card unix socket; the board records where the worktree lives and starts watching the session. The title is a placeholder derived from the prompt, replaced when the agent emits its session_title event, so card creation is instant.
func (*App) DeleteCard ¶
DeleteCard removes a card, kills its session, and cleans up its worktree.
func (*App) MoveCard ¶
MoveCard moves a card to the given column. A move never changes the card's status: the status tracks the agent's activity, not the move. A busy card cannot move forward; the check is enforced atomically by the store so a watcher flipping the status concurrently cannot slip past it. Moving forward sends the destination column's prompt to the card's agent; the move stays observable even when the prompt cannot be delivered.
func (*App) OpenEditor ¶
OpenEditor opens the card's worktree in the user's GUI editor: the command named by $BOARD_EDITOR, defaulting to VS Code's `code`. The editor is started detached and reaped in the background.
func (*App) RemoveProject ¶
RemoveProject deletes a project by name and persists the change. Existing cards keep the repo path and agent they were created with.
func (*App) SetColumnPrompt ¶
SetColumnPrompt updates a column's prompt and persists it to the user's global config file.
type Card ¶
type Card struct {
ID string `json:"id"`
Title string `json:"title"`
Column string `json:"column"`
Status CardStatus `json:"status"`
Project string `json:"project"`
Agent string `json:"agent"`
RepoPath string `json:"repoPath"`
Branch string `json:"branch"`
Worktree string `json:"worktree"`
Session string `json:"session"`
// AgentSession is the docker-agent conversation ID the card owns. It is
// passed to `docker agent run --session` on every launch, so a session
// recreated after the agent (or tmux) dies resumes the same conversation
// instead of starting over.
AgentSession string `json:"agentSession"`
}
Card is one task on the board.
type CardStatus ¶
type CardStatus string
CardStatus tracks what a card's agent is doing.
const ( // StatusStarting marks a card whose agent is launching but has not yet // answered on its control plane. The watcher replaces it with a real // status as soon as the agent emits events. StatusStarting CardStatus = "starting" StatusRunning CardStatus = "running" StatusWaiting CardStatus = "waiting" // StatusPaused marks a card whose turn is blocked on /pause. It lasts // until the runtime emits events again (resume) or the turn ends. StatusPaused CardStatus = "paused" // StatusError marks a card whose last turn failed. It is sticky: the // watcher keeps it until the next turn starts. StatusError CardStatus = "error" )
func (CardStatus) Busy ¶
func (s CardStatus) Busy() bool
Busy reports whether the card's agent cannot accept a prompt right now: it is either still starting or in the middle of a turn.
type Column ¶
Column is one kanban column with the prompt sent to a card's agent when the card moves forward into it.
func ColumnsFromConfig ¶
func ColumnsFromConfig(cols []userconfig.BoardColumn) []Column
ColumnsFromConfig maps user-configured columns to board columns, falling back to DefaultColumns when none are configured.
type Project ¶
Project is a repository cards can be created against, configured in the user's global config file (or through the TUI, which persists there).
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store persists the board's cards as a JSON file under the data directory. All methods are safe for concurrent use and return copies, so callers and background watchers can never alias the same Card.
func OpenStore ¶
OpenStore loads the card store from path, starting empty when the file does not exist yet.
func (*Store) DeleteCard ¶
DeleteCard removes a card. Deleting a missing card is a no-op.
func (*Store) InsertCard ¶
InsertCard appends a card to the board.
func (*Store) MoveCard ¶
MoveCard atomically moves a card to the given column and re-inserts it at the end of the board order. The card is re-read under the lock so the move preserves the current status; when requireIdle is set, a card whose watcher concurrently flipped it to busy is rejected with ErrCardBusy. The updated card is returned.
func (*Store) UpdateCardStatus ¶
func (s *Store) UpdateCardStatus(id string, status CardStatus) (bool, error)
UpdateCardStatus persists only the status field of a card, so background watchers holding a stale snapshot of the card cannot revert concurrent edits. It reports whether the status actually changed.
func (*Store) UpdateCardTitle ¶
UpdateCardTitle persists only the title field of a card. Same rationale as Store.UpdateCardStatus. It reports whether the title actually changed.