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
- func Add(root string, set *Set, id string) (string, error)
- func Bytes(l *Lane) ([]byte, error)
- func DefaultBoardPath() string
- func Differs(board *DefaultBoard, set *Set) bool
- func Export(l *Lane, dstDir string, overwrite bool) (string, error)
- func LoadOrder(root string) ([]string, error)
- func LoadRemoved(root string) ([]string, error)
- func Materialise(root string, set *Set, b *DefaultBoard) ([]string, error)
- func MaterialiseWorkingSet(root string, set *Set) error
- func Move(ids []string, id string, delta int) []string
- func MoveLane(root string, set *Set, id string, delta int) error
- func ProjectLanesActive(root string) bool
- func ProjectLanesDir(root string) string
- func Publish(l *Lane, dstDir, who string, overwrite bool) (string, error)
- func RefreshDrift(d DriftEntry) error
- func Remove(root string, set *Set, store *ticket.Store, id string) (string, error)
- func ResolveOptions(set *Set, board *DefaultBoard) []ticket.BodyOption
- func SaveDefaultBoard(b *DefaultBoard) error
- func SaveOrder(root string, ids []string) error
- func SaveRemoved(root string, ids []string) error
- func UserLanesDir() string
- func Validate(board *DefaultBoard, set *Set) []string
- type DefaultBoard
- type DriftEntry
- type Lane
- type Set
- type SharedLane
Constants ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 SaveRemoved ¶
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 ¶
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 ¶
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 Installable ¶
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 ¶
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 ¶
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 ¶
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) Options ¶
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 ¶
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.
type SharedLane ¶
type SharedLane struct {
}
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.