lookaround

package
v0.50.0 Latest Latest
Warning

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

Go to latest
Published: Oct 30, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package lookaround collects facts about one project and derives the feed (what changed in a window) or the state (where each task stands). Only Derive and DeriveFeed decide a stage or a group; docs/board.md is the spec.

Index

Constants

View Source
const (
	ViewFeed  = "feed"
	ViewState = "state"
)

The two views a board is.

View Source
const (
	SourceHost    = "host"
	SourceTracker = "tracker"
	SourceLocal   = "local"
)

The three sources, in the order the board lists them.

View Source
const (
	StageAsksWaiting      = "asks-waiting"
	StageBlocked          = "blocked"
	StageReady            = "ready"
	StageInReview         = "in-review"
	StagePROpen           = "pr-open"
	StageImplementing     = "implementing"
	StagePlanning         = "planning"
	StagePlanned          = "planned"
	StageMergedUnfinished = "merged-unfinished"
	StageNext             = "next"
	StageDone             = "done"
)

The stages, in the order the board lists rows.

View Source
const (
	GroupInProgress = "in progress"
	GroupNext       = "next"
	GroupDone       = "done"
)

The groups the state's rows draw under, in listing order.

View Source
const (
	FromLocal   = "local"
	FromTrace   = "trace"
	FromHost    = "host"
	FromTracker = "tracker"
	FromGuessed = "guessed"
)

Where a stage's or a group's evidence came from: local state, the traces f10 left on the host, the host's certain facts (a merge, a creation), the tracker's list, or the host's facts read as a guess.

View Source
const (
	GroupStarted  = "started"
	GroupUpdated  = "updated"
	GroupReviewed = "reviewed"
	GroupFinished = "finished"
	GroupNewTasks = "new-tasks"
)

The feed's groups, in the order it lists them.

View Source
const (
	RelationPart     = "part"      // the two overlapped
	RelationFollowUp = "follow-up" // it merged before the later one opened
	RelationReplaced = "replaced"  // it closed unmerged
)

How a task's earlier request relates to the one its feed row leads with.

View Source
const Version = 4

Version is the Board JSON's version; a change to a stage rule or a field's meaning bumps it with the spec.

Variables

Groups is every feed group in listing order.

Stages is every stage in listing order.

StateGroups is every state group in listing order.

Functions

func AgentFor

func AgentFor(id, branch, path string) string

AgentFor is the name start gave the task's agent in the worktree: the id and the suffix its branch carries, hashed with the path.

func Plural added in v0.39.0

func Plural(n int, word string) string

Plural is n and word, pluralised past one ("-sh" takes "es"). Every renderer counting a word goes through it.

func StateGroup added in v0.45.0

func StateGroup(r Row) string

StateGroup is the heading a state row draws under: a row held only by its base, with no work of its own, is still next. board.html's group() mirrors it.

func StateRank added in v0.45.0

func StateRank(r Row) int

StateRank orders state rows: by group, then by stage, except that a row waiting on its base follows the rows that can start now.

Types

type Board

type Board struct {
	Version     int       `json:"version"`
	GeneratedAt time.Time `json:"generated_at"`
	View        string    `json:"view"`
	Project     Project   `json:"project"`
	Window      Window    `json:"window"`
	Sources     []Source  `json:"sources"`
	Rows        []Row     `json:"rows"`           // the state view's; empty in the feed
	Feed        *Feed     `json:"feed,omitempty"` // the feed view's
}

A Board is one project's feed or rows, with where each fact came from.

func Derive

func Derive(f Facts, now, since time.Time) Board

Derive is the board from facts: one row per task or taskless request, staged by the first matching rule in docs/board.md, each naming its evidence. No clock and no I/O: now and since are the board's.

func DeriveFeed added in v0.39.0

func DeriveFeed(f Facts, now time.Time, w Window) Board

DeriveFeed is the feed from facts over the window: a row per task or taskless request with activity there, grouped by the first matching rule in docs/board.md, a row per task created there, and the quiet count.

func Unreachable

func Unreachable(p Project, err error, now time.Time, w Window, view string) Board

Unreachable is the board of a project no collector could run for. Every source carries the error, so a reader of several boards sees the project failed rather than empty; a project with no checkout keeps local skipped.

type Branch

type Branch struct {
	Task   string   // the id the branch is linked to, else what its name spells
	Riders []string // a bundle's other members
	After  string   // the base task recorded on it
	Model  string   // the agent's model, where start recorded it
	Agent  string   // the agent's profile as typed (opus:high), where start recorded one
	// Finish is `f10 drive`'s record of running the branch's finish, while
	// that drive holds the task's lease
	Finish *ref.DriveFinish
}

A Branch is what the local config records on a branch.

type Facts

type Facts struct {
	Project Project
	Prefix  string // the task id prefix, for reading ids off branches and titles
	Tasks   []Task
	PRs     []PR
	Local   *Local // nil when the local collector was skipped or failed
	People  []Person
	Sources []Source

	// the feed's: what the host did in the window, each started task's
	// other requests by id, and a local-only checkout's branch tips
	Activity *forge.Activity
	Earlier  map[string][]forge.PR
	Tips     []Tip
}

Facts is what the collectors found, before any interpretation.

func Collect

func Collect(ctx context.Context, in Inputs) Facts

Collect runs every collector and returns what each found, guarded: a collector that errors marks its source failed and contributes nothing, and every other one still runs.

type Feed added in v0.39.0

type Feed struct {
	Rows  []FeedRow `json:"rows"`
	Quiet Quiet     `json:"quiet"`
}

A Feed is what changed in the window: one row per task whose requests were touched there, per touched request naming no task, and per task created there, and the open requests nobody touched, counted.

type FeedRow added in v0.39.0

type FeedRow struct {
	Group string `json:"group"`
	Key   string `json:"key"` // the task id, else `#<number>`, else the branch
	Task  *Task  `json:"task,omitempty"`
	PR    *PRRef `json:"pr,omitempty"`
	From  string `json:"from"`
	Who   Who    `json:"who"`
	// Owes is who the next move waits on: the author after a review, a
	// requested reviewer before one.
	Owes      string      `json:"owes,omitempty"`
	Abandoned bool        `json:"abandoned,omitempty"` // closed without merging, nothing later
	Related   []RelatedPR `json:"related,omitempty"`   // the task's requests opened before PR
	Summary   string      `json:"summary,omitempty"`   // what happened, one sentence
	At        time.Time   `json:"at"`                  // the latest event in the window
}

A FeedRow is one task whose requests were touched in the window, one request naming no task, or one task created there.

type Findings

type Findings struct {
	Round int
	Open  int // findings with no verdict, or a verdict of ask
	At    time.Time
}

Findings is a task's latest review round as its findings file holds it.

type Inputs

type Inputs struct {
	Project Project
	Prefix  string
	// Since is where reading starts: the state view's done window, or the
	// earliest span the feed may show.
	Since time.Time

	// Feed reads what changed since Since instead of where every task
	// stands: the host's activity, the tracker's list, and nothing local
	// unless the checkout is local only.
	Feed bool

	// LocalOnly is a checkout with no origin remote: the host is skipped,
	// and the tracker too unless a driver answers it (Tasks set).
	LocalOnly bool

	Host      *forge.Host
	HostError string // why there is no host, for the source's error

	Tasks func(ctx context.Context) ([]Task, error)

	// RemoteOpen reports whether the project's declared remote reviewer
	// left a review on the request nobody handled; nil declares none. An
	// error is a note on the host source, the request kept without it.
	RemoteOpen func(ctx context.Context, number int) (bool, error)

	Remote bool
	Local  *LocalInputs

	// People is the user's map of one person's identities across the
	// sources; nil groups rows by whatever identity each source gives.
	People []Person

	// Now is the clock; nil is time.Now. A test fixes it so timings are
	// stable.
	Now func() time.Time
}

Inputs binds the collectors to one project. A nil Host, Tasks or Local is that source absent; Remote skips the local collector on purpose.

type Local

type Local struct {
	Runs       []state.Run
	Worktrees  []Worktree
	Branches   map[string]Branch    // by branch name
	Plans      map[string]time.Time // task id to the plan file's mtime
	Archived   map[string]bool      // task ids whose plan is archived
	Findings   map[string]Findings  // task id to its latest round
	Verdicts   map[string]string    // task id to its latest verdict
	Finished   map[string]bool      // task ids the ledger holds task_finished for
	Workspaces map[string]Workspace // worktree path to the Herdr workspace on it
}

Local is what this machine knows: live runs, worktrees, branches, plans, findings, verdicts, what the ledger closed, and Herdr's view.

type LocalInputs

type LocalInputs struct {
	Main       string // the main checkout
	StateDir   string // run state files
	TTL        time.Duration
	SharedRoot string           // the storage root every checkout shares; "" reads each checkout's .f10
	LedgerPath string           // "" reads no ledger
	ProjectID  string           // the ledger's project id
	Agents     agentctl.Backend // the agent backend, when reachable: read its workspaces; nil reads none
}

LocalInputs is where this machine keeps what the local collector reads.

type Next

type Next struct {
	Command string `json:"command,omitempty"`
	Text    string `json:"text,omitempty"`
}

Next is the row's next action: a command where one exists, else a sentence.

type PR

type PR struct {
	forge.PR

	Block      *trace.State   `json:"block,omitempty"`
	Trailers   []trace.Parsed `json:"trailers,omitempty"`
	HeadExists *bool          `json:"head_exists,omitempty"` // read for merged requests only
	RemoteOpen bool           `json:"remote_open,omitempty"` // the declared remote reviewer left one unhandled
}

A PR is a request with what the host says around it.

type PRRef

type PRRef struct {
	Number    int    `json:"number"`
	Title     string `json:"title,omitempty"`
	URL       string `json:"url,omitempty"`
	State     string `json:"state"` // open, merged or closed
	Draft     bool   `json:"draft,omitempty"`
	Head      string `json:"head,omitempty"`
	Base      string `json:"base,omitempty"`
	Mergeable string `json:"mergeable,omitempty"` // clean, conflicts or blocked
	Checks    string `json:"checks,omitempty"`    // green, red or pending
}

A PRRef is the request a row hangs on.

type Person

type Person struct {
	Name    string
	Tracker string
	Host    string
	Emails  []string
}

A Person joins the identities one person has across the sources: the tracker's name for them, their host login, the emails their commits carry. Every field but Name is optional.

type Project

type Project struct {
	ID       string `json:"id"`                 // the normalised remote, `github.com/org/repo`
	Name     string `json:"name"`               // the main checkout's basename
	Host     string `json:"host,omitempty"`     // gh or glab
	Checkout string `json:"checkout,omitempty"` // the main checkout; empty with no checkout read
	Remote   bool   `json:"remote"`             // local collectors skipped
	// LocalOnly is a checkout with no origin remote: no host to read, so
	// the board is this machine's alone.
	LocalOnly bool `json:"local_only,omitempty"`
}

Project names the project the board is about.

type Quiet added in v0.39.0

type Quiet struct {
	Count  int       `json:"count"`
	Oldest time.Time `json:"oldest,omitzero"` // the oldest one's creation time
}

Quiet is the open requests with no row in the window: no commits, reviews or status changes there.

type RelatedPR added in v0.44.0

type RelatedPR struct {
	Number   int    `json:"number"`
	URL      string `json:"url,omitempty"`
	State    string `json:"state"` // open, merged or closed
	Relation string `json:"relation"`
}

A RelatedPR is one of a task's earlier requests, beside the row's own.

type ReviewFacts

type ReviewFacts struct {
	Step             string `json:"step,omitempty"` // the step the state block names
	Round            int    `json:"round,omitempty"`
	Open             int    `json:"open,omitempty"`
	Asks             int    `json:"asks,omitempty"`
	Approvals        int    `json:"approvals,omitempty"`
	ChangesRequested int    `json:"changes_requested,omitempty"`
	RemoteOpen       bool   `json:"remote_open,omitempty"` // the declared remote reviewer left one unhandled
}

ReviewFacts is what the row's reviews add up to.

type Row

type Row struct {
	Key          string       `json:"key"` // the task id, else `#<number>`
	Task         *Task        `json:"task,omitempty"`
	Stage        string       `json:"stage"`
	StageFrom    string       `json:"stage_from"`
	Who          Who          `json:"who"`
	Where        Where        `json:"where"`
	Review       *ReviewFacts `json:"review,omitempty"`
	BlockedBy    []string     `json:"blocked_by,omitempty"`
	LastActivity time.Time    `json:"last_activity,omitzero"`
	Next         Next         `json:"next"`
}

A Row is one task, or one request naming no task.

type Source

type Source struct {
	Name    string   `json:"name"`
	OK      bool     `json:"ok"`
	Skipped bool     `json:"skipped,omitempty"`
	Error   string   `json:"error,omitempty"`
	Notes   []string `json:"notes,omitempty"`
	TookMS  int64    `json:"took_ms"`
}

A Source is one collector's result: ok with its facts in, failed with its error and nothing from it, or skipped on purpose.

type Task

type Task struct {
	ID       string `json:"id"`
	Title    string `json:"title,omitempty"`
	URL      string `json:"url,omitempty"`
	Status   string `json:"status,omitempty"`
	Priority string `json:"priority,omitempty"`
	Assignee string `json:"assignee,omitempty"`
	After    string `json:"after,omitempty"` // the base its body names
	// Created is when the tracker created it, where the tracker says.
	Created time.Time `json:"created,omitzero"`
}

A Task is a tracker row.

type Tip added in v0.39.0

type Tip struct {
	Branch string
	At     time.Time
}

A Tip is a local branch's last commit, what a checkout with no host shows in its feed.

type Where

type Where struct {
	PR        *PRRef `json:"pr,omitempty"`
	Branch    string `json:"branch,omitempty"`
	Worktree  string `json:"worktree,omitempty"`
	Workspace string `json:"workspace,omitempty"`
	// BranchLeft marks a merged row whose branch outlived its worktree
	BranchLeft bool `json:"branch_left,omitempty"`
}

Where the work is.

type Who

type Who struct {
	Agent       string   `json:"agent,omitempty"`        // the Herdr agent in its worktree
	AgentStatus string   `json:"agent_status,omitempty"` // idle, working, done
	Model       string   `json:"model,omitempty"`
	Author      string   `json:"author,omitempty"` // the request's author
	Assignees   []string `json:"assignees,omitempty"`
	Reviewers   []string `json:"reviewers,omitempty"`  // requested and not yet heard from
	Committers  []string `json:"committers,omitempty"` // the author emails on the row's commits
	// Person is the one name the row groups under: the assignee, else the
	// author, else the first committer, through the people map where it matches.
	Person string `json:"person,omitempty"`
}

Who is working on the row, and who it waits on.

type Window

type Window struct {
	Since time.Time `json:"since"`
	// End closes the feed's span, exclusive; zero in the state view.
	End time.Time `json:"end,omitzero"`
	// Chosen is how the feed's span was picked: the window named
	// (`today`, `last-week`, a date) or the default's pick.
	Chosen string `json:"chosen,omitempty"`
	// Fallback is the default falling back to the last working day
	// because today has no activity yet.
	Fallback bool `json:"fallback,omitempty"`
}

Window is the period the board covers: the feed's span, or the time since which the state view's done rows count.

func ChooseWindow added in v0.39.0

func ChooseWindow(today, lastWorkday Window, facts ...Facts) Window

ChooseWindow is the default's span: today when the facts give today a row, else the last working day marked as the fallback. The facts are read from the last working day's start, so one collection answers both.

type Workspace

type Workspace struct {
	ID          string
	Label       string
	AgentStatus string
}

A Workspace is a Herdr workspace open on a worktree.

type Worktree

type Worktree struct {
	Path   string
	Branch string
	Ahead  int      // commits past its base
	Emails []string // the distinct author emails on those commits
}

A Worktree is one checkout of the repo.

Jump to

Keyboard shortcuts

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