batch

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package batch resolves Manifests into scheduling Members: the Chains and Ticket-to-branch derivation that no fleet tool has an equivalent for. It is the deep module for treepad's batch orchestration feature — every scheduling predicate that can be dangerously wrong lands here as a pure, table-testable function.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CommonDir

func CommonDir(ctx context.Context, r Runner) (string, error)

CommonDir resolves the git common dir for the repository containing the process's current directory. In a linked worktree .git is a file, not a directory, so this must be used instead of filepath.Join(path, ".git").

func DeriveBranch

func DeriveBranch(prefix, ref string) string

DeriveBranch computes a branch name from prefix + slug(ref). Refs are unique within a Tracker, so no collision suffix is needed. It must stay deterministic and total: reconcile re-derives every branch name on every tick and stores none. ponytail: a Linear ref is a title slug (feat/silent-refresh), a GitHub ref is bare digits (feat/42) — readability trade-off accepted in ADR 0001.

func LinkArgs

func LinkArgs(chain []Member, prs map[string]PR) []string

LinkArgs returns the longest prefix of chain whose branches all have an open pull request, bottom to top — the argument list for `gh stack link`. It is a prefix, not a filter: stopping at the first member without an open pull request, rather than skipping it, because `gh stack link` sets each argument's base to the one before it — skipping a member would silently base the next one on the wrong branch.

gh stack link pushes every branch it is given and creates a pull request for any that lack one, so a member past the prefix must never be included — passing the whole Chain "for tidiness" would push work-in-progress and open unwanted pull requests. This is load-bearing: do not widen the filter to include members without an open pull request.

A Chain of fewer than two PR-having members produces no Stack, so the result is nil in that case too.

func Resolve

func Resolve(m Manifest, ticketURLTmpl string) ([][]Member, error)

Resolve is pure: no context.Context, no runner, no filesystem. It turns a Manifest's Chains into resolved Members, one slice per Chain.

func ResolveTicket

func ResolveTicket(ticketURLTmpl, input string) (ticketURL, ref string, err error)

ResolveTicket turns user input into a Ticket URL and the Ref it carries. Input matching http(s):// is used verbatim; anything else is a Ref rendered through ticketURLTmpl.

Types

type Chain

type Chain struct {
	Tickets []string `toml:"tickets"`
}

Chain is an ordered run of Tickets, each worktree branched from the one before it.

type Manifest

type Manifest struct {
	Name         string  `toml:"name"`
	BranchPrefix string  `toml:"branch_prefix"`
	Base         string  `toml:"base"`
	Chains       []Chain `toml:"chain"`
}

Manifest declares one Batch: a named collection of Chains. Written by an agent that read the Tracker — never by treepad, never by hand.

func Load

func Load(commonDir string) ([]Manifest, error)

Load reads every *.toml file under <commonDir>/treepad/batches and unions them. A repo with no Manifests is normal, so a missing directory returns nil rather than an error.

type Member

type Member struct {
	Ticket    string `json:"ticket"`
	Ref       string `json:"ref"`
	TicketURL string `json:"ticket_url"`
	Branch    string `json:"branch"`
	Base      string `json:"base"` // Chain position 0 -> Manifest.Base; otherwise the previous member's Branch
	Batch     string `json:"batch"`
	Chain     int    `json:"chain"`
	Position  int    `json:"position"`
}

Member is a Chain member resolved against config: one Ticket, its Ref, the branch it seeds, and the base that branch is created from.

func ReadyToMaterialise

func ReadyToMaterialise(chain []Member, existing map[string]bool, prs map[string]PR) []Member

ReadyToMaterialise returns the leading prefix of chain eligible for this tick: position 0 is always ready; position i is ready when member i-1's branch has an open (or merged) pull request. A pushed branch alone is not enough — an agent pushing a work-in-progress commit must not unblock the layer above against nothing.

An already-materialised member (existing[m.Branch] true) stays ready regardless of its parent's current pull request state, so a pull request closing after materialisation never un-reports something already created.

type PR

type PR struct {
	Number      int
	HeadRefName string
	BaseRefName string
	State       string // OPEN | MERGED | CLOSED
	URL         string
}

PR is a pull request as `gh pr list` reports it — plain data, no gh behaviour. It lives here rather than in internal/gh so batch's exported predicates (ReadyToMaterialise, LinkArgs) can take PR state as an argument: an exported signature can't name a type from an internal package.

type RestackAction

type RestackAction int

RestackAction is what Reconcile's restack step does to one member's worktree after `git fetch origin <branch>`.

const (
	RestackNone        RestackAction = iota // nothing to do: behind == 0
	RestackFastForward                      // clean, plainly behind: git merge --ff-only origin/<branch>
	RestackReset                            // clean, diverged, patch-equivalent: git reset --hard origin/<branch>
	RestackStale                            // dirty, or diverged with a genuinely local commit: wait for a human
)

func RestackDecision

func RestackDecision(clean bool, ahead, behind int, patchEquivalent bool) RestackAction

RestackDecision applies the restack safety predicate (issue #140, correcting ADR 0003's "fast-forward only" mechanism: a server-side rebase leaves the branch diverged from origin, not merely behind, and `merge --ff-only` refuses a diverged branch).

behind == 0 means origin has nothing this branch lacks — whether the branch is ahead with unpushed commits or fully in sync, there is nothing to restack, regardless of clean. Only when behind > 0 does dirty become disqualifying: a dirty worktree, or a diverged one holding a commit git cherry says is not yet upstream, never auto-repairs — treepad never stashes or discards work on an agent's behalf.

type Runner

type Runner interface {
	Run(ctx context.Context, name string, args ...string) ([]byte, error)
}

Runner is the shape of worktree.CommandRunner, redeclared here so no exported signature in this package names an internal type.

Jump to

Keyboard shortcuts

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