Help us shape the future of Go! Take the 2026 Go Developer Survey

backlog

package
v0.11.1 Latest Latest
Warning

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

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package backlog reads the repository's backlog: one Markdown file per item under docs/backlog/, and the index a small tool regenerates from them.

The shape is the owner's ruling of 2026-10-20 (docs/rigor-modes/, D26–D32). An item is a file named `<id>-<slug>.md`, where the id is the day the item was filed and a two-digit sequence that day — so two pull requests filing an item at once write two differently named files and the merge is a union of disjoint files rather than a conflict (D26). The file carries eight front-matter fields, each required, and a body whose headings are named (D27). The index is derived state: it is regenerated, never merged by hand, and two branches that regenerated it at once are reconciled by re-running the command (D28).

Nothing here reads a clock, a network or an environment: every date comes from the item file that carries it, so the same folder renders to the same bytes and a regeneration in review diffs only where an item changed (D27's reason for taking the date from the record rather than from `time.Now`).

Index

Constants

View Source
const (
	// Dir is the folder the items live in, relative to a repository root.
	Dir = "docs/backlog"
	// IndexName is the generated index inside Dir. It is derived state and is
	// never hand-edited (D28).
	IndexName = "README.md"
	// FormatName is the hand-written document stating the format an item
	// follows, inside Dir. It is not an item and carries no front matter.
	FormatName = "FORMAT.md"
)
View Source
const ClosedHeading = "## Closed by"

ClosedHeading is D31's closing section: the one heading a `closed` item carries and no other item does. It is named because Close writes it and the folder's pin requires it of a status, and the two have to agree on the heading's own bytes — Headings carries this value rather than a second literal, so the list and the writer cannot drift apart.

View Source
const Header = "<!-- generated by fugaro backlog index; do not edit -->"

Header is the index's first line: the "generated by <command>; do not edit" shape internal/clidoc's documents carry (D28). It names the command that writes the file, so a reader who finds a stale index has the one command that fixes it on the first line.

View Source
const MaxSlugRunes = 60

MaxSlugRunes bounds the slug half of a file name. An item's title is a filer's own words and a file name is what a reader has to live with, so the slug is cut to a length a listing can show rather than the title's own.

Variables

View Source
var Fields = []string{
	"id", "title", "mode", "area", "status", "filed-by", "filed-in", "date",
}

Fields are the eight front-matter keys D27 requires, in the order the format document states them. Each is required, and a ninth is a field nobody decided: Parse refuses both.

View Source
var FiledInKinds = []string{"run", "pr", "hand"}

FiledInKinds are the three things that can have filed an item. D27 names the two a run produces — `run:<run id>`, and the `pr:<number>` of the pull request the run opened — and `hand` is the third: an item a person files has no run and no recorded pull request behind it, and that state is said out loud rather than spelled as a run id nobody minted.

View Source
var Headings = []string{
	"## What was left open",
	"## Why",
	"## What done looks like",
	"## Links",
	ClosedHeading,
}

Headings are the body headings the format names: D27's four, and D31's `## Closed by`, which a `closed` item carries and no other item does. The first two are required, so an item cannot be a title with no text; a heading outside this set is refused, because the body's shape is the format and not a preference.

`## Closed by` is accepted here rather than required: what a `closed` item must carry is the closing sweep's rule (plan T4.7), and a parser that refused the heading would make the closing D31 describes unrepresentable. The order below is the order the format document lists them in.

View Source
var Statuses = []string{"open", "closed", "dropped"}

Statuses are D27's three words for `status`, and there is no fourth.

Functions

func AllocateID

func AllocateID(items []Item, date string) (string, error)

AllocateID returns the id an item filed on date (a `YYYYMMDD` day) takes: the lowest two-digit sequence that day not already used by items. It scans the folder's own state rather than a counter nobody owns, which is why D26 chose a date-plus-sequence name: two filers on two branches collide on one id at worst, and the collision is a rename.

func Body

func Body(what, why, done string, links []string) string

Body renders an item's body from the parts the format names. The first two headings are required — an item is not a title with no text (D27) — and the last two are written when the filer has them.

func Render

func Render(items []Item) string

Render returns the index the item files render to (D28): one row per item — the id linked to its file, the title, the mode, the area, the status, who filed it and the date — sorted by id, which is to say by date, plus a count line, with the closed and dropped rows grouped under the open ones.

It is a pure function of its argument. Every date in the document comes from an item, and it reads no clock, no network and no environment, so two renders of one folder are the same bytes and a regeneration in review diffs only where an item changed (D27). The items are sorted here rather than trusted to be sorted, so a caller cannot change the document by handing the same items over in another order.

func Slug

func Slug(title string) (string, error)

Slug is D26's slug: lower-kebab words from the title. A title with no word in it — punctuation, or a script this rule cannot pass through — is refused rather than filed under an empty name, because the slug is what a reader reads in a listing and an empty one is a file nobody can cite.

Types

type Item

type Item struct {
	ID      string // D26's id, and the file name's first two segments.
	Title   string // the filer's words, one line.
	Mode    string // the resolved mode a run ran under; "none" for a hand-filed item.
	Area    string // the matched areas, strictest first, comma-separated; "project" or "repo" for a whole layer.
	Status  string // one of Statuses.
	FiledBy string // the run's launcher, or the filer's login.
	FiledIn string // "run:<id>" or "pr:<number>".
	Date    string // the record's finish time in UTC, as YYYY-MM-DD.
	Path    string // the item's path, repository-relative.
	Body    string // everything after the front matter.
}

Item is one backlog item: its eight front-matter fields, the path it was read from and its body.

func Close added in v0.11.1

func Close(dir, id, closedBy string) (Item, error)

Close flips an open item to `closed`, appends D31's `## Closed by` section naming what closed it, and regenerates the index beside it (plan T4.7). id is D26's id — the file's own name, which is why a close is by id and not by path — and closedBy is the pull request that fixed the item, as `#<number>` or its URL: the section is what a reader follows from an item to the change that answered it, and a close naming none would be a status with nothing behind it.

The run that fixes an item is the one that closes it, so this is Write's sibling and not a command of its own (internal/cli's backlog command states the same thing): the index has to move in the same commit as the status, and the caller that can do that is the one holding the commit.

Every refusal happens before anything is written, so a close that cannot be made leaves the folder exactly as it was. The item's own words are left as they are: closing adds the section to somebody's account of what was left open rather than rewriting it.

func Load

func Load(dir string) ([]Item, error)

Load reads every item in the folder dir. It is LoadDir's body: the folder is a parameter there so a caller that is not standing at a repository root — `fugaro backlog list --dir`, and the folder's own guards over a synthetic tree — reads the same way.

func LoadDir

func LoadDir(root string) ([]Item, error)

LoadDir reads every item in root's backlog folder, sorted by id, and refuses a folder holding two items with one id (D26): the collision D26's naming rule leaves is a rename on the branch that would merge second, and this is where it surfaces rather than on main.

The index and the format document are not items. A .md file that is neither of them has to be an item — a stray document in the folder would otherwise be a file the index silently does not list, which is the one thing the folder's two guards exist to catch.

func Parse

func Parse(path string, data []byte) (Item, error)

Parse reads one item from data, whose path is the repository-relative path it was read from — the path is what the id is checked against, so an id resolves to a file without a lookup (D26).

Every rule the format states is a refusal here rather than a warning: a missing or unknown field, an empty value, a status outside the three words, a date that is not a day, an id that disagrees with the file name, a duplicate field and a body missing a required heading. An item the folder's pin accepts and this refuses would be one `fugaro backlog list` cannot show, which is why the two are checked against each other.

func Write

func Write(dir string, n New) (Item, error)

Write files one item in dir — the folder, not the repository root — and regenerates the index beside it. The folder is created when it is not there: a repository whose first item is filed by a run has no docs/backlog/ yet, and a filing command that refused to make it would make the first item of every repository a manual step.

Every rule the format states is checked before anything is written, so a filing that cannot become an item the folder accepts leaves the folder exactly as it was. What is written is parsed again — the file, not the struct — so a written item is by construction one Parse and the folder's pin both accept.

type New

type New struct {
	Title   string // the filer's words, one line
	Mode    string // the resolved mode a run ran under; "none" for a hand-filed item
	Area    string // the matched areas, strictest first, comma-separated; "project" or "repo" for a whole layer
	FiledBy string // the run's launcher, or the filer's login
	FiledIn string // "run:<run id>", "pr:<number>" or "hand"
	Date    string // the day it was filed, as YYYY-MM-DD
	Body    string // the body, its required headings included (Body renders one)
}

New is an item a caller is filing: D27's eight fields less the id, which the folder allocates, and less the status, which is always `open` — an item is filed open and closed by the pull request that fixes it (D31).

Jump to

Keyboard shortcuts

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