streams

package
v0.177.1 Latest Latest
Warning

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

Go to latest
Published: Oct 2, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Package streams owns the identity of a dependency stream: one named cross-repository unit of work spanning a library and the consumers that must change with it.

A stream introduces no second identity for the same work. Its name is the WB worktree task name, its worktrees are created by the existing worktree creation path, and its fleet-wide claim is the one `wb worktree create` already takes. What this package adds is the durable record that ties those per-repository checkouts together — membership, roles, the `stream/<name>` branch and its draft pull request, the branch lease, and every live local link — so `status`, `sync`, `propagate` and `end` can act on the set rather than on one repository at a time.

The record lives under WB's home directory, never inside a member repository: `stream-state-is-untracked-and-local` makes that a requirement rather than a convenience, so a stream survives an interrupted session and `git status` in every member stays clean.

Implements: dependency-streams#req:stream-is-a-named-set-of-worktrees, dependency-streams#req:stream-state-is-untracked-and-local.

Index

Constants

View Source
const (
	GoWorkFile = "go.work"
	GoWorkSum  = "go.work.sum"
)

GoWorkFile and GoWorkSum are the two untracked files a Go local link creates. They are named here because more than one verb has to recognise them: the link creates them, and both `wb worktree merge` and `wb worktree end` refuse a worktree that still carries one.

View Source
const (
	CheckHooks               = "hooks-check"
	CheckNpmProviderIdentity = "npm-provider-identity"
	CheckRedMain             = "red-main"
	CheckStreamConcurrency   = "stream-pr-concurrency"
)

Check names every preflight check WB runs, in the order it runs them. `verbs-state-and-deduplicate-their-work` requires a verb to say what it will run before running it, so the list is data rather than control flow.

View Source
const (
	RefusalStreamExists       = "stream-exists"
	RefusalRepositoryInStream = "repository-in-stream"
	RefusalPreflight          = "preflight-failed"
	// RefusalNoLibrary fires when a stream has no library member at all.
	RefusalNoLibrary = "no-library"
	// RefusalLibraryExists fires when a second library is proposed for a
	// stream that already has one. It used to share RefusalNoLibrary's code,
	// which said the opposite of what happened — and refusal codes are
	// contract that skills branch on.
	RefusalLibraryExists = "library-exists"
	// RefusalUsage marks an ambiguous invocation. It exists so a usage error
	// carries the same envelope and exit code as any other guard.
	RefusalUsage          = "usage"
	RefusalLiveLink       = "live-link"
	RefusalUnabsorbedWork = "unabsorbed-work"
	RefusalStreamEnded    = "stream-ended"
)

Refusal codes are contract: they appear in the JSON envelope and skills branch on them.

View Source
const BranchPrefix = streambranch.Prefix

BranchPrefix is the namespace every stream branch lives in. It is also what the push hook keys its "CI on the stream pull request is the gate" decision on, so the definition lives in the shared leaf package both sides import.

View Source
const EventSchemaVersion = 1

EventSchemaVersion is the stream event-log format this binary writes.

View Source
const SchemaVersion = 2

SchemaVersion is the stream-state format this binary writes and the newest it can read. A newer file is refused rather than silently misread: stream state carries live links whose reversal detail is the only record of the published versions a consumer had before linking.

Variables

View Source
var ErrNotFound = errors.New("stream not found")

ErrNotFound is returned when no stream with that name exists.

Functions

func Branch

func Branch(name string) string

Branch renders the stream branch name for one stream name.

func GoWorkUseEntries

func GoWorkUseEntries(worktree string) ([]string, error)

GoWorkUseEntries reads the `use` entries of a worktree's go.work, if any.

This is the file-based half of `merge-refuses-a-linked-worktree`, and it is deliberately independent of stream state: state alone would miss a hand-written workspace, so the refusal reads the file too.

func IsStreamBranch

func IsStreamBranch(ref string) bool

IsStreamBranch reports whether a branch name — or a full `refs/heads/…` ref — is inside the stream namespace.

func ParseGoWorkUseEntries

func ParseGoWorkUseEntries(contents string) []string

ParseGoWorkUseEntries reads both spellings Go accepts — a bare `use <dir>` and a `use (...)` block — and skips comments, so a commented-out entry is never read as a live link.

func PreflightChecks

func PreflightChecks() []string

PreflightChecks is the declared plan, in run order.

func RedactString

func RedactString(value string) string

RedactString removes credential-shaped substrings.

func ValidateName

func ValidateName(name string) error

ValidateName refuses a stream name that could not also be a worktree task name, before anything durable is created.

func ValidateRepository

func ValidateRepository(repository string) error

ValidateRepository refuses anything that is not an owner/repository slug.

Types

type AgentPullRequest

type AgentPullRequest struct {
	Repository string `json:"repository"`
	Number     int    `json:"number"`
	URL        string `json:"url"`
	Title      string `json:"title"`
	Head       string `json:"head"`
}

AgentPullRequest is one open pull request against the stream branch.

type AgentPullRequestOutcome

type AgentPullRequestOutcome struct {
	Repository string `json:"repository"`
	Number     int    `json:"number"`
	URL        string `json:"url"`
	Action     string `json:"action"`
	Detail     string `json:"detail,omitempty"`
}

AgentPullRequestOutcome is one agent pull request's disposition.

type Commit

type Commit struct {
	SHA     string `json:"sha"`
	Subject string `json:"subject"`
	// PatchID is `git patch-id --stable` over the commit's diff. It is empty
	// only when Git could not produce one (an empty commit, for example), and
	// an empty value is never treated as equal to another empty value.
	PatchID string `json:"patch_id,omitempty"`
}

Commit is one commit on a stream branch, identified by its patch rather than only by its SHA.

A rebase-and-merge landing rewrites SHAs by construction, and one body of work re-applied on two branches has two SHAs and one patch. Clustering by patch identity — Git's own `patch-id --stable` — is what lets `stream-backlog-is-counted-by-patch-identity` name N branches carrying one change as one item. Subject text is a label, never the identity.

func (Commit) Identity

func (commit Commit) Identity() string

Identity is what two commits are compared on. It falls back to the SHA when no patch id exists, so two commits WB could not compare never collapse into one cluster by accident.

type ConsumerBehind

type ConsumerBehind struct {
	Repository string `json:"repository"`
	Identity   string `json:"identity"`
	Manifest   string `json:"manifest"`
	Declared   string `json:"declared"`
	Published  string `json:"published"`
}

ConsumerBehind is gap three.

type CreatedWorktree

type CreatedWorktree struct {
	Repository string
	Worktree   string
	Canonical  string
	Branch     string
	Base       string
}

CreatedWorktree is one checkout the worktree creation path published.

type Declaration

type Declaration struct {
	Identity Identity `json:"identity"`
	// Manifest is the consumer-relative manifest that declares it.
	Manifest string `json:"manifest"`
	// Version is exactly as declared, including any range prefix.
	Version string `json:"version"`
	// Section is the canonical dependency section the declaration came from.
	Section string `json:"section,omitempty"`
	// Workspace is the consumer-relative npm workspace that owns Manifest.
	Workspace string `json:"workspace,omitempty"`
}

Declaration is one consumer's declared dependency on a library identity.

func DiscoverDeclarations

func DiscoverDeclarations(root string, identities []Identity) ([]Declaration, error)

DiscoverDeclarations finds every declaration a consumer worktree makes of the given library identities. A consumer that declares none of them is not linkable, and reporting that is the point: it must be skipped rather than linked to something it does not use.

type DiscardEvents

type DiscardEvents struct{}

DiscardEvents is an appender that records nothing. It exists so a verb can run in a context with no stream directory (a dry preflight, a unit test) without special-casing a nil appender at every call site.

func (DiscardEvents) Append

func (DiscardEvents) Append(Event) error

Append implements EventAppender.

type Ecosystem

type Ecosystem string

Ecosystem names one package system a library publishes into.

const (
	// EcosystemGo is a Go module path.
	EcosystemGo Ecosystem = "go"
	// EcosystemNpm is an npm package name.
	EcosystemNpm Ecosystem = "npm"
)

type EndMemberResult

type EndMemberResult struct {
	Repository string `json:"repository"`
	Worktree   string `json:"worktree"`
	// RemoteBranchDeleted records that origin/stream/<name> is gone.
	RemoteBranchDeleted bool `json:"remote_branch_deleted"`
	// WorktreeRemoved is true when the existing cleanup path retired it.
	WorktreeRemoved bool `json:"worktree_removed"`
	// LeaseReleased is true when the stream lease record was cleared.
	LeaseReleased bool `json:"lease_released"`
	// DraftPullRequest is the member's own stream pull request, reported
	// rather than merged: ending a stream publishes, bumps and merges
	// nothing.
	DraftPullRequest int    `json:"draft_pull_request,omitempty"`
	DraftAction      string `json:"draft_action,omitempty"`
	Detail           string `json:"detail,omitempty"`
}

EndMemberResult is one member's retirement.

type EndOptions

type EndOptions struct {
	Name string
	// Apply performs the removal. Without it the verb reports exactly what it
	// would do and changes nothing, so an operator can see which agent pull
	// requests would be closed before any of them are.
	Apply bool
	// Retarget moves still-open agent pull requests onto the member's base
	// instead of closing them. The default closes them, because GitHub's own
	// silent retarget is the hazard this verb exists to prevent and doing it
	// deliberately must be an explicit choice.
	Retarget bool
	// ForceUnabsorbed proceeds past the absorption guard. It is not a bypass
	// that hides anything: it requires Reason, records both in the event log,
	// and names every commit it is stepping over. Without it, an absorption
	// check WB could not run refuses — a check that cannot answer must not
	// pass.
	ForceUnabsorbed bool
	// Reason is mandatory with ForceUnabsorbed and is recorded verbatim.
	Reason string
	// KeepRemoteBranch leaves origin/stream/<name> in place. By default end
	// deletes it, because leaving it is scaffolding the verb claims to
	// remove.
	KeepRemoteBranch bool
}

EndOptions is one `wb stream end` invocation.

type EndResult

type EndResult struct {
	Stream  string            `json:"stream"`
	Applied bool              `json:"applied"`
	Members []EndMemberResult `json:"members"`
	// AgentPullRequests records what happened to every still-open pull
	// request that targeted a stream branch.
	AgentPullRequests []AgentPullRequestOutcome `json:"agent_pull_requests"`
	Errors            []string                  `json:"errors,omitempty"`
	// Forced names every finding `--force-unabsorbed` stepped over, and
	// ForcedReason the operator's recorded justification. Both are also
	// written to the event log, so the decision is auditable after the fact.
	Forced       []string `json:"forced,omitempty"`
	ForcedReason string   `json:"forced_reason,omitempty"`
}

EndResult is what `stream end` did, or would do.

type Engine

type Engine struct {
	Store     *Store
	Git       Git
	GitHub    GitHub
	Worktrees WorktreeCreator
	// ProjectsRoot locates the canonical clones the preflight checks read.
	ProjectsRoot string
	// HooksCheck answers the readiness preflight's hooks question. Nil uses
	// the installed `wb hooks check`.
	HooksCheck HooksChecker
	// Login, Machine and Session identify the lease holder.
	Login   string
	Machine string
	Session string
	Now     func() time.Time
}

Engine runs the stream verbs against injected ports.

func (*Engine) End

func (engine *Engine) End(ctx context.Context, options EndOptions) (EndResult, error)

End removes a stream's own scaffolding and restores published state.

It refuses while any live local link remains, because a stream that ended with a consumer still resolving an unpublished working tree has not restored published state at all; and it refuses a member whose branch carries work the base has not absorbed, by patch identity rather than by path listing. Before a stream branch could be deleted it enumerates every still-open pull request targeting it and closes or retargets each, because GitHub silently retargets such a pull request at the base when its base branch disappears — producing the misrouted pull request this Feature guards against with no operator mistake to blame.

Ending publishes, bumps and merges nothing.

Implements: dependency-streams#req:stream-end-proves-absorption-and-removes-its-own-scaffolding, dependency-streams#req:stream-end-removes-every-stream-worktree.

`stream-end-restores-published-state` is only PARTLY implemented here. The REQ says end must remove every live link before delegating worktree removal; this verb refuses while one remains and names the exact command per link, which is a guard rather than the removal. Removing them inside `end` needs the local-link verb that lands in the propagate-local row — calling it from here would be a forward dependency on code this row does not contain. Until then, `one-verb-per-operation` is satisfied only for the refusal, and the operator still chains the undos by hand. That gap is deliberate and tracked, not an oversight.

func (*Engine) Join

func (engine *Engine) Join(ctx context.Context, options JoinOptions) (StartResult, error)

Join adds a repository to an existing stream, creating its stream worktree, branch and draft pull request exactly as Start does, so every later verb treats it as a member from that point on.

Implements: dependency-streams#req:stream-pushes-use-a-lease-and-a-stream-claim (the join half of the one-stream-per-repository refusal).

func (*Engine) Start

func (engine *Engine) Start(ctx context.Context, options StartOptions, transitive []string) (StartResult, error)

Start creates a stream. Every fence runs before the first side effect: name validation, the one-open-stream-per-repository claim, and the fleet readiness checks all complete before any worktree is created.

Implements: dependency-streams#req:stream-is-a-named-set-of-worktrees, dependency-streams#req:stream-state-is-untracked-and-local, dependency-streams#req:stream-branch-with-draft-pr, dependency-streams#req:stream-start-proves-the-fleet-is-ready, dependency-streams#req:stream-pushes-use-a-lease-and-a-stream-claim.

func (*Engine) Status

func (engine *Engine) Status(ctx context.Context, name string) (Status, error)

Status reconstructs the stream from WB-owned state rather than from repository contents, so it answers after an interrupted session.

type Event

type Event struct {
	SchemaVersion int       `json:"schema_version"`
	Timestamp     time.Time `json:"timestamp"`
	Stream        string    `json:"stream"`
	Verb          string    `json:"verb"`
	// Phase is the step inside the verb — "preflight", "worktree",
	// "pull-request", "link", "unlink", "lease" — so a timeline can show
	// where a verb spent its time rather than only that it ran.
	Phase string `json:"phase,omitempty"`
	// Repository qualifies the event when it is about one member.
	Repository string `json:"repository,omitempty"`
	// Outcome is the envelope vocabulary: success, findings, refused.
	Outcome string `json:"outcome"`
	// RefusalCode is set only when Outcome is "refused". It is the stable
	// identifier a caller branches on.
	RefusalCode string `json:"refusal_code,omitempty"`
	// Detail is human-readable and redacted.
	Detail string `json:"detail,omitempty"`
	// DurationMS is the wall time of the phase, when the verb measured it.
	DurationMS int64 `json:"duration_ms,omitempty"`
	// Evidence carries the exact facts the verb relied on. Values are
	// redacted before the event is written.
	Evidence map[string]string `json:"evidence,omitempty"`

	// Provenance fields (wb#631, SDLC logging-gap analysis 2026-09-18): IDs
	// only, stamped by Append from the environment at zero cost, never a
	// prompt or response body. Additive and omitempty: a reader older than
	// this change simply never sees them, and EventSchemaVersion did not
	// need to move for a purely additive field.
	HarnessSessionID string `json:"harness_session_id,omitempty"`
	Harness          string `json:"harness,omitempty"`
	EffortLevel      string `json:"effort_level,omitempty"`
	AgentID          string `json:"agent_id,omitempty"`
	ToolUseID        string `json:"tool_use_id,omitempty"`
	WBVersion        string `json:"wb_version,omitempty"`
}

Event is one structured record appended by a stream verb.

`every-verb-appends-a-structured-event` is the requirement; the fields here are the minimum every P0 verb can populate truthfully. The log is append-only, versioned, redacted before any bytes are written, and safe for concurrent appenders.

func ReadEvents

func ReadEvents(path string) ([]Event, error)

ReadEvents parses one stream's event log. A record from a newer schema is refused rather than partially interpreted.

func Redact

func Redact(event Event) Event

Redact returns a copy of the event with every free-text field redacted.

type EventAppender

type EventAppender interface {
	Append(event Event) error
}

EventAppender is the seam between a stream verb and the event log.

LANE SEAM: the P0 delivery order puts the shared event-log implementation in the row that also owns the exit-code/JSON envelope contract. Stream verbs depend on this interface, never on a concrete writer, so the shared implementation can replace FileEventLog without touching a verb. Nothing below the interface is part of a verb's contract.

type ExecGit

type ExecGit struct {
	// Timeout bounds each child. Zero uses defaultCommandTimeout.
	Timeout time.Duration
}

ExecGit runs real Git.

func (ExecGit) CommitsNotIn

func (git ExecGit) CommitsNotIn(ctx context.Context, dir, branch, base string) ([]Commit, error)

func (ExecGit) CurrentBranch

func (git ExecGit) CurrentBranch(ctx context.Context, dir string) (string, error)

func (ExecGit) DefaultBranch

func (git ExecGit) DefaultBranch(ctx context.Context, dir string) (string, error)

func (ExecGit) DeleteRemoteBranch

func (git ExecGit) DeleteRemoteBranch(ctx context.Context, dir, branch, expectedSHA string) error

func (ExecGit) DirtyPaths

func (git ExecGit) DirtyPaths(ctx context.Context, dir string) ([]string, error)

func (ExecGit) Fetch

func (git ExecGit) Fetch(ctx context.Context, dir string) error

func (ExecGit) IsAncestor added in v0.127.3

func (git ExecGit) IsAncestor(ctx context.Context, dir, ancestor, descendant string) (bool, error)

func (ExecGit) LocalBranchHead added in v0.127.3

func (git ExecGit) LocalBranchHead(ctx context.Context, dir, branch string) (string, bool, error)

func (ExecGit) LocalHead

func (git ExecGit) LocalHead(ctx context.Context, dir string) (string, error)

func (ExecGit) LogSubjects

func (git ExecGit) LogSubjects(ctx context.Context, dir, from, to string) ([]string, error)

func (ExecGit) PushBranch

func (git ExecGit) PushBranch(ctx context.Context, dir, branch string) (string, error)

func (ExecGit) RemoteHead

func (git ExecGit) RemoteHead(ctx context.Context, dir, branch string) (string, bool, error)

func (ExecGit) Tags

func (git ExecGit) Tags(ctx context.Context, dir, pattern string) ([]string, error)

type ExecGitHub

type ExecGitHub struct {
	Timeout time.Duration
}

ExecGitHub runs the installed `gh`.

Every call uses `gh api` or a `--json` read rather than parsing human output: `land-verbs-work-with-the-installed-gh` records what happens when a verb depends on the presentation of a specific gh release.

func (ExecGitHub) ClosePullRequest

func (hub ExecGitHub) ClosePullRequest(ctx context.Context, dir string, number int, comment string) error

ClosePullRequest implements GitHub and asserts the effect.

`verbs-assert-effects-not-exit-codes` is a Principle-level MUST, and this is a destructive call whose result lands in a durable report: a `gh pr close` that exits 0 without closing — a permission edge, a race with a merge — must not be recorded as done.

func (ExecGitHub) CreateDraftPullRequest

func (hub ExecGitHub) CreateDraftPullRequest(ctx context.Context, dir, base, head, title, body string) (PullRequest, error)

CreateDraftPullRequest implements GitHub.

func (ExecGitHub) DefaultBranchStatus

func (hub ExecGitHub) DefaultBranchStatus(ctx context.Context, dir, branch string) (string, error)

DefaultBranchStatus implements GitHub. An unresolvable status is reported as an empty conclusion rather than an error: a red-`main` check that cannot run must be reported as unknown, never assumed green.

func (ExecGitHub) OpenPullRequestsTargeting

func (hub ExecGitHub) OpenPullRequestsTargeting(ctx context.Context, dir, base string) ([]PullRequest, error)

OpenPullRequestsTargeting implements GitHub.

func (ExecGitHub) PullRequest

func (hub ExecGitHub) PullRequest(ctx context.Context, dir string, number int) (PullRequest, bool, error)

PullRequest implements GitHub.

func (ExecGitHub) PullRequestForBranch

func (hub ExecGitHub) PullRequestForBranch(ctx context.Context, dir, branch string) (PullRequest, bool, error)

PullRequestForBranch implements GitHub.

func (ExecGitHub) RetargetPullRequest

func (hub ExecGitHub) RetargetPullRequest(ctx context.Context, dir string, number int, base string) error

RetargetPullRequest implements GitHub and asserts the effect.

func (ExecGitHub) UpdatePullRequestTitle added in v0.129.0

func (hub ExecGitHub) UpdatePullRequestTitle(ctx context.Context, dir string, number int, title string) error

UpdatePullRequestTitle implements GitHub and asserts the effect.

type FileEventLog

type FileEventLog struct {
	Path string
	Now  func() time.Time
}

FileEventLog is the append-only JSONL log beside a stream's state.

Concurrent appends are safe: each append takes an exclusive lock on the log and writes one already-encoded line, so two verbs running at once interleave records rather than corrupt one.

func (*FileEventLog) Append

func (log *FileEventLog) Append(event Event) error

Append writes one redacted event. A failure to record an event never fails the verb's own work — callers log it — but it is reported rather than swallowed here.

type Git

type Git interface {
	// CurrentBranch reports the checked-out branch of a worktree.
	CurrentBranch(ctx context.Context, dir string) (string, error)
	// DefaultBranch reports the repository's default branch from local
	// state. It must never contact the network.
	DefaultBranch(ctx context.Context, dir string) (string, error)
	// Fetch refreshes origin so a verb acts on a live view rather than a
	// session-start snapshot.
	Fetch(ctx context.Context, dir string) error
	// PushBranch publishes branch from dir and sets its upstream. It must
	// verify the pushed ref rather than trust the exit code.
	PushBranch(ctx context.Context, dir, branch string) (sha string, err error)
	// RemoteHead resolves origin/<branch>, reporting ok=false when the
	// branch does not exist on the remote.
	RemoteHead(ctx context.Context, dir, branch string) (sha string, ok bool, err error)
	// LocalHead resolves the worktree's HEAD.
	LocalHead(ctx context.Context, dir string) (string, error)
	// LocalBranchHead resolves a local branch without consulting origin. It is
	// used when a stream worktree has already disappeared: a surviving local
	// stream ref can still carry unpushed work that must block retirement.
	LocalBranchHead(ctx context.Context, dir, branch string) (sha string, ok bool, err error)
	// IsAncestor reports whether ancestor is reachable from descendant. Stream
	// cleanup uses commit ancestry, not patch similarity, when a merged PR is
	// the sole receipt that authorizes retiring a squash-merged member.
	IsAncestor(ctx context.Context, dir, ancestor, descendant string) (bool, error)
	// CommitsNotIn lists the commits on branch whose patch base does not
	// already carry, by patch identity rather than by SHA — a rebase landing
	// rewrites SHAs, so an ancestry test would refuse every landed stream
	// forever. Each commit carries its `git patch-id --stable`, which is what
	// clusters N branches carrying one body of work into one item.
	CommitsNotIn(ctx context.Context, dir, branch, base string) ([]Commit, error)
	// DirtyPaths lists modified or untracked paths in a worktree.
	DirtyPaths(ctx context.Context, dir string) ([]string, error)
	// Tags lists tags matching a glob pattern, newest version first.
	Tags(ctx context.Context, dir, pattern string) ([]string, error)
	// LogSubjects lists the subjects of commits in the exclusive range
	// from..to. An empty from means "every commit reachable from to".
	LogSubjects(ctx context.Context, dir, from, to string) ([]string, error)
	// DeleteRemoteBranch removes a branch from origin only if it remains at
	// expectedSHA, then verifies it is gone. The lease closes the interval
	// between stream proof and deletion: a later remote write must refuse, not
	// be erased as stream scaffolding.
	DeleteRemoteBranch(ctx context.Context, dir, branch, expectedSHA string) error
}

Git is the local Git surface stream verbs use.

type GitHub

type GitHub interface {
	// CreateDraftPullRequest opens a draft pull request from head to base.
	CreateDraftPullRequest(ctx context.Context, dir, base, head, title, body string) (PullRequest, error)
	// UpdatePullRequestTitle changes one pull request's title and verifies the
	// remote effect before returning success.
	UpdatePullRequestTitle(ctx context.Context, dir string, number int, title string) error
	// PullRequestForBranch finds the open pull request whose head is branch.
	PullRequestForBranch(ctx context.Context, dir, branch string) (PullRequest, bool, error)
	// OpenPullRequestsTargeting lists every open pull request whose base is
	// base. `stream end` needs this to find the agent pull requests that
	// GitHub would silently retarget at `main` when the stream branch is
	// deleted.
	OpenPullRequestsTargeting(ctx context.Context, dir, base string) ([]PullRequest, error)
	// ClosePullRequest closes one pull request with a comment saying why.
	ClosePullRequest(ctx context.Context, dir string, number int, comment string) error
	// RetargetPullRequest moves one pull request onto a new base.
	RetargetPullRequest(ctx context.Context, dir string, number int, base string) error
	// PullRequest re-reads one pull request by number, reporting found=false
	// when it does not resolve. It is what makes a close or a retarget an
	// asserted effect rather than a trusted exit code.
	PullRequest(ctx context.Context, dir string, number int) (PullRequest, bool, error)
	// DefaultBranchStatus reports the conclusion of the most recent
	// completed CI run on the repository's default branch: "success",
	// "failure", or "" when it cannot be established.
	DefaultBranchStatus(ctx context.Context, dir, branch string) (conclusion string, err error)
}

GitHub is the remote surface stream verbs use. Every method takes the worktree directory so the underlying tool resolves the repository the same way an operator standing in that directory would.

type GoModule

type GoModule struct {
	// Path is the module path declared by the manifest.
	Path string `json:"path"`
	// Manifest is the worktree-relative go.mod path.
	Manifest string `json:"manifest"`
	// Directory is the worktree-relative module directory, which is what a
	// workspace `use` entry names. It is "." for a module at the root.
	Directory string `json:"directory"`
}

GoModule is one Go module inside a worktree.

func GoModules

func GoModules(root string) ([]GoModule, error)

GoModules lists every Go module in a worktree: `backend/`, the module root where there is no `backend/`, and any nested tooling module.

A workspace containing only the library would leave the consumer's own module outside the workspace it now sits under, and `go build ./...` in `backend/` would not resolve at all — which is why this enumerates the whole worktree rather than a conventional pair of paths.

Implements: dependency-streams#req:go-consumers-link-through-an-untracked-go-work.

type HooksChecker

type HooksChecker func(path string) (findings []string, err error)

HooksChecker answers "are this checkout's WB-managed hooks healthy" for the readiness preflight. It is a port so the refusal it drives is provable without installing real hooks into a temporary repository.

func InstalledHooksChecker

func InstalledHooksChecker(wbExecutable, projectsRoot string) HooksChecker

InstalledHooksChecker is the production checker: the existing `wb hooks check`, called in process.

type Identity

type Identity struct {
	Ecosystem Ecosystem `json:"ecosystem"`
	Name      string    `json:"name"`
	// Manifest is the worktree-relative path that declares the identity.
	Manifest string `json:"manifest"`
	// Directory is the worktree-relative directory of the manifest. For a Go
	// module it is the directory a workspace `use` entry names.
	Directory string `json:"directory"`
	// Workspace is the worktree-relative npm workspace that owns Manifest.
	// It is "." for packages in a repository-root workspace.
	Workspace string `json:"workspace,omitempty"`
}

Identity is one published identity of a library: the name a consumer resolves, and the manifest that declares it.

func DiscoverPublished

func DiscoverPublished(root string) ([]Identity, error)

DiscoverPublished reads the identities a library worktree publishes.

Discovery is evidence-based and reads the library worktree itself: the Go module path from `backend/go.mod`, or from the module root where the repository has no `backend/`, and npm package names from `libs/**/package.json` in every repository-owned npm workspace (including nested frontend/). An operator-supplied package name is never accepted as a substitute, because the whole value of a local link is that it exposes what the library actually publishes.

Implements: dependency-streams#req:local-link-discovers-what-the-library-publishes.

type JoinOptions

type JoinOptions struct {
	Name       string
	Repository string
	Role       Role
	Base       string
}

JoinOptions is one `wb stream join` invocation.

type Lease

type Lease struct {
	Login   string `json:"login,omitempty"`
	Machine string `json:"machine,omitempty"`
	Session string `json:"session,omitempty"`
	// RecordedHead is the stream head WB last observed on the remote. It is
	// what `wb stream sync` passes to `--force-with-lease`; it is empty
	// until a stream push records one.
	RecordedHead string    `json:"recorded_head,omitempty"`
	HeldSince    time.Time `json:"held_since"`
}

Lease is the stream's claim on `stream/<name>` in one repository. It records the live registered session as well as the machine, because `claims-carry-a-session-identity` makes a push from a different live session on the same machine a refusal rather than a silent overwrite.

func (Lease) Holder

func (lease Lease) Holder() string

Holder renders the lease holder the way the claim store spells it.

type Link struct {
	// Library is the library worktree the link points at. It may no longer
	// exist when the link is undone; that is deliberate.
	Library string `json:"library"`
	// LibraryRepository names the owning repository, which survives the
	// worktree being removed.
	LibraryRepository string    `json:"library_repository"`
	Mechanism         Mechanism `json:"mechanism"`
	State             LinkState `json:"state,omitempty"`
	// Identity is the published identity being replaced: a Go module path or
	// an npm package name.
	Identity string `json:"identity"`
	// PreviousVersion is the version the consumer declared before linking.
	// It is what `--undo` restores.
	PreviousVersion string `json:"previous_version,omitempty"`
	// ContentHash identifies the library working tree, including modified and
	// untracked files, that this link exposed. The library is uncommitted by
	// construction, so it has no SHA.
	ContentHash string `json:"content_hash,omitempty"`
	// Artifacts are the untracked paths the link created, relative to the
	// consumer worktree, so removal never guesses.
	Artifacts []string `json:"artifacts,omitempty"`
	// Workspace is the consumer-relative npm workspace containing the link.
	// Empty and "." both mean the repository root for backward compatibility.
	Workspace string    `json:"workspace,omitempty"`
	CreatedAt time.Time `json:"created_at"`
}

Link is one live local link from a consumer worktree to a library worktree. It carries everything needed to reverse the link exactly, so `--undo` depends on the record rather than on the library worktree still existing.

type LinkState added in v0.110.0

type LinkState string

LinkState distinguishes a record written before its filesystem operation from one whose operation completed. Empty is a legacy applied record.

const (
	LinkStateIntent  LinkState = "intent"
	LinkStateApplied LinkState = "applied"
)

type LinkedConsumer

type LinkedConsumer struct {
	Repository      string    `json:"repository"`
	Worktree        string    `json:"worktree"`
	Library         string    `json:"library"`
	Mechanism       Mechanism `json:"mechanism"`
	Identity        string    `json:"identity"`
	PreviousVersion string    `json:"previous_version,omitempty"`
	ContentHash     string    `json:"content_hash,omitempty"`
}

LinkedConsumer is gap one.

type LinkedConsumerBinding added in v0.117.3

type LinkedConsumerBinding struct {
	Repository string `json:"repository"`
	Worktree   string `json:"worktree"`
	Links      []Link `json:"links,omitempty"`
}

type Mechanism

type Mechanism string

Mechanism names how one live local link replaces a published dependency.

const (
	// MechanismGoWork is an untracked `go.work` at the consumer worktree root.
	MechanismGoWork Mechanism = "go.work"
	// MechanismPnpmLink is the package manager's own link from a built dist
	// into the consumer's node_modules.
	MechanismPnpmLink Mechanism = "pnpm-link"
)

type Member

type Member struct {
	Repository string `json:"repository"`
	Role       Role   `json:"role"`
	// Worktree is the checkout `wb worktree create` published for this
	// member. It is the path every later verb acts in.
	Worktree string `json:"worktree"`
	// Canonical is the shared clone the worktree was cut from, recorded so
	// `status` can answer without re-deriving it from the projects root.
	Canonical string `json:"canonical"`
	Branch    string `json:"branch"`
	// Base is the branch the draft pull request targets.
	Base string `json:"base"`
	// PullRequest is the draft pull request opened from Branch to Base. Zero
	// means none was opened; PullRequestError then says why.
	PullRequest      int    `json:"pull_request,omitempty"`
	PullRequestURL   string `json:"pull_request_url,omitempty"`
	PullRequestError string `json:"pull_request_error,omitempty"`
	Lease            Lease  `json:"lease"`
	// Links are the live local links this member holds as a consumer. They
	// are written by `wb deps propagate local` and are the source of truth
	// for `--undo`, for `wb stream status`, and for the refusal that keeps a
	// linked worktree from being pushed or landed.
	Links    []Link    `json:"links,omitempty"`
	JoinedAt time.Time `json:"joined_at"`
}

Member is one repository inside a stream.

type MemberLink struct {
	Member Member
	Link   Link
}

MemberLink pairs a link with the member that holds it.

type MemberStatus

type MemberStatus struct {
	Repository     string `json:"repository"`
	Role           Role   `json:"role"`
	Worktree       string `json:"worktree"`
	Branch         string `json:"branch"`
	Base           string `json:"base"`
	PullRequest    int    `json:"pull_request,omitempty"`
	PullRequestURL string `json:"pull_request_url,omitempty"`
	// PullRequestMissing carries the reason no draft pull request exists.
	PullRequestMissing string `json:"pull_request_missing,omitempty"`
	// PullRequestRecovery is the exact WB verb that retries the missing
	// publication without asking an operator to hand-roll Git or GitHub calls.
	PullRequestRecovery string `json:"pull_request_recovery,omitempty"`
	// PullRequestBlocked explains when no safe automatic retry exists. A
	// divergent shared branch needs an owner decision; advertising join there
	// would only repeat the same refusal forever.
	PullRequestBlocked string `json:"pull_request_blocked,omitempty"`
	// PullRequestUnrecorded says GitHub has the member PR but stream.json does
	// not. Status remains read-only and join persists the discovered receipt.
	PullRequestUnrecorded bool `json:"pull_request_unrecorded,omitempty"`
	// LastPublicationError is persisted evidence from an earlier mutating
	// verb. It is separate from current discovery so status output cannot make
	// a historical push/create transcript look like work it just performed.
	LastPublicationError *PublicationFailure `json:"last_publication_error,omitempty"`
	// Unabsorbed is the number of commits on the stream branch that the base
	// does not carry by patch identity.
	Unabsorbed int `json:"unabsorbed"`
	// UnabsorbedClusters names each cluster of patch-identical work, so N
	// branches carrying one body of work read as one item rather than N. The
	// label is the commit subject; the IDENTITY is the patch id, so two
	// commits sharing a message are not collapsed and one change re-applied
	// with an edited message still is.
	UnabsorbedClusters []string `json:"unabsorbed_clusters,omitempty"`
	LeaseHolder        string   `json:"lease_holder,omitempty"`
	RecordedHead       string   `json:"recorded_head,omitempty"`
	LiveLinks          int      `json:"live_links"`
}

MemberStatus is one member's own row.

type MergedUntagged

type MergedUntagged struct {
	Repository string   `json:"repository"`
	Base       string   `json:"base"`
	LatestTag  string   `json:"latest_tag,omitempty"`
	Commits    []string `json:"commits"`
}

MergedUntagged is gap two.

type Phase

type Phase string

Phase is a stream's lifecycle position. It exists because a stream has a state between "does not exist" and "usable": PhaseCreating names the window in which worktrees, branches and draft pull requests are being published.

Recording that window is what makes an interrupted `stream start` recoverable: a half-created stream is a state a verb can see, so `wb stream end` can retire exactly what was published. Without it, a crash after the first push strands branches and pull requests no verb can reach.

const (
	// PhaseCreating means the stream record exists and its side effects are
	// being published. Every member carries its intended coordinates from the
	// moment the record is written.
	PhaseCreating Phase = "creating"
	// PhaseOpen means every member was published successfully.
	PhaseOpen Phase = "open"
	// PhaseEnded means `wb stream end` retired it.
	PhaseEnded Phase = "ended"
)

type Preflight

type Preflight struct {
	Findings []PreflightFinding `json:"findings"`
}

Preflight is the fleet-readiness report `wb stream start` produces before it creates anything.

Implements: dependency-streams#req:stream-start-proves-the-fleet-is-ready, dependency-streams#req:push-hook-defers-to-ci-on-stream-branches (its concurrency clause).

func RunPreflight

func RunPreflight(ctx context.Context, hub GitHub, checkHooksIn HooksChecker, inputs []PreflightInput) Preflight

RunPreflight runs every readiness check over the proposed members.

Every check reports per repository. A check that cannot run reports PreflightUnknown with its reason rather than passing: this is the same rule `batch-verification-runs-what-ci-runs` applies to skipped mechanisms, and for the same reason — a false assurance is worse than no gate.

func (Preflight) Failed

func (preflight Preflight) Failed() []PreflightFinding

Failed reports the findings that must be resolved or explicitly accepted.

type PreflightFinding

type PreflightFinding struct {
	Repository string          `json:"repository"`
	Check      string          `json:"check"`
	Status     PreflightStatus `json:"status"`
	Detail     string          `json:"detail,omitempty"`
}

PreflightFinding is one check's result for one member.

type PreflightInput

type PreflightInput struct {
	Repository string
	// Path is the checkout the checks read. It is the canonical clone at
	// start time: the stream worktrees do not exist yet, and refusing to
	// create them is the whole point of running these first.
	Path string
	// DefaultBranch is the branch the red-`main` check reads.
	DefaultBranch string
}

PreflightInput is one member's coordinates for the readiness checks.

type PreflightStatus

type PreflightStatus string

PreflightStatus is one member check's outcome.

const (
	// PreflightPass means the check ran and found nothing.
	PreflightPass PreflightStatus = "pass"
	// PreflightFail means the check ran and found a problem. A failing check
	// refuses the start unless the caller explicitly reported past it.
	PreflightFail PreflightStatus = "fail"
	// PreflightUnknown means the check could not be established. It is
	// reported, never silently treated as a pass: an unverified assurance is
	// worse than no gate.
	PreflightUnknown PreflightStatus = "unknown"
)

type PublicationFailure added in v0.125.7

type PublicationFailure struct {
	Detail     string     `json:"detail"`
	OccurredAt *time.Time `json:"occurred_at,omitempty"`
}

PublicationFailure is one historical failed publication attempt.

type PullRequest

type PullRequest struct {
	Number int    `json:"number"`
	URL    string `json:"url"`
	Title  string `json:"title"`
	Body   string `json:"body,omitempty"`
	Head   string `json:"head"`
	Base   string `json:"base"`
	Draft  bool   `json:"draft"`
	State  string `json:"state"`
	// HeadSHA and MergeSHA are immutable GitHub identities. They are required
	// when a stream member was already retired by wb pr land: the missing
	// checkout cannot be used as evidence of what the PR actually landed.
	HeadSHA  string `json:"head_sha,omitempty"`
	MergeSHA string `json:"merge_sha,omitempty"`
}

PullRequest is the subset of a pull request stream verbs read.

type Refusal

type Refusal struct {
	Code       string
	Message    string
	Sanctioned []string
}

Refusal is a guard that fired. It carries the stable code a caller branches on and the exact command that satisfies the guard, because `every-refusal-names-the-sanctioned-command` makes a refusal without a next step a hand-written workaround waiting to happen.

func Refused

func Refused(err error) (*Refusal, bool)

Refused reports whether err is a guard refusal rather than a failure, and returns it. A caller uses this to choose exit code 2 over 1.

func (*Refusal) Error

func (refusal *Refusal) Error() string

Error renders the refusal for stderr. It redacts, because a refusal message routinely quotes a child process's output and that output routinely carries a remote URL with an embedded credential.

type Role

type Role string

Role separates the one repository whose published artifacts the others resolve from the repositories that resolve them. Propagation direction is not symmetric, so the distinction is part of the state rather than something each verb re-derives.

const (
	// RoleLibrary is the repository whose published artifacts the consumers
	// resolve. Exactly one member holds it.
	RoleLibrary Role = "library"
	// RoleConsumer is a repository that resolves the library's artifacts.
	RoleConsumer Role = "consumer"
)

type SquashAbsorptionReceipt added in v0.127.3

type SquashAbsorptionReceipt struct {
	Target       string
	SourceBranch string
	SourceSHA    string
	CandidateSHA string
	LandingSHA   string
}

SquashAbsorptionReceipt is the exact immutable receipt a stream passes to the worktree lifecycle after proving a clean member is an ancestor of its merged stream pull request. Cleanup verifies it again before removal.

type StartOptions

type StartOptions struct {
	Name string
	// Repositories are the members in declaration order. The first is the
	// library unless Library names another.
	Repositories []string
	Library      string
	// Base overrides the branch the draft pull requests target. Empty
	// resolves each repository's own default branch.
	Base string
}

StartOptions is one `wb stream start` invocation.

type StartResult

type StartResult struct {
	Stream    Stream    `json:"stream"`
	Preflight Preflight `json:"preflight"`
	// Reported are the preflight findings that did not refuse the start but
	// that the operator must see. `stream-start-proves-the-fleet-is-ready`
	// allows a member to be reported instead of refused; nothing may be
	// silently passed.
	Reported []PreflightFinding `json:"reported,omitempty"`
	// TransitiveOmissions names consumers the dependency graph reaches that
	// the operator did not include. Leaving one out is legal; leaving one
	// out silently is not, because remote propagation bumps only members.
	TransitiveOmissions []string `json:"transitive_omissions,omitempty"`
}

StartResult is what `stream start` produced.

type Status

type Status struct {
	Stream string `json:"stream"`
	Open   bool   `json:"open"`
	// Phase distinguishes a stream still being created from a usable one, so
	// an interrupted start is visible rather than looking like a healthy
	// stream with missing pull requests.
	Phase   Phase          `json:"phase"`
	Library string         `json:"library,omitempty"`
	Branch  string         `json:"branch"`
	Members []MemberStatus `json:"members"`
	// LinkedConsumers is gap one: consumers holding a live local link, and
	// the library worktree each is linked to.
	LinkedConsumers []LinkedConsumer `json:"linked_consumers"`
	// MergedUntagged is gap two: library changes that are merged into the
	// base but carry no tag yet.
	MergedUntagged *MergedUntagged `json:"merged_untagged,omitempty"`
	// ConsumersBehind is gap three: consumers still declaring a version
	// older than the library's newest published version.
	ConsumersBehind []ConsumerBehind `json:"consumers_behind"`
	// OpenAgentPullRequests are the pull requests targeting the stream
	// branch. They are the ones GitHub would silently retarget at the base
	// if the stream branch were deleted with them still open.
	OpenAgentPullRequests []AgentPullRequest `json:"open_agent_pull_requests"`
	// Unknowns record every question this report could not answer, so an
	// empty gap list never has to be read as "nothing is wrong".
	Unknowns []string `json:"unknowns,omitempty"`
}

Status is the whole-stream report.

The three gaps are reported separately and named per repository, because a merged-but-untagged library is not the same problem as a consumer left behind, and collapsing them would hide which one is blocking the stream.

Implements: dependency-streams#req:stream-status-reports-the-three-gaps, dependency-streams#req:stream-backlog-is-counted-by-patch-identity.

type Store

type Store struct {
	// Root is <wb-home>/streams. Callers normally build a Store with Open.
	Root string
	// Now is the clock stream timestamps come from. Tests substitute it.
	Now func() time.Time
}

Store reads and writes stream state under WB's home directory.

Every mutation goes through Update, which takes an exclusive lock, re-reads from disk, applies the caller's change and writes atomically. That is what `stream-verbs-re-read-state-before-mutating` requires: a value a verb read when it started is a snapshot, and two verbs running at once must not each write back their own stale copy of the whole stream.

func Open

func Open(projectsRoot string) (*Store, error)

Open resolves WB's home for one projects root and returns the store below it. It does not create anything; the first Save does.

func OpenAt

func OpenAt(root string) *Store

OpenAt returns a store rooted at an exact directory. It is the seam tests and embedders use instead of an environment variable.

func (*Store) Archive

func (store *Store) Archive(name string) (string, error)

Archive renames an ended stream so its name can be used again, keeping the record and its event log intact.

`work-and-event-logs-are-never-pruned` forbids discarding the evidence, but keeping it must not burn the name forever: before this, `start` refused an existing name and `join` refused an ended stream, and the two refusals pointed at each other with no way out.

func (*Store) ArchiveLocked

func (store *Store) ArchiveLocked(name string) (string, error)

ArchiveLocked is Archive for a caller already holding the store lock.

func (*Store) Create

func (store *Store) Create(stream Stream) (Stream, error)

Create reserves a stream name and writes its record.

It is the arbiter for two concurrent `stream start` calls on one name: the exclusive create happens under the store-wide lock and BEFORE either call publishes anything, so the loser refuses with no side effects on the remote. The earlier design let both calls create worktrees, push branches and open pull requests and only then arbitrated the file — which left the loser's effects stranded.

The record is written through the same temp-file-and-rename path Update uses, so an interrupted create can never leave a truncated file that would then refuse every later `stream start` on the machine.

func (*Store) CreateLocked

func (store *Store) CreateLocked(stream Stream) (Stream, error)

CreateLocked is Create for a caller already holding the store lock through WithStoreLock. Calling it without that lock is a defect, not a deadlock: the lock is not reentrant.

func (*Store) Delete

func (store *Store) Delete(name string) error

Delete removes an ended stream and its event log. It refuses an open stream: deleting one would strand its worktrees, branches and pull requests with no record any verb could reach.

func (*Store) Dir

func (store *Store) Dir(name string) string

Dir is the per-stream directory. Its layout is part of the contract: the event log sits beside the state file so a stream is one removable unit.

func (*Store) EventLog

func (store *Store) EventLog(name string) *FileEventLog

EventLog returns the log for one stream in this store.

func (*Store) LinkSourcesForWorktree added in v0.117.2

func (store *Store) LinkSourcesForWorktree(worktree string) ([]StreamLinkSource, error)

LinkSourcesForWorktree returns every live link, across every open stream, whose Library points AT this worktree — the opposite direction from LiveLinksForWorktree. A worktree reported here is not a linked consumer; it is the unpublished source a consumer elsewhere still resolves instead of a published version.

This is the state half of the guard that keeps `wb worktree cleanup` (and any verb that removes a managed worktree while landing, such as `wb pr land` and `wb worktree merge`) from deleting a checkout another stream's consumer still depends on. On 2026-09-07 exactly this happened: a provider worktree was removed while a live stream's `go.work` still named it, and every composed Go command in the consumer failed until the entry was repointed by hand.

func (*Store) List

func (store *Store) List() ([]Stream, []Unreadable, error)

List returns every readable stream in the store, newest first, alongside every stream whose record could not be parsed.

func (*Store) LiveLinksForWorktree

func (store *Store) LiveLinksForWorktree(worktree string) ([]StreamLink, error)

LiveLinksForWorktree returns every live link recorded against one consumer worktree path, across every open stream.

This is the state half of `merge-refuses-a-linked-worktree`. It is exported because the refusal belongs to `wb worktree merge` and `wb pr land`, which must be able to ask the question without importing a stream verb.

func (*Store) Load

func (store *Store) Load(name string) (Stream, error)

Load reads one stream. It returns ErrNotFound when the stream does not exist, so a caller can tell "no such stream" from "the state is unreadable" without matching on prose.

func (*Store) RepositoryStream

func (store *Store) RepositoryStream(repository string) (Stream, bool, []Unreadable, error)

RepositoryStream answers the one-open-stream-per-repository question: which open stream, if any, already holds this repository. It is what `stream start` refuses on, and what names the holder in that refusal.

func (*Store) Update

func (store *Store) Update(name string, mutate func(*Stream) error) (Stream, error)

Update applies mutate to the freshly re-read stream under an exclusive lock and writes the result atomically. mutate may be called only once; returning an error leaves the stored stream untouched.

func (*Store) WithStoreLock

func (store *Store) WithStoreLock(body func() error) error

WithStoreLock runs body while holding the store-wide lock, so a caller can make "no repository is in another open stream" and "reserve this name" one indivisible decision. Without it the guard is a check with a gap after it.

type Stream

type Stream struct {
	SchemaVersion int       `json:"schema_version"`
	Name          string    `json:"name"`
	CreatedAt     time.Time `json:"created_at"`
	UpdatedAt     time.Time `json:"updated_at"`
	// Phase is the lifecycle position. An empty value reads as PhaseOpen so a
	// record written by an older binary stays usable.
	Phase Phase `json:"phase,omitempty"`
	// ArchivedFrom is set when a stream was archived to free its name for a
	// new stream. It records the name the record used to carry.
	ArchivedFrom string `json:"archived_from,omitempty"`
	// EndedAt is set by `wb stream end`. An ended stream is kept, not
	// deleted: its event log and link history are the evidence a later
	// report is built from, and `work-and-event-logs-are-never-pruned`
	// forbids discarding them as a side effect of ending the work.
	EndedAt *time.Time `json:"ended_at,omitempty"`
	Members []Member   `json:"members"`
	// LinkedConsumers are explicitly admitted alternate managed worktrees of a
	// repository already represented by Members. They participate only in
	// local dependency linking; stream branch, PR and repository ownership stay
	// with the member. Recording the exact checkout keeps undo and landing
	// guards authoritative without pretending the alternate branch is a member.
	LinkedConsumers []LinkedConsumerBinding `json:"linked_consumers,omitempty"`
}

Stream is the durable record of one named cross-repository unit of work.

func (Stream) Consumers

func (stream Stream) Consumers() []Member

Consumers returns every member that is not the library, in recorded order.

func (Stream) Library

func (stream Stream) Library() (Member, bool)

Library returns the member holding RoleLibrary.

func (Stream) Lifecycle

func (stream Stream) Lifecycle() Phase

Lifecycle resolves the phase, treating an empty value as PhaseOpen so a record written before phases existed keeps its meaning.

func (Stream) LinkedConsumer added in v0.117.3

func (stream Stream) LinkedConsumer(repository string) (LinkedConsumerBinding, bool)

LinkedConsumer finds one admitted alternate managed worktree by owner/repository. It is distinct from Member: a linked consumer holds only local links, never the stream branch, PR, or repository ownership.

func (stream Stream) LiveLinks() []MemberLink

LiveLinks returns every live link the stream currently records, paired with the consumer repository holding it.

func (Stream) Member

func (stream Stream) Member(repository string) (Member, bool)

Member finds one member by owner/repository.

func (Stream) Open

func (stream Stream) Open() bool

Open reports whether the stream still holds its repositories — which is true while it is being created as well as once it is usable. A repository is only released by ending the stream.

type StreamLink struct {
	Stream     string `json:"stream"`
	Repository string `json:"repository"`
	Link       Link   `json:"link"`
}

StreamLink is one live link qualified by the stream and repository holding it, so a refusal can name both without a second lookup.

type StreamLinkSource added in v0.117.2

type StreamLinkSource struct {
	Stream             string `json:"stream"`
	ConsumerRepository string `json:"consumer_repository"`
	// ConsumerWorktree is the linked consumer's worktree path, so a refusal
	// can name the exact repoint command without a second lookup.
	ConsumerWorktree string `json:"consumer_worktree"`
	Link             Link   `json:"link"`
}

StreamLinkSource is one live link whose unpublished source is a worktree under consideration for removal, qualified by the stream and consumer holding it, so a refusal can name both without a second lookup.

type Unreadable

type Unreadable struct {
	Name   string `json:"name"`
	Path   string `json:"path"`
	Reason string `json:"reason"`
}

Unreadable names one stream whose record could not be parsed.

It is reported rather than returned as an error for the whole store: a single truncated file must not refuse every `wb stream start` on the machine. It is also never silently dropped — a stream WB cannot read may hold live links, and pretending it is absent is the failure mode `status` exists to prevent.

type WorktreeCreator

type WorktreeCreator interface {
	// PlannedWorktree is where Create will publish one repository's checkout.
	// It performs no side effect, so the stream record can carry each
	// member's intended coordinates BEFORE anything is created — which is
	// what makes an interrupted start recoverable.
	PlannedWorktree(task, repository string) (string, error)
	Create(ctx context.Context, task, branch string, repositories []string) ([]CreatedWorktree, error)
	// Remove retires one member's worktree through the existing cleanup
	// path. `stream end` delegates removal rather than deleting directories.
	Remove(ctx context.Context, task, repository, worktree string, receipt *SquashAbsorptionReceipt) error
}

WorktreeCreator is the existing worktree creation path.

`stream-is-a-named-set-of-worktrees` forbids inventing a second creation path, so this port exists to delegate to `wb worktree create` — with its branch policy, prompt archival and fleet-wide claim — rather than to reimplement any of it.

Jump to

Keyboard shortcuts

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