Documentation
¶
Overview ¶
Package lore keeps a change's intent, decisions, findings, verdicts and lessons as immutable markdown entries next to the code, and answers why a line is the way it is and what to know before editing a file. docs/lore.md is the spec.
The package imports nothing else from this module, so it can be copied out as a standalone tool: git goes through the Git interface, and the root is always a parameter.
Index ¶
- Constants
- Variables
- func Marshal(e Entry) ([]byte, error)
- func RenderLessons(w io.Writer, path string, lessons []Entry) error
- func RenderShow(w io.Writer, change string, entries []Entry) error
- func RenderWhy(w io.Writer, r *WhyResult) error
- func Validate(e Entry) error
- type Agent
- type Anchor
- type CLIGit
- func (g CLIGit) BlameLine(ctx context.Context, path string, line int) (string, error)
- func (g CLIGit) Head(ctx context.Context) (string, error)
- func (g CLIGit) IsAncestor(ctx context.Context, ancestor, commit string) (bool, error)
- func (g CLIGit) Trailers(ctx context.Context, commit string) (map[string][]string, error)
- func (g CLIGit) UserEmail(ctx context.Context) (string, error)
- type CheckResult
- type Entry
- type Git
- type Kind
- type Problem
- type Record
- type Rejected
- type Store
- func (s *Store) Add(ctx context.Context, e Entry) (string, error)
- func (s *Store) Changes() ([]string, error)
- func (s *Store) Check(change string) (*CheckResult, error)
- func (s *Store) Effective(change string) ([]Entry, error)
- func (s *Store) Lessons(path string) ([]Entry, error)
- func (s *Store) Load(change string) ([]Entry, error)
- func (s *Store) Validate(change string) ([]Problem, error)
- func (s *Store) Why(ctx context.Context, path string, line int) (*WhyResult, error)
- type ValidationError
- type WhyResult
Constants ¶
const ( TrailerChange = "Change" TrailerTask = "Task" )
The trailers that link a commit to its change; Task is f10's alias.
const ( ViaTrailer = "trailer" ViaAnchor = "anchor" )
How Why linked the blamed commit to a change.
Variables ¶
var ( Kinds = []Kind{KindIntent, KindDecision, KindFinding, KindVerdict, KindLesson} Severities = []string{"blocker", "major", "minor", "nit"} Statuses = []string{"open", "fixed", "wontfix", "duplicate"} // Outcomes are a judge's four verdicts, spelled as its verdict files spell them. Outcomes = []string{"proceed", "proceed with changes", "rethink", "stop"} Gates = []string{"judge", "review", "human"} // Sources are where a finding came from: a local review or the host's. Sources = []string{"local", "remote"} // Judged is what a verdict judged: the task itself, its plan, or the change. Judged = []string{"task", "plan", "change"} )
The vocabularies the enumerated fields accept.
var ErrBadFileName = errors.New("lore: not an entry file name")
ErrBadFileName is a file in a change directory not named `<ulid>-<kind>.md`.
var ErrNoRecord = errors.New("lore: no record")
ErrNoRecord is a valid empty answer: no entries, no trailer, a line not committed yet, no email configured. It is never a git failure.
Functions ¶
func RenderLessons ¶
RenderLessons writes the lessons for a file, or says there are none.
func RenderShow ¶
RenderShow writes a change's entries for a person: a header line per entry, its fields indented under it, then its body.
Types ¶
type Agent ¶
type Agent struct {
Model string `json:"model,omitempty" yaml:"model,omitempty"`
Via string `json:"via,omitempty" yaml:"via,omitempty"`
Session string `json:"session,omitempty" yaml:"session,omitempty"`
}
Agent is who did the work when it was not the human alone.
type Anchor ¶
type Anchor struct {
Path string `json:"path" yaml:"path"`
Line int `json:"line" yaml:"line"`
EndLine int `json:"end_line,omitempty" yaml:"end_line,omitempty"`
Commit string `json:"commit" yaml:"commit"`
Symbol string `json:"symbol,omitempty" yaml:"symbol,omitempty"`
}
Anchor is where an entry points in the code. EndLine 0 means the anchor covers Line alone.
func ParseAnchor ¶
ParseAnchor reads `path:line[-end][@commit][#symbol]`. The commit may be left out; Validate requires one by the time the entry is written.
type CLIGit ¶
type CLIGit struct {
Dir string
}
CLIGit answers through the git binary, run in Dir, the repository root.
func (CLIGit) BlameLine ¶
BlameLine asks blame for one line; git names an uncommitted line with an all-zero sha.
func (CLIGit) IsAncestor ¶
IsAncestor reads merge-base's exit code: 0 yes, 1 no; an unknown object is a no too, since an anchor may name a commit squashed away.
type CheckResult ¶
type CheckResult struct {
Change string `json:"change"`
Reasons []string `json:"reasons,omitempty"`
}
CheckResult is the gate's answer: it passes with no reasons.
func (*CheckResult) Pass ¶
func (c *CheckResult) Pass() bool
Pass reports whether nothing held the change back.
type Entry ¶
type Entry struct {
ID string `json:"id" yaml:"-"`
Kind Kind `json:"kind" yaml:"kind"`
Change string `json:"change" yaml:"change"`
Human string `json:"human" yaml:"human"`
Agent *Agent `json:"agent,omitempty" yaml:"agent,omitempty"`
At time.Time `json:"at" yaml:"at"`
Anchors []Anchor `json:"anchors,omitempty" yaml:"anchors,omitempty"`
Supersedes string `json:"supersedes,omitempty" yaml:"supersedes,omitempty"`
Chosen string `json:"chosen,omitempty" yaml:"chosen,omitempty"`
Rejected []Rejected `json:"rejected,omitempty" yaml:"rejected,omitempty"`
Severity string `json:"severity,omitempty" yaml:"severity,omitempty"`
Status string `json:"status,omitempty" yaml:"status,omitempty"`
Resolution string `json:"resolution,omitempty" yaml:"resolution,omitempty"`
Source string `json:"source,omitempty" yaml:"source,omitempty"`
Outcome string `json:"outcome,omitempty" yaml:"outcome,omitempty"`
Gate string `json:"gate,omitempty" yaml:"gate,omitempty"`
Commit string `json:"commit,omitempty" yaml:"commit,omitempty"`
Judged string `json:"judged,omitempty" yaml:"judged,omitempty"`
Retired bool `json:"retired,omitempty" yaml:"retired,omitempty"`
Body string `json:"body" yaml:"-"`
// contains filtered or unexported fields
}
Entry is one lore file. ID comes from the filename, never the frontmatter; the per-kind fields are flat, and Validate rejects one on another kind.
type Git ¶
type Git interface {
// BlameLine is the commit that last touched the line; ErrNoRecord when
// the line is not committed yet.
BlameLine(ctx context.Context, path string, line int) (string, error)
// Trailers is the commit message's trailers, keys as written except
// Change and Task, which are normalised so a lookup ignores case.
Trailers(ctx context.Context, commit string) (map[string][]string, error)
// UserEmail is `git config user.email`; ErrNoRecord when unset.
UserEmail(ctx context.Context) (string, error)
// Head is HEAD's abbreviated sha; ErrNoRecord before the first commit.
Head(ctx context.Context) (string, error)
// IsAncestor reports whether ancestor is reachable from commit; a sha
// this clone does not have is false, not an error.
IsAncestor(ctx context.Context, ancestor, commit string) (bool, error)
}
Git is all lore asks of git. Paths are relative to the repository root.
type Record ¶
type Record struct {
Change string `json:"change"`
Intent *Entry `json:"intent,omitempty"`
Verdict *Entry `json:"verdict,omitempty"`
Entries []Entry `json:"entries"` // decisions, findings and lessons on the line
}
Record is one change's part of a why answer.
type Rejected ¶
type Rejected struct {
Option string `json:"option" yaml:"option"`
Reason string `json:"reason" yaml:"reason"`
}
Rejected is an option a decision did not take, and why.
type Store ¶
type Store struct {
Root string
Git Git
// Now and Entropy default to the clock and ulid's monotonic source; a
// test sets them for stable ids.
Now func() time.Time
Entropy io.Reader
}
Store is one lore root: a directory per change, a file per entry.
func (*Store) Add ¶
Add validates e against the schema and the rest of its change, writes it, and returns its id. Human comes from git when unset, At from the clock. There is no update or delete: a file is created once and never rewritten.
func (*Store) Changes ¶
Changes lists the change directories under the root, sorted. No root is no changes.
func (*Store) Check ¶
func (s *Store) Check(change string) (*CheckResult, error)
Check fails a change with no intent, no verdict or a latest verdict that is not a proceed, or a blocker or major finding still open.
func (*Store) Effective ¶
Effective is the change's entries with every superseded one dropped, the intent first, then by time. A fork keeps its later head.
func (*Store) Lessons ¶
Lessons is every effective, non-retired lesson anchored to path, across every change, oldest first.
func (*Store) Load ¶
Load reads every entry of the change, by id, which is time order. A change with no directory is ErrNoRecord; a file that does not parse is an error naming it.
func (*Store) Validate ¶
Validate lints every entry of the change: the schema, the filename and directory against the frontmatter, and the supersedes rules.
func (*Store) Why ¶
Why answers for line of path, both repo-relative: blame the line, follow the commit's Change: trailers (else Task:) to their changes, and keep the entries anchored on the line. A commit with no trailer, or whose changes hold nothing, falls back to entries anchored at the blamed commit on the same path. Nothing found is ErrNoRecord.
type ValidationError ¶
type ValidationError struct {
Problems []string
}
ValidationError lists every problem found, not the first.
func (*ValidationError) Error ¶
func (v *ValidationError) Error() string