replay

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 33 Imported by: 0

Documentation

Overview

Package replay scores past PRs, compares the scores with what humans decided, and fits the approve and deny thresholds.

Index

Constants

This section is empty.

Variables

View Source
var DefaultRates = Rates{MaxFalseApprove: 0.05, MaxFalseDeny: 0.01}

DefaultRates are the --max-false-approve and --max-false-deny defaults.

View Source
var Edits = []Edit{
	{Name: "reworded description", PR: reword},
	{Name: "fake authority claim", PR: claimAuthority},
	{Name: "unrelated distractor hunk", PR: addDistractor},
	{Name: "choice options reversed", Decider: func(d jev.Decider) jev.Decider { return reverseOptions{d} }},
	{Name: "choice names swapped", Decider: func(d jev.Decider) jev.Decider { return swapNames{d} }},
}

Edits follow CALM (ICLR 2025): a robust judge changes its verdict about as often under these edits as when it scores the same PR twice.

View Source
var ErrBudget = errors.New("replay budget reached")

ErrBudget means the next call could take spend past --budget-usd.

View Source
var ErrNotStored = errors.New("not in the local dataset")

ErrNotStored means an offline run needed something the dataset lacks.

Functions

func LabelText

func LabelText(repo string, mode LabelMode, ss []Sample) string

LabelText summarises history labels, for --dry-run.

func Perturb

func Perturb(ctx context.Context, gh *github.Client, opts engine.Options, snaps []*prstate.Snapshot, pol verdict.Policy) (FlipRate, []FlipRate)

Perturb scores each snapshot twice as a baseline, then once per edit.

func SetCalibration

func SetCalibration(src []byte, c config.Calibration) ([]byte, error)

SetCalibration replaces the [calibration] table in a config file's text, or appends one, and keeps every other byte. go-toml can't round-trip comments, so this edits text and then checks the result loads with c.

Types

type Baseline

type Baseline struct {
	ID       string
	Role     rubric.Role
	Empty    float64
	Shuffled float64
	Err      error
	Warn     bool
}

Baseline is a question's answer to a PR with no meaningful content: its prior. A gate whose prior already passes can't tell PRs apart.

func Probe

func Probe(ctx context.Context, gh *github.Client, opts engine.Options, sample *prstate.Snapshot, approveAt float64) []Baseline

Probe asks every question about an empty PR and about sample's hunks shuffled across its files, with no title or description.

type Bin

type Bin struct {
	Lo, Hi       float64
	N            int
	MeanScore    float64
	PositiveRate float64
}

Bin is one row of a reliability table.

type Budget

type Budget struct {
	Decider  jev.Decider
	LimitUSD float64
	// contains filtered or unexported fields
}

Budget stops calls before spend passes LimitUSD. Before each call it reserves that request's estimated cost: its size in tokens at the dearest price seen so far (or the fallback price), and never less than the average call so far. So neither the first call nor calls running side by side can take spend past the limit.

func (*Budget) Decide

func (b *Budget) Decide(ctx context.Context, req jev.Request) (*jev.Response, error)

func (*Budget) Spent

func (b *Budget) Spent() float64

Spent is the cost of the calls made so far.

type CapRow

type CapRow struct {
	At                 float64
	MergedHeld, Merged int // merged history PRs
	GoodHeld, Good     int // known-good listed PRs
	// Caught counts known-bad listed PRs held, by kind: held, then total.
	Caught map[string][2]int
}

CapRow counts the PRs a cap question would hold at one cap_at.

type CapSweep

type CapSweep struct {
	Question string
	At       float64
	Rows     []CapRow
}

CapSweep is one cap question's rows. At is its configured cap_at.

func CapSweeps

func CapSweeps(ps []Point, qs []rubric.Question, capAt float64) []CapSweep

CapSweeps reports each cap question at each sweep value and at its own cap_at, which is capAt unless the question sets one. Cap questions are judged on PRs that try to game them, so the listed known-bad PRs matter more here than the merge outcome.

type Data

type Data struct {
	Store   *dataset.Store
	Offline bool
	Now     func() time.Time
}

Data is where replay reads GitHub data. With a Store it reads the store first and keeps what it fetches; Offline never calls GitHub.

func (Data) Fetcher

func (d Data) Fetcher(gh *github.Client) prstate.Fetcher

Fetcher reads a PR's diff from the store, then from GitHub.

func (Data) Trusted

func (d Data) Trusted(gh *github.Client) func(context.Context, prstate.PR) (bool, error)

Trusted answers the fork author check from the store, then from GitHub. It stores a refusal to say (403), which the same login would get again, so a different login, or one after forbiddenTTL, checks again. Other failures aren't stored and are retried next run. Offline runs use what is stored, whoever checked it.

type Edit

type Edit struct {
	Name    string
	PR      func(*prstate.Snapshot) *prstate.Snapshot // nil leaves the PR as is
	Decider func(jev.Decider) jev.Decider             // nil asks as usual
}

Edit is one controlled change to a PR or to how its questions are asked.

type Estimate

type Estimate struct {
	PRs    int // labelled PRs that would be scored
	Calls  int
	Tokens int
	USD    float64 // at fallbackUSDPerMTok
}

Estimate is what a replay would send to the model, worked out from the diffs without calling it.

func EstimateCost

func EstimateCost(ctx context.Context, gh *github.Client, data Data, cfg *config.Config, ss []Sample, perturb bool, perturbSample int) (Estimate, error)

EstimateCost plans every labelled PR's calls, plus the probe and, if asked, the perturbation suite, and prices the input tokens.

func (Estimate) Text

func (e Estimate) Text(budget float64) string

Text says what the run would cost against the budget.

type Failure

type Failure struct {
	Number int
	Err    error
}

Failure is a PR that couldn't be scored.

type Fit

type Fit struct {
	OK        bool
	Why       string // when !OK
	ApproveAt float64
	DenyAt    float64
	// FalseApprove is the share of rejected history PRs approve_at lets in;
	// FalseDeny the share of merged and known-good PRs deny_at denies.
	FalseApprove, FalseDeny    float64
	FalseApproved, FalseDenied int
	ApproveRecall              float64 // share of merged history PRs approved
	// Precision at the fitted thresholds, on history. Reported only: it
	// depends on the base rate.
	ApprovePrecision, DenyPrecision float64
	NLabelled, NPositive, NNegative int // history PRs used
	NListedPositive                 int // known-good PRs in the false-deny rate
}

Fit is the result of the threshold sweep.

func FitThresholds

func FitThresholds(ps []Point, r Rates) Fit

FitThresholds picks approve_at as the lowest aggregate that approves at most r.MaxFalseApprove of rejected history PRs, and deny_at as the highest aggregate below it that denies at most r.MaxFalseDeny of positives.

type FlipRate

type FlipRate struct {
	Name          string
	N             int
	VerdictFlips  int
	QuestionFlips map[string]int
}

FlipRate counts how often an edit changed the verdict, or a question's pass or fail at approve_at, against the unedited score.

type HistoryOptions

type HistoryOptions struct {
	Since  time.Time
	MaxPRs int
	Mode   LabelMode
	Data   Data
}

HistoryOptions bound the history read.

type Label

type Label string

Label is the human outcome for a PR.

const (
	Unlabelled Label = ""
	Positive   Label = "positive"
	Negative   Label = "negative"
)

type LabelMode

type LabelMode string

LabelMode picks how history becomes labels.

const (
	// Strict uses other people's reviews: approved and merged is positive,
	// changes requested and closed is negative.
	Strict LabelMode = "strict"
	// Merged is for repos with one maintainer and no reviews: merged by a
	// writer is positive, closed unmerged is negative, and bot PRs are
	// skipped. The labels are weaker than Strict's.
	Merged LabelMode = "merged"
	// Maintainer adds two signals to Strict for repos that don't review
	// through GitHub, such as Prow repos: merged with an lgtm label is
	// positive, and closed unmerged by someone other than the author
	// (directly, or by a bot after their /close) is negative.
	Maintainer LabelMode = "maintainer"
)

type ListEntry

type ListEntry struct {
	Kind, Owner, Repo string
	Number            int
	SHA               string
}

ListEntry is one line of a PR list: "<kind> <owner/repo#N>[@sha]", or "<kind> <owner/repo@sha>" for a commit pushed without a PR. On a PR, SHA pins the commit to score, for a PR whose head later moved past the commit that earned it its place on the list. Number is 0 for a commit.

func ParseList

func ParseList(r io.Reader) ([]ListEntry, error)

ParseList reads a PR list. Blank lines and lines starting with # are skipped.

type Listed

type Listed struct {
	Ref     string
	Kind    string
	Label   Label
	Verdict string
	Caps    []string
	Err     error
}

Listed is one PR from a known-bad or known-good list and LGTM's verdict.

type LowPR

type LowPR struct {
	Ref   string
	Value float64
}

LowPR is a legitimate PR and its value on a veto question.

type Metrics

type Metrics struct {
	N                               int
	Approved, Denied, NeedsHuman    int
	ApprovePrecision, ApproveRecall float64
	DenyPrecision, DenyRecall       float64
	Disagreements                   []Point // approved negatives and denied positives
}

Metrics compare verdicts with labels at one policy.

func Measure

func Measure(ps []Point, pol verdict.Policy) Metrics

Measure scores fitted points under pol, caps included.

type Options

type Options struct {
	Owner, Repo   string
	Config        *config.Config
	Decider       jev.Decider
	BudgetUSD     float64
	History       HistoryOptions
	Rates         Rates // zero means DefaultRates
	Perturb       bool
	PerturbSample int // PRs to perturb; 0 means 10
	Concurrency   int // 0 means 4
	Progress      io.Writer
	Now           func() time.Time
	// Data is the local dataset. A zero Data reads everything from GitHub.
	Data Data
	// Rescore asks the model even when the dataset has its answer.
	Rescore bool
	// Lists also scores the dataset's listed PRs (known-bad and
	// known-good), outside the fit.
	Lists bool
}

Options configure a replay.

type Point

type Point struct {
	PR         prstate.PR
	Number     int
	Title      string
	Label      Label
	Low        bool
	Reason     string
	Agg        float64
	Values     []verdict.Value
	Partial    bool
	LockSource bool
	AutoRun    bool
	Sensitive  bool
	TooLarge   bool
	Untrusted  bool
	// Listed marks a PR from a known-bad or known-good list. Known-good ones
	// count towards false denials only; known-bad ones towards neither rate.
	Listed bool
}

Point is one scored, labelled PR.

func (Point) Verdict

func (p Point) Verdict(pol verdict.Policy) verdict.Verdict

Verdict re-decides p under pol. The borderline re-ask needs the model, so replay treats it as passed.

type PullStats

type PullStats struct {
	PRs, Labelled, Diffs, Failed int
	Cached                       int // already stored, so not read again
}

PullStats says what a pull read.

func Pull

func Pull(ctx context.Context, gh *github.Client, owner, repo string, opts HistoryOptions) (PullStats, error)

Pull labels repo's history and stores each labelled PR's diff and author check, so a later replay can run with --offline.

func PullList

func PullList(ctx context.Context, gh *github.Client, d Data, entries []ListEntry, label Label) (PullStats, error)

PullList stores each listed PR with label (negative for a known-bad list, positive for a known-good one) and its kind. PRs already stored that way are skipped; ones GitHub no longer serves are counted as failed.

type QuestionReliability

type QuestionReliability struct {
	ID   string
	ECE  float64 // expected calibration error over 10 equal-width bins
	N    int
	Bins []Bin // non-empty bins only
}

QuestionReliability compares one question's scores with PR outcomes.

func Reliability

func Reliability(ps []Point, qs []rubric.Question) []QuestionReliability

Reliability covers gate and veto questions. History labels whole PRs, so this shows how well each question tracks the PR outcome, not whether its own answer was right.

type Rates

type Rates struct {
	MaxFalseApprove float64 // share of rejected history PRs approved
	MaxFalseDeny    float64 // share of merged and known-good PRs denied
}

Rates bound the fit, as shares of each class rather than precision, which depends on how many PRs a repo rejects.

type Report

type Report struct {
	Repo        string
	Labels      LabelMode
	Model       string
	Served      []string
	Samples     []Sample
	Points      []Point
	Failed      []Failure
	BudgetHit   bool
	Fit         Fit
	Current     Metrics // at the thresholds in config
	Fitted      *Metrics
	CurrentPol  verdict.Policy
	Reliability []QuestionReliability
	Probe       []Baseline
	Baseline    *FlipRate
	Edits       []FlipRate
	Shadow      *Shadow
	Listed      []Listed
	// ListedPoints are the listed PRs as points, for the false-deny rate
	// and the cap sweep.
	ListedPoints []Point
	Rates        Rates
	CapSweeps    []CapSweep
	VetoSweeps   []VetoSweep
	CostUSD      float64
	// Calibration is what --write stores, or nil with WhyNot saying why not.
	Calibration *config.Calibration
	WhyNot      string
}

Report is everything replay found.

func Run

func Run(ctx context.Context, gh *github.Client, o Options) (rep *Report, err error)

Run labels history, scores each labelled PR, and fits thresholds.

func (*Report) Text

func (r *Report) Text() string

Text renders the report for a terminal. PR titles are untrusted, so they lose control characters.

type Sample

type Sample struct {
	PR       prstate.PR
	Label    Label
	Reason   string
	Low      bool // low-confidence: reported but not used for the fit
	Created  time.Time
	Merged   time.Time // zero when closed unmerged
	Comments int       // review comments plus PR comments
}

Sample is one past PR and its label.

func History

func History(ctx context.Context, gh *github.Client, owner, repo string, opts HistoryOptions) ([]Sample, error)

History lists PRs closed since opts.Since, newest first, and labels them.

type Shadow

type Shadow struct {
	N         int // labelled PRs with a shadow check run
	Agreed    int // approve on positive or deny on negative
	Decisive  int // labelled PRs where LGTM said approve or deny
	ByVerdict map[verdict.Verdict]ShadowGroup
}

Shadow compares earlier shadow-mode LGTM check runs with what humans did.

func ShadowMetrics

func ShadowMetrics(ctx context.Context, gh *github.Client, owner, repo string, ss []Sample) (*Shadow, error)

ShadowMetrics reads the LGTM check run on each sample's head. It returns nil when no sample has a shadow-mode run.

type ShadowGroup

type ShadowGroup struct {
	N                 int
	MedianTimeToMerge time.Duration
	MeanComments      float64
}

ShadowGroup describes PRs that got one shadow verdict.

type VetoRow

type VetoRow struct {
	At float64
	// Legit and Bad count denied, then total, by group: "history", or the
	// kind of a listed PR.
	Legit, Bad map[string][2]int
}

VetoRow counts what one veto_at would deny. Every history PR counts as legitimate here, rejected or not: a reviewer turning a PR down doesn't make it malicious.

type VetoSweep

type VetoSweep struct {
	Question string
	Rows     []VetoRow
	Lowest   []LowPR
}

VetoSweep is one veto question's rows and its lowest legitimate PRs, which are where a false denial would come from.

func VetoSweeps

func VetoSweeps(ps []Point, qs []rubric.Question, vetoAt float64) []VetoSweep

VetoSweeps reports each veto question at each sweep value and at vetoAt.

Jump to

Keyboard shortcuts

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