lane

package
v0.1.0 Latest Latest
Warning

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

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

Documentation

Overview

Package lane resolves the board's columns.

Seven base lanes are compiled into the binary so a fresh clone renders a working board with no repository configuration. Custom lanes are single markdown files under ~/.jaira/lanes (the catalogue) or, when a project has scoped itself down, under <root>/.jaira/lanes (see ProjectLanesDir) — which makes sharing one "send someone the file" rather than "reproduce my config". Both kinds go through this same parser: a built-in lane is not a special case, it is just a lane that happens to ship inside the executable.

A custom lane whose id matches a built-in overrides it, prompt included, rather than being refused. That is deliberate: the user owns the pipeline and nothing about it is off limits, including the protections a built-in carries. The loader's job is not to stop an override, only to make sure it is never quiet — see Load.

Index

Constants

View Source
const Template = `` /* 1704-byte string literal not displayed */

Template is the skeleton a fresh lane file starts from: every field parse() reads, commented, plus a prompt body — a file this parses without error, so the template cannot rot away from the parser it feeds.

Shared by 'jaira lanes template' and the lane settings screen's 'n' key, so there is one skeleton to keep in sync with parse(), not two.

Variables

This section is empty.

Functions

func Add

func Add(root string, set *Set, id string) (string, error)

Add brings a built-in or catalogue lane into this project: materialising the working set first when the project has no lane directory yet, then exporting the lane's file and appending its id to the order file. It refuses a lane already part of set — "already in this project" — rather than re-exporting it.

func Bytes

func Bytes(l *Lane) ([]byte, error)

Bytes returns a lane's file content verbatim: from the embedded binary for a built-in, from disk for anything else. Callers that need to copy a lane (Export, Publish, Materialise) go through this rather than re-serialising the parsed struct, so unknown fields, comments and field order all survive — the file format is the API.

func DefaultBoardPath

func DefaultBoardPath() string

DefaultBoardPath is the per-user file that decides a new board's lanes and which ticket Options start ticked. $JAIRA_DEFAULT_BOARD overrides it — an explicit override rather than a sibling of UserLanesDir, because a test needs to point at a temp file and deriving a sibling path from an already-overridable directory breaks the day someone points JAIRA_LANES_DIR at something that is not <something>/lanes.

func Differs

func Differs(board *DefaultBoard, set *Set) bool

Differs reports whether board's selection differs from the built-in set: a different set of ids, or a selected lane that resolves — through set, the catalogue-aware load already in hand — to something other than the built-in of that id. Compared as a set, not a list: reordering alone must not trigger materialisation, since order is precedence's business, not the default board's.

func Export

func Export(l *Lane, dstDir string, overwrite bool) (string, error)

Export copies a lane's file, verbatim, from wherever it was loaded from into dstDir — the catalogue-to-project move. It refuses to overwrite an existing file unless overwrite is true, so the caller (the lane settings screen) decides rather than silently clobbering a project's own copy.

func LoadOrder

func LoadOrder(root string) ([]string, error)

LoadOrder reads a project's column order file. An absent file is not an error: it means the project has not customised its column order, and callers fall back to today's after:-anchor-derived order.

func LoadRemoved

func LoadRemoved(root string) ([]string, error)

LoadRemoved reads the ids explicitly removed from this project.

func Materialise

func Materialise(root string, set *Set, b *DefaultBoard) ([]string, error)

Materialise writes <root>/.jaira/lanes/ only when board's selection differs from the built-in set — the point of the criterion is that a repo whose owner changed nothing carries no lane files at all. set is used to resolve each selected id to its lane (built-in or catalogue override) and to read its bytes; an id the set does not have is warned about, on b, rather than failing the whole write.

func MaterialiseWorkingSet

func MaterialiseWorkingSet(root string, set *Set) error

MaterialiseWorkingSet writes every currently loaded lane into this project's own lane directory, verbatim, via Export — but only when that directory is not yet authoritative per D-03 (ProjectLanesActive is false). Add, Remove and MoveLane all call this before making their own change, so a project's first lane change can never leave it with only the one lane that change touched: the failure mode "directory present means the whole list" exists to prevent.

It is a no-op once the directory is authoritative, which also makes it safe to call on every mutation without double-writing.

func Move

func Move(ids []string, id string, delta int) []string

Move returns a copy of ids with id shifted delta positions — -1 is one step toward the front, +1 one step toward the back. It is the user's model exactly: moving a lane one step swaps it with its neighbour. Moving past either end is a no-op, not an error and not a wrap-around: asking to move further than the board allows is not the same as asking for a mistake.

func MoveLane

func MoveLane(root string, set *Set, id string, delta int) error

MoveLane shifts id one step in this project's column order and writes the order file — the one implementation the CLI's 'lanes move' and the settings screen's H/L keys both call, so "move a lane" never has two bodies to drift apart. It materialises the working set first when the project has no lane directory yet, for the same reason Add does.

func ProjectLanesActive

func ProjectLanesActive(root string) bool

ProjectLanesActive reports whether root's project lane directory is currently authoritative per D-03: present and holding at least one lane file. Load and 'jaira lanes path' both need this same test, so it lives here once rather than as two copies that could drift apart.

func ProjectLanesDir

func ProjectLanesDir(root string) string

ProjectLanesDir is where a project scopes itself down to the lanes it actually uses. Per D-03 it is authoritative when present and non-empty: it replaces the catalogue as this project's second lane source rather than adding to it, so a project directory holding only three lane files means a three-lane board, not the catalogue plus three overrides.

func Publish

func Publish(l *Lane, dstDir, who string, overwrite bool) (string, error)

Publish copies a lane's file into dstDir (normally Store.SharedDir()/identity.Slug(who)), stamping creator: with who when the file does not already declare one. It refuses to overwrite an existing file unless overwrite is true.

func RefreshDrift

func RefreshDrift(d DriftEntry) error

RefreshDrift pulls the catalogue copy into the project, in the direction the user asked for — nothing syncs on its own, per D-02.

func Remove

func Remove(root string, set *Set, store *ticket.Store, id string) (string, error)

Remove takes a lane out of this project — never the catalogue — refusing when any ticket currently sits in it, naming them: a lane that vanishes under a ticket leaves it in a lane nothing knows. It materialises the working set first when the project has no lane directory yet, for the same reason Add does.

A removed lane's project copy, if any, is deleted, and its id is recorded in the removed-lanes tombstone (see removedFileName) so Load excludes it even when it is a built-in — Load always injects built-ins first, so a built-in has no file whose absence alone could mean "removed".

func ResolveOptions

func ResolveOptions(set *Set, board *DefaultBoard) []ticket.BodyOption

ResolveOptions turns a default board's option choices into the ticked/ unticked list a new ticket's Options checklist needs, against the options this set's installed lanes actually declare. An option the board names that no lane requires is silently dropped here — 'jaira lanes' reporting that typo is Validate's job, not this function's.

func SaveDefaultBoard

func SaveDefaultBoard(b *DefaultBoard) error

SaveDefaultBoard writes b to b.Path with lanes: and options: in a fixed order, so a saved file diffs predictably. The prose body is preserved verbatim if the file already had one.

func SaveOrder

func SaveOrder(root string, ids []string) error

SaveOrder writes a project's column order file, one id per line.

func SaveRemoved

func SaveRemoved(root string, ids []string) error

SaveRemoved writes the ids explicitly removed from this project.

func UserLanesDir

func UserLanesDir() string

UserLanesDir is where custom lane files live. This is the catalogue: every lane a user has built or adopted, independent of any one project.

func Validate

func Validate(board *DefaultBoard, set *Set) []string

Validate checks a default board against the currently installed lanes, returning warnings in the same shape core/lane's own loader already uses: an unknown lane id, an unknown option, or (carried over from LoadDefaultBoard) an unparseable file. There is one warning channel, already surfaced by 'jaira lanes', --json, the TUI warnings block and every command through loadEnv — a second reporting path for the same class of problem would be the mistake here.

board.Warnings is included as-is rather than re-derived: LoadDefaultBoard is the only place that knows whether the file failed to parse.

Types

type DefaultBoard

type DefaultBoard struct {
	Lanes    []string
	Options  []string
	Path     string
	Warnings []string
	// Body is the prose below the frontmatter, preserved across a save so
	// SaveDefaultBoard never erases a note the user left themselves.
	Body string
}

DefaultBoard is a per-user setting: which lanes a freshly initialised board gets, and which ticket Options start ticked. An absent file, an absent lanes: field, or an empty list all mean the same thing — the built-ins — which is easy to get backwards, so it is stated here rather than only in the file format.

func LoadDefaultBoard

func LoadDefaultBoard() (*DefaultBoard, error)

LoadDefaultBoard reads DefaultBoardPath(). No file at all is the normal state, not a missing-configuration error, and returns a zero board. A file that exists but does not parse is warned about and treated the same as absent, rather than taking 'jaira init' or 'jaira lanes' down over one bad file — a teammate's shared default board naming their own lane, or a typo, must not break your board.

type DriftEntry

type DriftEntry struct {
	ID            string
	ProjectPath   string
	CataloguePath string
}

DriftEntry names one lane whose project copy no longer matches its catalogue copy of the same id.

func Drift

func Drift(root string, set *Set) ([]DriftEntry, error)

Drift implements D-02: it compares every lane this project's directory supplies against a catalogue file of the same id, and reports the ones whose bytes differ. Editing a lane writes through to the catalogue immediately, so it is the project's copy that goes stale, not the other way round — nothing here fixes that automatically; see RefreshDrift.

Checked only when the caller asks (the lane settings screen, on open), per the decided design: checking on every command would mean every invocation of the loader pays for a comparison most callers never look at.

type Lane

type Lane struct {
	ID          string
	Name        string
	Description string

	// After anchors ordering to another lane's ID. Anchoring is used instead of
	// a numeric position because two independently written lane files would
	// otherwise both claim the same slot.
	After string

	// Precedence orders lanes by how far along the work is, and is what a merge
	// consults so concurrent edits never revert forward progress. It is
	// deliberately separate from display order: Blocked appears last on the
	// board but must not outrank active work in a merge.
	Precedence int

	// Agentic marks a lane whose work is performed by a subagent.
	Agentic   bool
	Terminal  bool
	ModelTier string

	// InputRequires names the ticket fields assembled into a subagent's bounded
	// input. OutputProduces names the fields it must return.
	InputRequires  []string
	OutputProduces []string

	RequiresQuestion       bool
	RequiresOutcome        bool
	RequiresNonModelSignal bool

	// RequiresBlockedReason means a ticket may not be parked here without saying
	// what it is waiting on. Parking is cheap and forgetting is silent, so this
	// is the one place the board charges for it.
	RequiresBlockedReason bool

	// RequiresCommits means a ticket cannot enter this lane without naming the
	// commits that carry its change. It sits on the done lane rather than on
	// the implementing lane's output contract, so work can move through review
	// uncommitted — but nothing is accepted that cannot be checked.
	RequiresCommits bool

	// RequiresHumanExit means no agent may move a ticket out of this lane. It is
	// the difference between a review step that is conventionally respected and
	// one that actually stops: an agent that decides its own work passed review
	// would make the step decorative.
	RequiresHumanExit bool

	// RequiresOption names an entry in a ticket's Options checklist. When set,
	// this lane is only part of a ticket's path if that option is ticked — which
	// is how one ticket is planned and reviewed while another goes straight to
	// implementation, without either lane being uninstalled.
	RequiresOption string

	// RequiresSpecified marks the first lane, in precedence order, that sets
	// this: the boundary between thinking about a ticket and working on it.
	// Everything before it is a place a half-formed ticket may sit; nothing at
	// or after it may hold a ticket that is missing its promotion fields.
	RequiresSpecified bool

	// Prompt is the markdown body: the instruction given to the subagent.
	Prompt string

	// Creator names who wrote this lane, for provenance once a lane has been
	// adopted or copied between machines. A built-in with no creator: field
	// defaults to "jaira" rather than having the field written into all nine
	// shipped files; a custom lane with no creator: field is left empty, since
	// "shipped by the tool" and "author unknown" are different facts.
	Creator string

	// Builtin distinguishes shipped lanes from user-installed ones.
	Builtin bool
	// Source is the path a custom lane came from, for error messages.
	Source string
	// Overrides names the built-in this lane displaced, empty if it does not
	// override anything. Set by Load, not by parse: overriding is a fact about
	// resolution, not about the file itself.
	Overrides string
	// Unknown marks a synthetic passthrough lane created for a ticket whose
	// status names a lane this installation does not have.
	Unknown bool
}

Lane is one column of the board.

func Adopt

func Adopt(srcPath, dstDir string, overwrite bool) (*Lane, string, error)

Adopt parses srcPath first — an unparseable file must never land in the catalogue — then copies its bytes verbatim into dstDir under the parsed lane's own id, never the source filename. It refuses to overwrite an existing file unless overwrite is true.

func Builtins

func Builtins() ([]*Lane, error)

Builtins returns the seven lanes shipped in the binary.

func Installable

func Installable(set *Set) ([]*Lane, error)

Installable lists every built-in and catalogue lane not already part of set — the "add a lane to this project" catalogue, for both 'jaira lanes add' and the settings screen's '+' column. A file that fails to read or parse is skipped rather than failing the whole listing, matching Shared's treatment of the same class of problem.

func Passthrough

func Passthrough(id string) *Lane

Passthrough synthesises a read-only column for a status no installed lane claims. Hiding such tickets would be a worse failure than showing an inert column: the ticket still exists and the user needs to know it does.

type Set

type Set struct {
	Lanes []*Lane

	// Warnings records non-fatal problems (a custom lane anchored to a lane that
	// is not installed, for instance) so the caller can surface them without
	// failing to draw a board.
	Warnings []string
	// contains filtered or unexported fields
}

Set is the resolved, ordered collection of lanes.

func Load

func Load(root string) (*Set, error)

Load resolves the full lane set: built-ins first, then either the project's own lane directory or the catalogue, per D-03.

root is the board's root. An empty root means no project is in hand — the launcher spans many boards and cannot scope to one — and Load falls back to built-ins plus the catalogue, exactly as it would for any project with no <root>/.jaira/lanes directory of its own.

A custom lane whose id collides with a built-in overrides it, prompt included, rather than being refused. The override is always reported as a warning naming the file, the id and the built-in it displaced. If the override also drops a protection the built-in carried — requires-human-exit, terminal, requires-outcome or requires-nonmodel-signal — a second, separate warning names which protection is gone: those exist to stop an agent accepting its own work, and that is exactly what losing one silently would allow.

func (*Set) Columns

func (s *Set) Columns(statuses []string) []*Lane

Columns returns the lanes to draw for a given set of ticket statuses, appending passthrough columns for any status with no matching lane. Unknown lanes are sorted alphabetically among themselves so the board is deterministic.

func (*Set) Default

func (s *Set) Default() *Lane

Default is the lane new tickets enter.

func (*Set) Get

func (s *Set) Get(id string) (*Lane, bool)

Get returns a lane by ID.

func (*Set) IDs

func (s *Set) IDs() []string

IDs lists lane IDs in display order.

func (*Set) Index

func (s *Set) Index(id string) int

Index reports a lane's display position, or -1.

func (*Set) Options

func (s *Set) Options() []string

Options lists the distinct RequiresOption values declared by the installed lanes, in display order. A ticket's Options checklist is derived from what is actually installed rather than a fixed list, so a user-written optional lane shows up in it too, without the loader needing to know its name.

func (*Set) Precedence

func (s *Set) Precedence(id string) int

Precedence returns a lane's merge precedence. An unknown lane sorts below everything known, so a merge never promotes a ticket into a lane this installation cannot reason about.

func (*Set) Terminal

func (s *Set) Terminal() *Lane

Terminal is the lane a signed-off ticket lands in — the first lane declaring itself terminal, which is where accepting work moves it to.

type SharedLane

type SharedLane struct {
	Lane   *Lane
	Folder string
	Path   string
}

SharedLane is one lane found under a project's .jaira/shared/ tree, alongside the folder (normally the publisher's identity.Slug) it came from.

func Shared

func Shared(root string) (lanes []SharedLane, warnings []string, err error)

Shared walks <root>/.jaira/shared/*/*.md and returns every lane it can parse, alongside the folder it came from and its path. A file that fails to read or parse is skipped and reported in warnings rather than failing the walk — one teammate's broken file must not hide everyone else's lanes.

Shared lanes are never loaded by Load: they are visible here, adoptable through Adopt, and otherwise inert. That separation is the whole security story for T-5-02 — a committed shared lane is untrusted prompt content that would otherwise run at whatever model-tier it declares — so nothing should ever wire this function's result into Load.

Jump to

Keyboard shortcuts

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