reflect

package
v0.13.3 Latest Latest
Warning

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

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

Documentation

Overview

Package reflect is the core of the release retrospective (itd-24, spc-2609211751376504): the seed a retrospective interview opens from, the thin-answer floor the interview holds its answers to, the writer that turns the answers into `.abcd/development/retrospectives/<release-tag>/README.md`, the lessons reader and ranking a later voyage's embark shows, and the one-line nudge a release cut prints.

The interview itself is host-run: a front door renders the Seed, asks the four asked sections one question at a time, and hands the answers to Write. The metrics section is computed from the seed, never asked. Nothing here writes to stdout or knows a transport.

The unit is the release (adr-2609212115255771; itd-24 decisions 4 and 5): a retrospective starts from the intents a tag shipped, with the audit notes the intent auditor wrote on each and the changelog section the cut composed.

Index

Constants

View Source
const (
	MinClauses     = 2
	MinClauseWords = 3
)

The declared floor (spec scope 3). An answer is thin when it is blank, when it only restates its section heading, or when it holds fewer than MinClauses substantive clauses. A clause is substantive when it carries at least MinClauseWords words. The criterion's own example, "it worked", is one clause of two words, so it is thin twice over.

The floor is a heuristic and a cheap one to be wrong about: a thin answer costs one follow-up question, never a refusal of the answer the follow-up brings.

View Source
const RetrospectivesRelDir = ".abcd/development/retrospectives"

RetrospectivesRelDir is the retrospective store, relative to the repository root: the durable record tier, a peer of the intent store (itd-24 decision 3).

View Source
const TopLessons = 3

TopLessons is how many predecessor lessons embark shows ranked; the rest are a list opened on request (itd-24 decision 2, spec scope 7).

Variables

AllSections are the five sections the retrospective carries, in order.

AskedSections are the sections the interview asks, in order.

View Source
var ErrExists = errors.New("retrospective already written")

ErrExists is the refusal for a tag that already has a retrospective: a retrospective is written once and not edited after (the spec's out-of-scope list).

View Source
var ErrNothingShipped = errors.New("nothing shipped to reflect on")

ErrNothingShipped is the refusal for a tag whose release shipped no intent. NothingShippedError wraps it, so a front door can test for it with errors.Is.

View Source
var ErrThinAnswers = errors.New("an answer is under the floor and has no follow-up")

ErrThinAnswers is the refusal Write returns when an answer falls under the floor and carries no follow-up (criterion 4).

View Source
var ErrUnshippedTargets = errors.New("intents targeted at this release are still unshipped")

ErrUnshippedTargets is the refusal Write returns when intents targeted at the release are still unshipped and the person has not said to proceed anyway (criterion 7).

Functions

func IsReleaseTag

func IsReleaseTag(tag string) bool

IsReleaseTag reports whether tag has the shape a retrospective's directory takes: a leading v and a strict MAJOR.MINOR.PATCH core, no prerelease and no build suffix. The lifeboat's packer and embarker hold the store's directory names to it, so the name a hostile lifeboat carries never reaches a path in any other shape.

func Nudge

func Nudge(tag string) string

Nudge is the one line a release cut prints when it is written (criterion 8, itd-24 decision 1): a retrospective for the release is owed, and the command that writes it. It is said once, at the cut, and gates nothing; the retrospective's absence is never announced again.

Types

type Answer

type Answer struct {
	Text     string `json:"answer"`
	FollowUp string `json:"follow_up,omitempty"`
}

Answer is the person's answer to one asked section, and the answer to its follow-up question when the first was thin.

type Answers

type Answers struct {
	WentWell     Answer `json:"went_well"`
	CouldImprove Answer `json:"could_improve"`
	Lessons      Answer `json:"lessons"`
	Decisions    Answer `json:"decisions"`
}

Answers are the four asked sections' answers, as a front door hands them to Write. The metrics section is computed, so it has no answer.

func ParseAnswers

func ParseAnswers(data []byte) (Answers, error)

ParseAnswers reads an answers document strictly: an unknown key or a repeated one is refused, because a mistyped section name would otherwise drop an answer without a word.

func (Answers) Of

func (a Answers) Of(s Section) Answer

Of returns the answer to an asked section.

type AuditOffer

type AuditOffer struct {
	IntentID string `json:"intent_id"`
	Command  string `json:"command"`
}

AuditOffer names a shipped intent without audit notes and the command that audits it (criterion 2). It is an offer, never a gate.

type AuditRollup

type AuditRollup struct {
	Met             int `json:"met"`
	MetWithConcerns int `json:"met_with_concerns"`
	NotMet          int `json:"not_met"`
	Inconclusive    int `json:"inconclusive"`
}

AuditRollup is the per-criterion verdict count an ingested audit writes on its `Acceptance rollup:` line.

type ChangelogSection

type ChangelogSection struct {
	Found   bool   `json:"found"`
	Heading string `json:"heading,omitempty"`
	// Anchor is the fragment a link to the heading takes on a rendered page.
	Anchor string `json:"anchor,omitempty"`
	Body   string `json:"body,omitempty"`
}

ChangelogSection is the section the cut composed for the tag. Found is false when CHANGELOG.md carries no dated heading for the release; the seed goes ahead without it.

type ExistsError

type ExistsError struct{ Tag, Path string }

ExistsError names the retrospective that already exists.

func (*ExistsError) Error

func (e *ExistsError) Error() string

func (*ExistsError) Unwrap

func (e *ExistsError) Unwrap() error

type GapCounts

type GapCounts struct {
	Honoured int `json:"honoured"`
	Diverged int `json:"diverged"`
	Missing  int `json:"missing"`
}

GapCounts is the honoured / diverged / missing count of an audit's gap audit.

type Lesson

type Lesson struct {
	Release string `json:"release"`
	Text    string `json:"text"`
}

Lesson is one lesson a retrospective carries, with the release it came from.

func ReadLessons

func ReadLessons(readme []byte) []Lesson

ReadLessons reads the lessons out of a retrospective: one per top-level bullet of its lessons section, or one per paragraph when the section has no bullets. Fenced and commented lines are not lessons.

type MetricsBlock

type MetricsBlock struct {
	IntentsShipped  int         `json:"intents_shipped"`
	Audited         int         `json:"audited"`
	Unaudited       int         `json:"unaudited"`
	Rollup          AuditRollup `json:"rollup"`
	Gaps            GapCounts   `json:"gaps"`
	TagDate         string      `json:"tag_date"`
	PreviousTag     string      `json:"previous_tag,omitempty"`
	PreviousTagDate string      `json:"previous_tag_date,omitempty"`
}

MetricsBlock is the metrics section, computed from the seed (spec scope 2): the intents shipped, the audit notes' verdict distribution, and the dates of this tag and the previous one.

type NothingShippedError

type NothingShippedError struct{ Tag string }

NothingShippedError refuses a release that shipped no intent (criterion 3). Its text is the criterion's own wording.

func (*NothingShippedError) Error

func (e *NothingShippedError) Error() string

func (*NothingShippedError) Unwrap

func (e *NothingShippedError) Unwrap() error

type RankedLesson

type RankedLesson struct {
	Lesson
	Score  float64  `json:"score"`
	Shared []string `json:"shared"`
}

RankedLesson is a lesson with its score against the brief and the terms the two share, rarest first.

type Ranking

type Ranking struct {
	Heuristic string         `json:"heuristic"`
	Top       []RankedLesson `json:"top"`
	Rest      []RankedLesson `json:"rest"`
}

Ranking is the embark view of predecessor lessons: the few most like the new voyage's brief, the rest as a list, and the method named as a heuristic.

func RankLessons

func RankLessons(framing string, lessons []Lesson) Ranking

RankLessons ranks lessons against framing, the new voyage's brief framing chapter, with the canonical term-overlap primitive (record/match): a lesson's score is the weighted share of its terms the framing holds, the weights taken across the lessons and the framing together. The best TopLessons come first, the rest follow best first; ties keep the order the lessons were given in.

type Section

type Section string

Section names one of the retrospective's five sections, in the order the retrospective carries them (spec scope 2). The first four are asked; metrics is computed from the seed and never asked.

const (
	WentWell     Section = "went_well"
	CouldImprove Section = "could_improve"
	Lessons      Section = "lessons"
	Decisions    Section = "decisions"
	Metrics      Section = "metrics"
)

func (Section) FollowUp

func (s Section) FollowUp() string

FollowUp is the one clarifying question a thin answer is met with (spec scope 3).

func (Section) Heading

func (s Section) Heading() string

Heading is the section's heading in the written retrospective.

func (Section) Question

func (s Section) Question() string

Question is the opening question the interview asks for an asked section.

type Seed

type Seed struct {
	Tag       string           `json:"tag"`
	Intents   []SeedIntent     `json:"intents"`
	Unaudited []AuditOffer     `json:"unaudited"`
	Unshipped []TargetedIntent `json:"unshipped_targets"`
	Changelog ChangelogSection `json:"changelog"`
	Metrics   MetricsBlock     `json:"metrics"`
	// Output is the repo-relative path the retrospective is written to.
	Output string `json:"output"`
}

Seed is what a retrospective interview opens from (spec scope 1).

func BuildSeed

func BuildSeed(root, tag string) (Seed, error)

BuildSeed reads what the release tag shipped. It refuses a malformed tag, a tag the repository does not hold, a release that already has a retrospective, and a release that shipped no intent (NothingShippedError). It writes nothing.

Which intents a tag shipped is read the way the release cut reads it (changelog.ShippedSince): the intents that reached shipped/ between the previous release tag and this one, less any that say `shipped_in:` another release, plus any in shipped/ now that say `shipped_in:` this one. The stamp alone is not enough: it is a migration field the cut never writes, so a release cut the ordinary way stamps none of its intents.

type SeedIntent

type SeedIntent struct {
	ID    string `json:"id"`
	Title string `json:"title"`
	// Path is repo-relative and names where the record lives now, so a link
	// resolves; a record the intent store no longer holds is read from, and
	// named by, the tag's tree.
	Path   string `json:"path"`
	Impact string `json:"impact"`
	// Audited is true when the record carries audit notes: an ingested review
	// (its `Acceptance rollup:` line) or a hand-written audit. A placeholder, an
	// owed review and an absent section are not audit notes.
	Audited bool        `json:"audited"`
	Receipt string      `json:"receipt,omitempty"`
	Rollup  AuditRollup `json:"rollup"`
	Gaps    GapCounts   `json:"gaps"`
}

SeedIntent is one intent the release shipped, as the interview opens from it: what it is, where it lives now, the impact it declared, and what its audit notes say, as counts. The notes themselves stay on the intent; the retrospective links to them.

type TargetedIntent

type TargetedIntent struct {
	ID   string `json:"id"`
	Path string `json:"path"`
}

TargetedIntent is a planned intent whose target_release names the release and which has not shipped (criterion 7).

type ThinAnswer

type ThinAnswer struct {
	Section  Section `json:"section"`
	Heading  string  `json:"heading"`
	Reason   string  `json:"reason"`
	Question string  `json:"question"`
}

ThinAnswer is one section whose answer is under the floor.

type ThinAnswersError

type ThinAnswersError struct{ Thin []ThinAnswer }

ThinAnswersError lists every thin answer, each with the follow-up question to ask before anything is written.

func (*ThinAnswersError) Error

func (e *ThinAnswersError) Error() string

func (*ThinAnswersError) Unwrap

func (e *ThinAnswersError) Unwrap() error

type UnshippedError

type UnshippedError struct {
	Tag     string
	Intents []TargetedIntent
}

UnshippedError lists the intents whose target_release names the release and which have not shipped.

func (*UnshippedError) Error

func (e *UnshippedError) Error() string

func (*UnshippedError) Unwrap

func (e *UnshippedError) Unwrap() error

type WriteRequest

type WriteRequest struct {
	Tag                     string
	Answers                 Answers
	ProceedDespiteUnshipped bool
	Now                     time.Time
}

WriteRequest is one retrospective write. ProceedDespiteUnshipped is the person's confirmation, asked for when intents targeted at the release are still unshipped (criterion 7). Now dates the retrospective.

type WriteResult

type WriteResult struct {
	Path string `json:"path"`
	Seed Seed   `json:"seed"`
}

WriteResult names the file written and the seed it was written from.

func Write

func Write(root string, req WriteRequest) (WriteResult, error)

Write writes the retrospective for req.Tag. It rebuilds the seed, so every refusal BuildSeed makes holds at the write too, then refuses, writing nothing: while unshipped targets are unconfirmed (UnshippedError), and while any asked answer is under the floor with its follow-up unanswered, or is blank after it (ThinAnswersError). Missing audit notes are not a refusal (criterion 2): the seed names them and the write goes ahead.

The file is created exclusively inside the retrospectives tree, every level of which must be a real directory, so neither a second run nor a symlinked store can overwrite or escape, and under the intent store's lock, so an embark writing the same path is serialised with it.

Jump to

Keyboard shortcuts

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