repos

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2026 License: MIT Imports: 19 Imported by: 0

README

Local repository registry

This package owns the repository tables in the shared private (0600) app.sqlite application database. It stores what the user chose to review. It reads local git metadata only and contacts nothing; the daemon supplies the one GitHub lookup its HTTP handler uses.

Stored data

  • repositories(identity, settings): settings shared by every clone of a repository. identity is the lowercase owner/name on github.com and host/owner/name elsewhere. settings is JSON (automatic, decisions); a daily_cap, backend or model an older baum stored is ignored and dropped on the next write, and dropped from a request an older app sends: the harnesses and models are global preferences.
  • clones(id, path, common_dir, repository): registered working trees. A list reprobes each path and reports a changed or missing one unavailable without rewriting it.
  • picked(identity): repositories picked by name, possibly without a clone.
  • aliases(alias, repository): earlier github.com names GitHub forwards to a repository after a rename or transfer, recorded when a folder whose remote uses one is registered (AddForwarded). Open and OpenReadOnly load them into gitlocal (every baum command opens the registry read-only first), so in that process every remote URL using an earlier name reads as the current one (gitlocal.RemoteRepository, github.RepositoryFromRemote) and the clone is a clone of the repository everywhere. Clones registered under the earlier name move to the current one, which is picked once and takes the earlier name's settings while its own are still the default. Renames chain, and renaming back undoes them. The daemon asks GitHub again at startup, and a fix before it pushes for its repository (RecheckAliases): an alias GitHub now answers with another repository than the recorded one (a new one took the name) is dropped, so its remotes name that repository again; one it could not check or does not show the token is kept, but a fix does not push while one is: a repository the token cannot see may have taken the name. The remote is never rewritten. Repositories lists them as earlier_names.
  • preferences(key, value): the global preferences as one JSON row. Its appearance object (theme, key_hints, page_modifier) stores the device’s General settings independently of the daemon’s loopback port. Its plan_usage.show object maps claude and codex to the person's choice of showing that plan's usage; a missing plan follows the backends in use (see planusage). Its decisions object (enabled, model) configures local decision capture; Settings.DecisionsEnabled combines it with the automatic master switch and the repository's decisions (nil is on). See decisionlog. Its review_time_limit_minutes and run_time_limit_minutes, when set (1 to MaxTimeLimit, 240), replace the config file's review_timeout and run_timeout in Preferences.Resolve; a value out of range keeps the file's and does not block saving other preferences.

A repository is picked exactly when it has a picked row. Adding a clone also picks its repository, and removing the clone keeps it picked with zero clones. A repository with no clone is still registered: RepositorySettings and automatic admission treat it as such, and only unpick revokes that. Work that needs a clone waits for one: discovery lists the repository's pull requests, but the job API answers needs_clone and automatic generation submits nothing until a clone is registered. Unpick deletes the repository's clones and its picked row but keeps the repositories row, so its settings survive a re-pick. Pick accepts only github.com owner/name; it reads no local git and checks nothing on GitHub.

The application database owns schema versioning. Pre-release registry files are not imported; a new registration follows the global automatic preference.

HTTP

Registry.Handler has no authentication; the daemon mounts it behind its authenticated host and origin checks. JSON is decoded strictly.

Route Result
GET /api/repos clones
GET /api/repos/check?path= whether a path can be registered
POST /api/repos {"path","settings"?,"repository"?} register a clone; optional repository uses any matching remote (400 {"message"})
DELETE /api/repos/{id} remove a clone (204, 404)
PUT /api/repos/{id}/settings replace the repository's settings
GET /api/repositories picked repositories, [{"repository","settings","clones":[]}]
POST /api/repositories {"repository","settings"} pick (400 on a bad name or settings)
DELETE /api/repositories/{owner}/{name} unpick (204, 404)
PUT /api/repositories/{owner}/{name}/settings replace settings (404 unless picked)

Only POST validates the name. DELETE and PUT only lowercase it and look it up, so a malformed owner/name answers 404, and an Enterprise identity added through add can be unpicked from the CLI.

Add files a clone under its first GitHub remote, origin first. AddFor(path, repository, settings) files it under repository when any of its GitHub remotes names it (a fork clone whose upstream is the picked repository), and fails with ErrNoMatchingRemote when none does. The fork's own repository is neither registered nor picked. A list reprobes a clone by its stored repository the same way, so such a clone stays available while some remote names that repository. Pull request commits a clone lacks are fetched by generation from the remote whose URL matches the registered repository, not origin, into refs/baum/pull/<N>/.

Cloning a picked repository is served beside these routes by internal/githubrepos (the registry package contacts nothing): GET /api/repositories/{owner}/{name}/clone-target (the default folder), POST /api/repositories/{owner}/{name}/clone {"dir"?} (picks the repository if needed, clones in the background and registers the clone with AddFor; 202, 400, 409) and GET .../clone (the current or last state).

The explicit Choose folder action supplies repository to POST /api/repos, which calls AddFor. When no remote names the repository, HandlerWith's CurrentName (the daemon asks GitHub) checks each GitHub remote, origin first; the first GitHub answers with the repository's current name is registered through AddForwarded. Otherwise the request fails with 400 and a JSON message naming the repositories the remotes use, or saying GitHub could not be asked. Handler has no CurrentName and refuses such folders. Omitting it preserves the standalone path registration behavior.

AddFor moves a path already registered to another repository to the new one. The daemon's clone finder uses AttachFound instead, which never changes what is stored: it fails with ErrClonedElsewhere when the path or its repository directory belongs to another repository, and with ErrNotPicked unless the repository is picked, checked in the same transaction, so an unpick in between is not undone. PickedWithoutClones (picked repositories with no clone row), CloneRows (clones as stored, available false) and RepositoryRows (Repositories with clones as stored) read the registry only and run no git, unlike List and Repositories, which probe every clone.

Settings PUTs replace the whole object and require automatic, null for the default. A daily_cap an older app still sends, in a settings PUT or in the settings of a POST, is dropped rather than refused: baum sets no spending limits.

CLI

baum internal repos add|remove|list manage clones. pick <owner/name> and unpick <owner/name> manage picked repositories, and repositories [--json] lists them, including those without a clone.

Documentation

Overview

Package repos stores explicitly registered local clones and repository settings.

Index

Constants

View Source
const (
	PullRequestsAuthored  = "authored"
	PullRequestsRequested = "requested"
	// PullRequestsAll covers every open pull request in the repository.
	PullRequestsAll = "all"
)

The PullRequests choices besides both.

View Source
const MaxJobConcurrency = 4

MaxJobConcurrency is the most jobs the daemon runs at once besides questions to a review's agent, and the default. A person lowers it when parallel agents load the computer too much.

View Source
const MaxTimeLimit = 240

MaxTimeLimit is the longest time limit, in minutes, the app sets.

Variables

View Source
var (
	// ErrNotPicked reports a repository that is not picked.
	ErrNotPicked = errors.New("repository is not picked")
	// ErrClonedElsewhere reports a working tree, or a repository directory,
	// already registered to another repository.
	ErrClonedElsewhere = errors.New("clone is registered to another repository")
)

Errors of AttachFound.

View Source
var ErrInvalidRepository = errors.New("repository must be a github.com owner/name")

ErrInvalidRepository reports a name that is not a github.com owner/name.

View Source
var ErrNoMatchingRemote = errors.New("working tree has no remote for that repository")

ErrNoMatchingRemote reports a working tree none of whose GitHub remotes names the requested repository.

View Source
var ErrNotFound = errors.New("registered clone or picked repository not found")

ErrNotFound reports a clone or picked repository the registry does not hold.

View Source
var ErrNotWorkingTree = errors.New("path is unavailable or is not a git working tree")

ErrNotWorkingTree reports a path that is missing or not in a git working tree.

Functions

func Path

func Path() (string, error)

func ValidateTimeLimit

func ValidateTimeLimit(minutes int) error

ValidateTimeLimit accepts zero (the config file's) or 1 to MaxTimeLimit minutes.

Types

type AppearancePreferences

type AppearancePreferences struct {
	Theme    string `json:"theme,omitempty"`
	KeyHints *bool  `json:"key_hints,omitempty"`
	// PageModifier is the key held with 1 to 8 to open the sidebar's pages:
	// "meta", "ctrl" or "alt"; empty is the platform's command key.
	PageModifier string `json:"page_modifier,omitempty"`
}

AppearancePreferences are device choices kept independently of the daemon HTTP origin.

type Clone

type Clone struct {
	ID         string   `json:"id"`
	Path       string   `json:"path"`
	CommonDir  string   `json:"common_dir"`
	Repository string   `json:"repository"`
	Settings   Settings `json:"settings"`
	Available  bool     `json:"available"`
}

func Inspect

func Inspect(ctx context.Context, path string) (Clone, error)

Inspect reports the clone a path would register as, or why it cannot be registered, without registering it.

type CurrentName

type CurrentName func(ctx context.Context, repository string) (string, error)

CurrentName is the lower-case owner/name a github.com repository has now; the daemon asks GitHub, which answers an earlier name it forwards with the current one. The registry itself contacts nothing.

type DecisionPreferences

type DecisionPreferences struct {
	Enabled *bool           `json:"enabled,omitempty"`
	Backend *config.Backend `json:"backend,omitempty"`
	Model   *string         `json:"model,omitempty"`
	Effort  *string         `json:"effort,omitempty"`
}

DecisionPreferences are the global choices for local decision capture and the feature log, the data extraction the app's settings name. A nil Enabled is on. A nil Backend follows the reviews' backend. Model, when set, replaces the backend's default decision model. Effort is the reasoning effort (config.Efforts), empty or nil for the harness's own.

type PlanUsagePreferences

type PlanUsagePreferences struct {
	Show map[string]bool `json:"show,omitempty"`
}

PlanUsagePreferences are the plans the person chose to show or hide in plan usage, by provider ("claude", "codex"). A provider with no entry follows the backends baum uses, so a choice survives backend changes.

type Preferences

type Preferences struct {
	// Automatic is the default for repositories without an explicit choice.
	Automatic bool `json:"automatic"`
	// Backend and Model, when set, replace the config file's for generation
	// in the app; a repository's own settings still override them.
	Backend *config.Backend `json:"backend,omitempty"`
	Model   *string         `json:"model,omitempty"`
	// Effort is the reviews' reasoning effort (config.Efforts), empty or nil
	// for the harness's own.
	Effort *string `json:"effort,omitempty"`
	// FixBackend, FixModel and FixEffort choose the agent that fixes pull
	// requests (ResolveFix). A nil FixBackend means the same as reviews; an
	// empty FixModel is the fix backend's default model and an empty
	// FixEffort its own effort.
	FixBackend *config.Backend `json:"fix_backend,omitempty"`
	FixModel   *string         `json:"fix_model,omitempty"`
	FixEffort  *string         `json:"fix_effort,omitempty"`
	// PullRequests narrows which pull requests get automatic reviews:
	// PullRequestsAuthored, PullRequestsRequested, PullRequestsAll, or empty
	// for both authored and requested.
	PullRequests string `json:"pull_requests,omitempty"`
	// AgentEnv, when it sets any variable, replaces the config file's
	// agent_env (config.Config.AgentEnv).
	AgentEnv map[string]string `json:"agent_env,omitempty"`
	// Appearance survives daemon restarts and changes to its loopback origin.
	Appearance AppearancePreferences `json:"appearance,omitzero"`
	// Decisions configures local decision capture.
	Decisions DecisionPreferences `json:"decisions,omitzero"`
	// PlanUsage chooses which plans plan usage shows.
	PlanUsage PlanUsagePreferences `json:"plan_usage,omitzero"`
	// JobConcurrency, when set, is how many jobs the daemon runs at once
	// besides questions to a review's agent; Concurrency resolves it.
	JobConcurrency int `json:"job_concurrency,omitempty"`
	// ReviewTimeLimit and RunTimeLimit, in minutes, when set replace the
	// config file's review_timeout and run_timeout (Resolve): how long a
	// review, and any other agent run, may take.
	ReviewTimeLimit int `json:"review_time_limit_minutes,omitempty"`
	RunTimeLimit    int `json:"run_time_limit_minutes,omitempty"`
}

Preferences are the registry's global choices, edited in the app. Fields an older baum saved and this one dropped, such as cursor_agent_acknowledged, are ignored on read and gone after the next write.

func DefaultPreferences

func DefaultPreferences() Preferences

DefaultPreferences apply until the user changes them: automatic generation on.

func (Preferences) Concurrency

func (p Preferences) Concurrency() int

Concurrency is how many jobs run at once: JobConcurrency, or MaxJobConcurrency when it is unset or out of range (a hand edit, or a newer baum's).

func (Preferences) Covers

func (p Preferences) Covers(authored, requested bool) bool

Covers reports whether automatic reviews cover a pull request the viewer authored or was asked to review, as PullRequests chooses.

func (Preferences) Resolve

func (p Preferences) Resolve(global config.Config) config.Config

Resolve overlays the app's backend, model, agent environment and time limits on the config file's.

func (Preferences) ResolveExtraction

func (p Preferences) ResolveExtraction(cfg config.Config) config.Config

ResolveExtraction gives the config data extraction runs with, from cfg, the config resolved for reviews: the extraction's own effort. Its backend and model are decisionlog's to resolve.

func (Preferences) ResolveFix

func (p Preferences) ResolveFix(review config.Config) config.Config

ResolveFix gives the config a fix runs with, from review, the config already resolved for reviews. A set FixBackend replaces the review backend, model and effort, and takes FixModel and FixEffort (empty for the backend's own); without one the fix follows the reviews.

func (Preferences) Validate

func (p Preferences) Validate() error

Validate accepts an empty Model, unlike Settings: it means the backend's default even when the config file names a model.

func (Preferences) ValidateChange

func (p Preferences) ValidateChange(prev Preferences) error

ValidateChange is Validate for preferences saved over prev: the desktop app offers Claude Code and Codex only, so a backend chosen now must be one of them. A retired backend already stored (cursor-agent or anthropic, from a pre-release version) does not block saving other settings; the daemon refuses the jobs that use it.

type Registry

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

func Open

func Open(path string) (*Registry, error)

Open opens repository tables in the shared application database.

func OpenReadOnly

func OpenReadOnly(path string) (*Registry, error)

OpenReadOnly opens an existing registry only to list it. Unlike Open it creates no directory, file or table and changes no file mode, and SQLite opens the file read-only, so it writes no journal. A missing file is an error wrapping fs.ErrNotExist; a file without tables lists no clones.

func (*Registry) Add

func (r *Registry) Add(ctx context.Context, path string, settings *Settings) (Clone, error)

Add registers an existing clone. Nil settings preserve existing settings; explicit settings replace the settings shared by all its repository's clones.

func (*Registry) AddFor

func (r *Registry) AddFor(ctx context.Context, path, repository string, settings *Settings) (Clone, error)

AddFor registers an existing clone for repository (github.com owner/name) when any of its GitHub remotes names it, not only origin: a fork clone whose upstream remote is the repository is filed under the repository, and the fork's own repository is neither registered nor picked. It fails with ErrNoMatchingRemote when no remote names the repository. Settings are as for Add.

func (*Registry) AddForwarded

func (r *Registry) AddForwarded(ctx context.Context, path, repository, alias string, settings *Settings) (Clone, error)

AddForwarded registers a clone for repository, as AddFor does, whose remote uses alias, an earlier name GitHub forwards to repository, which the caller has checked. The alias is recorded first: from then on remote URLs using it read as repository (gitlocal.RenamedTo), so the clone stays a clone of repository everywhere, until RecheckAliases finds GitHub no longer forwards it.

func (*Registry) AddPatch

func (r *Registry) AddPatch(ctx context.Context, path string, edit func(Settings) (Settings, error)) (Clone, error)

AddPatch atomically registers a clone and applies a settings edit. The edit runs under the writer transaction, so omitted fields cannot lose another process's concurrent edits. It must not call back into the registry.

func (*Registry) AttachFound

func (r *Registry) AttachFound(ctx context.Context, path, repository string) (Clone, error)

AttachFound registers a clone the daemon found for a picked repository, as AddFor does, but never changes what is already stored: it fails with ErrNotPicked unless the repository is picked (checked in the same transaction, so an unpick in between is not undone) and with ErrClonedElsewhere when the path or its repository directory is registered to another repository, which AddFor would move.

func (*Registry) CloneRows

func (r *Registry) CloneRows(ctx context.Context) ([]Clone, error)

CloneRows lists the registered clones exactly as stored, without probing them: Available is false throughout. It runs no git.

func (*Registry) Close

func (r *Registry) Close() error

func (*Registry) Handler

func (r *Registry) Handler() http.Handler

Handler exposes the registry API for the future local UI host. It does not listen or authenticate: the host MUST mount it behind its authenticated, host/origin-checked middleware. Never mount this on the public view server.

func (*Registry) HandlerWith

func (r *Registry) HandlerWith(current CurrentName) http.Handler

HandlerWith is Handler, also registering a folder for a repository when its remote uses an earlier name that current reports GitHub forwards to it.

func (*Registry) List

func (r *Registry) List(ctx context.Context) ([]Clone, error)

func (*Registry) Pick

func (r *Registry) Pick(ctx context.Context, repository string, settings *Settings) (Repository, error)

Pick registers a github.com repository by owner/name without a local clone. Nil settings keep the existing settings, or the default for a new repository; explicit settings replace them. Picking again is a no-op.

func (*Registry) PickedWithoutClones

func (r *Registry) PickedWithoutClones(ctx context.Context) ([]string, error)

PickedWithoutClones lists the picked repositories that have no clone registered. It reads the registry only and runs no git, so it is cheap enough to ask for every hook event.

func (*Registry) Preferences

func (r *Registry) Preferences(ctx context.Context) (Preferences, error)

Preferences reads the global preferences: DefaultPreferences until saved.

func (*Registry) RecheckAliases

func (r *Registry) RecheckAliases(ctx context.Context, repository string, current func(context.Context, github.Repository) (string, error)) (unconfirmed []string, err error)

RecheckAliases asks GitHub (current) again about the recorded earlier names of repository, or of every repository when it is "", and drops each one it now answers with another repository than the recorded one does, as when a new repository took the name: remotes using it then name that one. One it could not check or does not show the token is kept, and returned as unconfirmed: a repository the token cannot see may have taken it.

func (*Registry) Remove

func (r *Registry) Remove(ctx context.Context, idOrPath string) error

Remove accepts an ID or path, including a path that no longer exists.

func (*Registry) Repositories

func (r *Registry) Repositories(ctx context.Context) ([]Repository, error)

Repositories lists every picked repository by identity, with its clones probed for availability as List does.

func (*Registry) RepositoryRows

func (r *Registry) RepositoryRows(ctx context.Context) ([]Repository, error)

RepositoryRows is Repositories without probing the clones: Available is false throughout. It runs no git.

func (*Registry) RepositorySettings

func (r *Registry) RepositorySettings(ctx context.Context, repository string) (Settings, bool, error)

RepositorySettings reads one repository's shared settings without probing its clones; false means the repository is not picked.

func (*Registry) Unpick

func (r *Registry) Unpick(ctx context.Context, repository string) error

Unpick removes a picked repository's clones and its picked row. The repositories row stays, as in Remove, so its settings survive a later pick.

func (*Registry) Update

func (r *Registry) Update(ctx context.Context, id string, s Settings) error

Update replaces shared repository settings without needing an available clone.

func (*Registry) UpdatePreferences

func (r *Registry) UpdatePreferences(ctx context.Context, edit func(Preferences) Preferences) (Preferences, error)

UpdatePreferences atomically edits the global preferences. The edit runs under the writer transaction and must not call back into the registry.

func (*Registry) UpdateRepository

func (r *Registry) UpdateRepository(ctx context.Context, repository string, s Settings) error

UpdateRepository replaces a picked repository's shared settings by identity.

type RemoteMismatchError

type RemoteMismatchError struct {
	Repository string
	Remotes    []string
	// Unchecked is set when GitHub could not be asked whether a remote uses
	// an earlier name of Repository.
	Unchecked bool
}

RemoteMismatchError is ErrNoMatchingRemote, naming the repositories the working tree's GitHub remotes name instead, origin's first; none when it has no GitHub remote.

func (*RemoteMismatchError) Error

func (e *RemoteMismatchError) Error() string

func (*RemoteMismatchError) Is

func (e *RemoteMismatchError) Is(target error) bool

type Repository

type Repository struct {
	Repository string   `json:"repository"`
	Settings   Settings `json:"settings"`
	Clones     []Clone  `json:"clones"`
	// EarlierNames are names GitHub forwards to the repository that clones'
	// remotes were registered with (AddForwarded).
	EarlierNames []string `json:"earlier_names,omitempty"`
}

Repository is a picked repository with its settings and registered clones, of which there may be none.

type Settings

type Settings struct {
	Automatic *bool `json:"automatic"`
	// Decisions is the repository's choice for local decision capture under
	// the global switches (see DecisionsEnabled): nil and true follow them,
	// false turns the repository off.
	Decisions *bool `json:"decisions,omitempty"`
}

Settings are shared by all clones of a repository. Automatic is the repository's choice under the global master switch (see AutomaticEnabled): nil follows it, false turns the repository off, true is an explicit on that still stops while the global switch is off. Registration itself starts no generation. A daily_cap, backend or model an older baum stored is ignored on read and dropped on the next write: baum sets no spending limits, since it cannot price what a subscription bills, and the harnesses and models are global (Preferences).

func (Settings) AutomaticEnabled

func (s Settings) AutomaticEnabled(p Preferences) bool

AutomaticEnabled reports whether the daemon generates reviews of this repository automatically. The global preference is a master switch: off stops every repository, whatever its choice; on, only an explicit false turns a repository off.

func (Settings) DecisionsEnabled

func (s Settings) DecisionsEnabled(p Preferences) bool

DecisionsEnabled reports whether the daemon captures the decisions of this repository's agent sessions: the automatic master switch (which the tray's pause turns off) and the decisions switch are both on, and the repository has not turned capture off.

Jump to

Keyboard shortcuts

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