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
- Variables
- func IsReleaseTag(tag string) bool
- func Nudge(tag string) string
- type Answer
- type Answers
- type AuditOffer
- type AuditRollup
- type ChangelogSection
- type ExistsError
- type GapCounts
- type Lesson
- type MetricsBlock
- type NothingShippedError
- type RankedLesson
- type Ranking
- type Section
- type Seed
- type SeedIntent
- type TargetedIntent
- type ThinAnswer
- type ThinAnswersError
- type UnshippedError
- type WriteRequest
- type WriteResult
Constants ¶
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.
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).
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 ¶
var AllSections = []Section{WentWell, CouldImprove, Lessons, Decisions, Metrics}
AllSections are the five sections the retrospective carries, in order.
var AskedSections = []Section{WentWell, CouldImprove, Lessons, Decisions}
AskedSections are the sections the interview asks, in order.
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).
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.
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).
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 ¶
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.
Types ¶
type Answer ¶
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 ¶
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.
type AuditOffer ¶
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 ¶
Lesson is one lesson a retrospective carries, with the release it came from.
func ReadLessons ¶
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 ¶
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 ¶
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.
func (Section) FollowUp ¶
FollowUp is the one clarifying question a thin answer is met with (spec scope 3).
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 ¶
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 ¶
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 ¶
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 ¶
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.