adr

package
v0.18.0 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package adr parses ADR files under docs/decisions, renders the ACTIVE.md index, and scaffolds new ADR files from the rendered template (awf new adr). Generated by awf sync (regenerates docs/decisions/ACTIVE.md).

Index

Constants

This section is empty.

Variables

View Source
var FilenameRe = regexp.MustCompile(`^(\d{4})-.+\.md$`)

FilenameRe matches an ADR filename (NNNN-slug.md); group 1 is the 4-digit number.

Functions

func NewFile added in v0.6.0

func NewFile(dir, title string) (string, error)

NewFile scaffolds a new ADR under dir: the next sequential number, the rendered template.md with every marker comment stripped and its date and title heading filled in, named NNNN-slug.md. Refuses to overwrite an existing file at that path. touches-invariant: adr-new-strips-markers - NewFile strips every marker comment from the copied template; proof in adr_test.go touches-invariant: adr-new-heading-matches-file - NewFile fills the heading from the allocated file number; proof in adr_test.go touches-invariant: adr-new-no-overwrite - refuse-overwrite guard; unbacked (unreachable), see ADR-0042 Verify note

func NextNumber added in v0.6.0

func NextNumber(dir string) (string, error)

NextNumber returns the next available 4-digit ADR number for dir: one more than the highest number ParseDir finds, or "0001" for an ADR-less dir. touches-invariant: adr-new-sequential-numbering - NextNumber returns highest-plus-one; proof in adr_test.go

func RenderActiveMD

func RenderActiveMD(corpus Corpus) string

RenderActiveMD renders the ACTIVE.md index for corpus, grouped by status. It returns a placeholder index when the corpus holds no ADRs (ADR-0020). The content carries no generated-by banner - like RenderDomainIndex, that is the caller's job (internal/project's generateActiveMD, via injectBanner) so every rendered artifact's banner comes from the one canonical source.

func RenderDomainIndex

func RenderDomainIndex(corpus Corpus, domain string) string

RenderDomainIndex renders the per-domain ADR index for corpus: every ADR whose domains frontmatter includes domain, grouped by status in the same order as ACTIVE.md, with links relative to docs/domains/ (one dir over) and each superseded entry annotated with its successor. Returns a placeholder line when no ADR matches, so the rendered section is never empty.

Types

type ADR

type ADR struct {
	Number        string            // e.g. "0001"
	Title         string            // e.g. "ADR-0001: Template Overlay Rendering Engine"
	Status        string            // e.g. "Accepted"
	Date          string            // frontmatter date, retained verbatim as YYYY-MM-DD text
	Filename      string            // e.g. "0001-template-overlay-rendering-engine.md"
	Path          string            // path as globbed
	Domains       []string          // `domains:` frontmatter (ADR-0014)
	Tags          []string          // `tags:` frontmatter (keyword labels)
	Related       []int             // `related:` frontmatter (ADR numbers)
	Refs          []SupersessionRef // inline partial-supersession tokens in the Decision section (ADR-0120)
	Sections      map[string]string // `## ` heading -> non-fenced section body
	DecisionStart int               // raw file byte offset of the Decision heading; 0 when absent
	DecisionEnd   int               // raw file byte offset immediately after the Decision section; 0 when absent
}

ADR is a parsed ADR record.

func ParseBytes added in v0.18.0

func ParseBytes(name string, data []byte) (ADR, bool, error)

ParseBytes parses one ADR from bytes: status and the other frontmatter fields, plus the title from the first `# ` heading. It is the seam the git-blob consumers take (ADR-0130 item 5): internal/audit reads history rather than the working tree, so it cannot take a Corpus, but it can share the parser and the frontmatter schema, which is where the duplication actually was.

found reports whether frontmatter was present at all, which is the tri-state the audit needs: absent frontmatter is a legitimate empty status, while present-but-unparseable is an error. name is the ADR's base filename, from which Filename and Number are derived; Path is left empty, since a blob-sourced record has no working-tree path.

func ParseDir

func ParseDir(dir string) ([]ADR, error)

ParseDir scans dir for ADR files (NNNN-*.md) and parses each into an ADR.

func (ADR) Bucket added in v0.18.0

func (a ADR) Bucket() string

Bucket is the ACTIVE.md section an ADR belongs to. Every superseded ADR folds into one group regardless of the successor its status names.

func (ADR) DecisionItems added in v0.18.0

func (a ADR) DecisionItems() []int

DecisionItems returns the numbers of the column-0 numbered items of the Decision section, in order of appearance.

func (ADR) DeclaredSlugs added in v0.18.0

func (a ADR) DeclaredSlugs() []string

DeclaredSlugs returns the invariant slugs a's Invariants section declares, backed and unbacked alike, in declaration order.

func (ADR) HasSameStatus added in v0.18.0

func (a ADR) HasSameStatus(other ADR) bool

HasSameStatus reports exact status equality without exporting literal comparisons to migration consumers.

func (ADR) HasStatus added in v0.18.0

func (a ADR) HasStatus() bool

HasStatus reports whether the record carries a frontmatter status at all. The audit distinguishes an ADR with no status from one with a real status, and that tri-state is what the bytes seam carries (ADR-0130 item 3).

func (ADR) InvariantDecls added in v0.18.0

func (a ADR) InvariantDecls() []InvariantDecl

InvariantDecls returns the declarations a's Invariants section carries, in declaration order. Status-independent: the ref-validity check and the retirement migration resolve slug anchors against any ADR's declarations, not just Implemented ones (ADR-0120 item 2).

func (ADR) IsImplemented added in v0.18.0

func (a ADR) IsImplemented() bool

IsImplemented reports whether the ADR's decisions have shipped. Invariant backing and token retirement are both gated on this.

func (ADR) IsInflight added in v0.18.0

func (a ADR) IsInflight() bool

IsInflight reports a legacy decision that must be resolved before bridge attestation.

func (ADR) IsLegacyShipped added in v0.18.0

func (a ADR) IsLegacyShipped() bool

IsLegacyShipped reports whether a legacy decision shipped, including the historical Superseded state. Migration inventory uses this broader predicate; normal legacy authority continues to use its existing predicates.

func (ADR) IsLive added in v0.18.0

func (a ADR) IsLive() bool

IsLive reports whether the ADR's decisions are current guidance.

func (ADR) IsProposed added in v0.18.0

func (a ADR) IsProposed() bool

IsProposed reports whether the ADR's body is still mutable.

func (ADR) IsSuperseded added in v0.18.0

func (a ADR) IsSuperseded() bool

IsSuperseded reports whether the ADR has been retired. The prefix test tolerates the pre-generation-12 suffixed form as well as the bare status ADR-0128 item 4 moves to.

type Anchor added in v0.18.0

type Anchor struct {
	ADR  string // 4-digit target ADR number
	Item int    // Decision item number; 0 for a slug anchor
	Slug string // invariant slug; "" for an item anchor
}

Anchor is one addressable decision unit: a Decision item or a declared invariant slug on a specific ADR. Anchors are the nodes of ADR-0129's model; claims are the edges. Exactly one of Item/Slug is set.

func (Anchor) Describe added in v0.18.0

func (a Anchor) Describe() string

Describe renders the anchor's local part for a human line: "item N" or "slug `<slug>`".

Named Describe rather than Label because ADR-0129's supersession-model-single-source requires the identifiers SupersessionIndex, Override, and Label to appear nowhere in the tree - a greppable check that the retired render index is really gone. A new method reusing that name would defeat the grep while looking innocent.

func (Anchor) String added in v0.18.0

func (a Anchor) String() string

String renders the anchor in the token grammar's own shape, ADR-NNNN#anchor.

type Chain added in v0.18.0

type Chain struct {
	Predecessor string
	Successors  []string
}

Chain is one supersedence relationship for the ACTIVE.md index: a covered predecessor and every ADR that retired one of its anchors. The shape is one-to-many (ADR-0129 item 6) because coverage may split across successors, which is exactly why the scalar `superseded_by:` field could not express it.

type Citation added in v0.18.0

type Citation struct {
	Carrier     string // citing ADR's number
	CarrierItem int    // the citing ADR's own Decision item; 0 before the first
	Anchor      Anchor // the cited anchor
	HasVerb     bool   // an override verb occurs in the same Decision item
}

Citation is one citation of another ADR's anchor inside a Decision item (ADR-0131 item 2). HasVerb records whether an override verb occurs in the same Decision item, which is what separates a mention from a claim the record is expected to encode.

Extraction lives here rather than in internal/project because ADR-0130's corpus-owns-field-reads confines Sections reads to this package: consumers take parsed citations and never touch raw section text.

type Claim added in v0.18.0

type Claim struct {
	Anchor      Anchor
	Carrier     string   // claiming ADR number
	CarrierItem int      // the claiming ADR's Decision item that carries the token
	Relation    Relation // retirement or refinement
}

Claim is one edge: an ADR claiming an anchor, carrying the relation and the claiming ADR's own Decision item, so the rationale site is addressable (ADR-0129 item 2).

func (Claim) Verb added in v0.18.0

func (c Claim) Verb() string

Verb renders the claim's relation for a human line: a retired anchor reads "superseded by", an adapted one "refined by".

type Corpus added in v0.18.0

type Corpus struct {
	// contains filtered or unexported fields
}

Corpus is the parsed decisions directory: one parse, threaded to every consumer that needs an ADR fact (ADR-0130 item 1). It answers questions rather than exposing fields for a caller to re-derive an answer from (item 2), which is what collapsed the three-way "is live" and the twice-built supersession relation into one place.

The zero value is not useful; construct with NewCorpus.

func LoadCorpus added in v0.18.0

func LoadCorpus(dir string) (Corpus, error)

LoadCorpus parses a decisions directory into the view. It is the single construction seam: adr.ParseDir has no production caller outside this package, so every consumer - the *Project that threads the view to the checks, and the schema migrations, which run before a Project can be opened and so cannot be handed one - enters through here.

func NewCorpus added in v0.18.0

func NewCorpus(adrs []ADR) Corpus

NewCorpus builds the view over an already-parsed slice. Construction is the single seam where derived structure is built, so nothing downstream rebuilds it (corpus-model-not-rebuilt).

func (Corpus) All added in v0.18.0

func (c Corpus) All() []ADR

All returns every parsed ADR in directory order.

func (Corpus) Anchors added in v0.18.0

func (c Corpus) Anchors(num string) []Anchor

Anchors returns the named ADR's anchors: its Decision items then its declared invariant slugs.

func (Corpus) AnnotatedAnchors added in v0.18.0

func (c Corpus) AnnotatedAnchors() []Claim

AnnotatedAnchors returns the claimed anchors of ADRs that are still live - the "superseded anchors on live ADRs" view. A covered ADR is excluded: its whole record is retired, so per-anchor annotation would be noise, and the chains subsection already names its retirers.

func (Corpus) ByNumber added in v0.18.0

func (c Corpus) ByNumber(num string) (ADR, bool)

ByNumber returns the ADR with the given four-digit number. The ADR number is the sole identity key (ADR-0130 item 4).

func (Corpus) Chains added in v0.18.0

func (c Corpus) Chains() []Chain

func (Corpus) Citations added in v0.18.0

func (c Corpus) Citations(num string) []Citation

Citations returns every anchor citation in the named ADR's Decision section, in no guaranteed order beyond shape grouping. An absent ADR, or one with no Decision section, cites nothing.

func (Corpus) ClaimsOn added in v0.18.0

func (c Corpus) ClaimsOn(num string) []Claim

ClaimsOn returns the claims made on the named ADR's anchors, ordered by anchor then carrier. This is the "who claims this" question ACTIVE.md's chains and the domain index both ask.

func (Corpus) DecisionItems added in v0.18.0

func (c Corpus) DecisionItems(num string) []int

DecisionItems returns the Decision item numbers the named ADR enumerates. An absent ADR yields no items rather than an error: every caller is already validating existence separately, and a token into a missing target is that check's finding to report, not this one's.

func (Corpus) DeclaredSlugs added in v0.18.0

func (c Corpus) DeclaredSlugs(num string) []string

DeclaredSlugs returns the invariant slugs the named ADR declares, backed and unbacked alike, in declaration order.

func (Corpus) GraphFaults added in v0.18.0

func (c Corpus) GraphFaults() ([]SelfClaim, []Cycle)

GraphFaults returns the irreflexivity and acyclicity violations in the retirement relation (ADR-0129 item 7). Nothing previously forbade either: the single-claimant check that used to stand in for this died with the frontmatter encoding, and a self-targeting token or an A-to-B-to-A cycle would otherwise derive a coherent-looking state from a contradiction.

Acyclicity is scoped to ADRs the model classifies as Covered. A cycle among live ADRs is not yet a contradiction: partial claims in both directions are legitimate, since two ADRs may each refine or retire some of the other's anchors while both remain current. Only when the cycle's members are all fully retired does it assert that each is dead because the other is.

func (Corpus) Has added in v0.18.0

func (c Corpus) Has(num string) bool

Has reports whether the corpus contains an ADR with the given number.

func (Corpus) Raw added in v0.18.0

func (c Corpus) Raw(num string) ([]byte, error)

Raw returns the ADR file's bytes. Raw access is enumerated and closed (ADR-0130 item 6): the migration's offset surgery and the retired-key frontmatter scan are the only two legitimate consumers below the semantic layer. A third caller means the view is missing a question.

func (Corpus) RefsOf added in v0.18.0

func (c Corpus) RefsOf(num string) []SupersessionRef

RefsOf returns the supersession tokens the named ADR carries, in document order. This is the "what does this ADR claim" question consumers previously answered by ranging over ADR.Refs themselves (corpus-owns-field-reads).

The mirror question - "who claims this anchor" - is not here yet: nothing asks it until the coverage model needs it, and the dead-code gate refuses a production method no main can reach.

func (Corpus) Retirers added in v0.18.0

func (c Corpus) Retirers(num string) []string

Retirers returns the numbers of the Implemented ADRs that retired at least one of the named ADR's anchors, sorted and deduplicated. This is what renders in place of the scalar successor the frontmatter used to carry: coverage may split across several carriers, so the answer is a set, not a name.

func (Corpus) State added in v0.18.0

func (c Corpus) State(num string) State

State returns the ADR's derived supersession state. An unknown number is Live: it has no anchors anyone could have retired.

func (Corpus) UncoveredAnchors added in v0.18.0

func (c Corpus) UncoveredAnchors(num string) []Anchor

UncoveredAnchors returns the named ADR's anchors that carry no counting retirement, in anchor order. The coverage-versus-status check names these when a `Superseded` ADR is not in fact fully covered, so the author is told exactly which decision still needs a successor rather than merely that the status is wrong.

type Cycle added in v0.18.0

type Cycle []string

Cycle is one retirement cycle among covered ADRs, as the sequence of ADR numbers that closes back on its first element.

type InvariantDecl added in v0.18.0

type InvariantDecl struct {
	Slug     string
	Unbacked bool
	Bullet   string
}

InvariantDecl is one invariant declaration in an ADR's Invariants section. The grammar lives here rather than in internal/invariants because ADR-0130 item 2 makes declared slugs a question the corpus view answers, and corpus-owns-field-reads forbids any other package reading ADR.Sections to re-derive it. Bullet carries the whole declaration - lead line plus wrapped continuation lines - so a consumer can scan it for the `Verify:` note without a second pass over the section.

type Relation added in v0.18.0

type Relation string

Relation distinguishes the three claims an ADR can record on an anchor. A retirement replaces the anchor and counts toward the target's coverage; a refinement adapts it and counts toward nothing, so an ADR whose items have only ever been refined is still live; a citation claims nothing at all and exists only to mark the reference informational (ADR-0128 item 2 for the first two, ADR-0131 item 4 for the third).

The split is corpus-driven: of the 37 pre-existing item tokens, 22 were refinements and only 13 genuine retirements. ADR-0034 item 1 is the live precedent - refined by ADR-0057, and only actually retired by ADR-0121 years later. Slug anchors have no refinement form: a slug is atomic, so `supersedes-invariant:` is always a retirement.

const (
	// Retires is `supersedes:` (items) and `supersedes-invariant:` (slugs).
	Retires Relation = "retires"
	// Refines is `refines:`, items only.
	Refines Relation = "refines"
	// Cites is `cites:`: an informational citation asserting no claim on the
	// anchor. It contributes to nothing and exists only to suppress the
	// citation check (ADR-0131 item 4).
	Cites Relation = "cites"
)

type SelfClaim added in v0.18.0

type SelfClaim struct {
	ADR    string
	Anchor Anchor
}

SelfClaim is an ADR whose own Decision section claims one of its own anchors.

type State added in v0.18.0

type State string

State is an ADR's derived supersession state (ADR-0129 item 3). It is computed from anchor coverage, never stored: the frontmatter keys that used to assert it are gone, and `status: Superseded` is checked against this rather than believed.

const (
	// StateLive is an ADR with no retired anchors.
	StateLive State = "Live"
	// StatePartial is the residual: some anchors retired, not all.
	StatePartial State = "PartiallySuperseded"
	// StateCovered is an ADR every one of whose anchors has been retired.
	StateCovered State = "Covered"
)

type SupersessionRef added in v0.18.0

type SupersessionRef struct {
	Target string // 4-digit target ADR number, e.g. "0116"
	Item   int    // Decision item number; 0 for an invariant ref
	Slug   string // invariant slug; "" for an item ref
	// Relation is whether the claim retires the anchor or adapts it.
	Relation Relation
	// CarrierItem is the carrying ADR's own Decision item number - the item
	// whose prose justifies the claim. ADR-0129 item 2 makes the rationale site
	// addressable, which is the whole reason the token sits inside a Decision
	// item rather than in frontmatter. 0 when the token precedes the first item.
	CarrierItem int
}

SupersessionRef is one inline partial-supersession token (ADR-0120): `supersedes: ADR-NNNN#<item>` or `supersedes-invariant: ADR-NNNN#<slug>` as an inline code token inside a Decision section. Exactly one of Item/Slug is set; the key names the kind, never the anchor's shape.

Jump to

Keyboard shortcuts

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