Documentation
¶
Index ¶
- Constants
- func AdjustColumnWidths(cols []TableColumn, prs []PRInfo)
- func AgeColorCode(created, now time.Time) string
- func AgeColorFunc(p PRInfo) string
- func AgeDays(created, now time.Time) int
- func AgeInfoFunc(p PRInfo) string
- func AuthorInfoFunc(p PRInfo) string
- func ChangeID(title string) string
- func ColorizeRescue(m *RescueMarker, now time.Time) string
- func ColorizeStatus(state StatusState, detail string) string
- func DependencyInfoFunc(p PRInfo) string
- func DisableLineWrap(w *os.File)
- func EnableLineWrap(w *os.File)
- func ExtractDependencyName(title string) string
- func ExtractOwnerRepo(htmlURL string) (string, string, error)
- func ExtractTargetVersion(title string) string
- func ExtractVersion(title string) string
- func FormatAge(created, now time.Time) string
- func FormatRescue(m *RescueMarker, now time.Time) string
- func IsDependencyUpdateTitle(title string) bool
- func MakeHyperlink(text, url string) string
- func ParsePRURL(htmlURL string) (owner, repo string, number int, err error)
- func PatchID(files []*github.CommitFile, changedFiles int) string
- func PrintPlainResults(w *os.File, status *PRStatus)
- func PrintRow(w *os.File, e StatusEntry, cols []TableColumn)
- func PrintTableHeader(w *os.File, cols []TableColumn)
- func RepoInfoFunc(p PRInfo) string
- func UpdateTable(w *os.File, entries []StatusEntry, cols []TableColumn)
- func VersionInfoFunc(p PRInfo) string
- type Counts
- type Fingerprint
- type PRGroup
- type PRInfo
- type PRStatus
- func (s *PRStatus) ActionRequired() []StatusEntry
- func (s *PRStatus) Add(pr PRInfo) int
- func (s *PRStatus) BlockedEntries() []StatusEntry
- func (s *PRStatus) CancelledEntries() []StatusEntry
- func (s *PRStatus) FormatSummary() string
- func (s *PRStatus) Len() int
- func (s *PRStatus) MergedEntries() []StatusEntry
- func (s *PRStatus) NoVerdictEntries() []StatusEntry
- func (s *PRStatus) RefreshedEntries() []StatusEntry
- func (s *PRStatus) RescueAt(idx int) *RescueMarker
- func (s *PRStatus) RetriedEntries() []StatusEntry
- func (s *PRStatus) SecurityFailedEntries() []StatusEntry
- func (s *PRStatus) SetRescue(idx int, marker *RescueMarker)
- func (s *PRStatus) SkippedEntries() []StatusEntry
- func (s *PRStatus) Snapshot() []StatusEntry
- func (s *PRStatus) StaleEntries() []StatusEntry
- func (s *PRStatus) StateAt(idx int) StatusState
- func (s *PRStatus) Summary() Counts
- func (s *PRStatus) Update(idx int, state StatusState, detail string)
- type RescueMarker
- type StatusEntry
- type StatusState
- type TableColumn
Constants ¶
const ( StaleGroupHeader = "Stale (behind base, failing checks green there -- refresh first)" RefreshedGroupHeader = "Refreshed (re-checking)" CancelledGroupHeader = "Cancelled (CircleCI auto-cancelled the build -- retry first)" RetriedGroupHeader = "Retried (re-checking)" // NoVerdictGroupHeader names the bucket for checks that established // nothing about the code. Each entry's detail names its own remedy. NoVerdictGroupHeader = "CI unavailable (no verdict) -- the checks decided nothing, see the detail" )
Section headers shared by the plain-text and TUI summaries for the stale and cancelled buckets, so both outputs name them identically.
Variables ¶
This section is empty.
Functions ¶
func AdjustColumnWidths ¶
func AdjustColumnWidths(cols []TableColumn, prs []PRInfo)
AdjustColumnWidths widens each column so every value fits without truncation.
func AgeColorCode ¶
AgeColorCode returns the ANSI color prefix for a PR age: "" while the PR is fresh, yellow once it passes ageWarnAfter, red once it passes ageStaleAfter. Callers must reset with \033[0m when non-empty.
func AgeColorFunc ¶
AgeColorFunc is the table-column color accessor for the Age column.
func AgeDays ¶
AgeDays returns the whole number of days since created, or 0 for a zero creation time.
func AgeInfoFunc ¶
AgeInfoFunc is the table-column accessor for the Age column.
func AuthorInfoFunc ¶
func ChangeID ¶
ChangeID fingerprints a dependency-update PR by what it updates: the dependency name and the target version parsed from the PR title, e.g. "typescript@v7" for "chore(deps): update dependency typescript to v7". Titles naming no single dependency or no version ("Update all non-major dependencies", "Lock file maintenance") yield "".
func ColorizeRescue ¶
func ColorizeRescue(m *RescueMarker, now time.Time) string
ColorizeRescue wraps the FormatRescue annotation in ANSI colors: a stale marker renders yellow (retriable -- the change differs from the one attempted), a fresh one bold red (a rescue already failed on exactly this change, whether or not the branch was rebased since; a human is needed).
func ColorizeStatus ¶
func ColorizeStatus(state StatusState, detail string) string
func DependencyInfoFunc ¶
func DisableLineWrap ¶
DisableLineWrap turns off the terminal's auto-wrap (DECAWM) so rows wider than the terminal are clipped at the right edge instead of wrapping onto a second physical line. UpdateTable's cursor-up math counts logical rows, so a wrapped row would desync the redraw and produce the garbled output users see when CI detail strings push a line past the terminal width.
func EnableLineWrap ¶
EnableLineWrap restores the terminal's auto-wrap mode. Always pair it with DisableLineWrap (typically via defer) so an early return does not leave the user's terminal in a clipped state.
func ExtractDependencyName ¶
func ExtractOwnerRepo ¶
ExtractOwnerRepo parses owner and repo from a GitHub HTML URL (e.g. "https://github.com/OWNER/REPO/pull/123").
func ExtractTargetVersion ¶
ExtractTargetVersion returns the version a dependency update moves to, regardless of whether the title also names the version it moves from.
func ExtractVersion ¶
ExtractVersion renders the version change named in a title for display: "4.17.20 -> 4.17.21" for a from/to title, "v1.10.2" for a to-only title, "" when the title names no version.
func FormatAge ¶
FormatAge renders the time since created as a compact human string ("5h", "3d", "2w"). It returns "" for a zero creation time so callers can omit the column value when the discovery path did not provide it.
func FormatRescue ¶
func FormatRescue(m *RescueMarker, now time.Time) string
FormatRescue renders the marker as a short human-readable annotation for status output, e.g. "rescue failed 1d ago (klaus): ESM-only", "rescue failed 3d ago (klaus), stale: new commits since" or "rescue blocked 5d ago (klaus), rebased since: same change: ESM-only".
func IsDependencyUpdateTitle ¶
IsDependencyUpdateTitle returns true if the PR title looks like an automated dependency update (Renovate or Dependabot), regardless of who authored it.
func MakeHyperlink ¶
func ParsePRURL ¶
ParsePRURL parses owner, repo, and PR number from a GitHub pull request URL (e.g. "https://github.com/OWNER/REPO/pull/123").
func PatchID ¶
func PatchID(files []*github.CommitFile, changedFiles int) string
PatchID computes the content fingerprint of a PR from the files of its compare response (GET /repos/{owner}/{repo}/compare/{base}...{head}). changedFiles is the PR's changed_files count. The compare API returns at most 300 files and omits the patch of a file whose diff is too large, so PatchID returns "" whenever the input cannot describe the whole change rather than fingerprinting part of it.
Per file the hash covers status, previous and current path, and every added or removed line of the patch in order. Hunk headers and context lines are skipped: both shift when the base branch changes around the PR's lines, which is exactly what a rebase produces. A file without a patch and without changed lines (binary content, a pure rename, a mode change) contributes its blob SHA instead.
func PrintPlainResults ¶
func PrintRow ¶
func PrintRow(w *os.File, e StatusEntry, cols []TableColumn)
func PrintTableHeader ¶
func PrintTableHeader(w *os.File, cols []TableColumn)
func RepoInfoFunc ¶
func UpdateTable ¶
func UpdateTable(w *os.File, entries []StatusEntry, cols []TableColumn)
func VersionInfoFunc ¶
Types ¶
type Counts ¶
type Counts struct {
Merged int
Failed int
Blocked int
NoVerdict int
Stale int
Refreshed int
Cancelled int
Retried int
Skipped int
}
Counts holds the aggregate tallies of a sweep by outcome category.
Failed covers every action-required outcome (plain and security failures, conflicts, untrusted authors). Blocked (CI could not run because of an Actions budget block), NoVerdict (the failing checks established nothing about the code), Stale (failing checks are green on the base branch head and the PR is behind it), Refreshed (a stale branch was just updated from its base), Cancelled (CircleCI auto-cancelled the failing builds) and Retried (those builds were just retried) are counted separately from Failed: none of them is a genuine CI failure and none of them belongs in the rescue path.
type Fingerprint ¶
type Fingerprint struct {
PatchID string `json:"patch_id,omitempty"`
ChangeID string `json:"change_id,omitempty"`
}
Fingerprint identifies what a PR changes independently of its head SHA. Renovate force-pushes a rebased branch whenever its base moves, so the head SHA changes while the change itself stays byte-identical; a fingerprint lets a rescue marker survive that.
PatchID is the precise fingerprint: a hash over the PR's compare diff normalised the way `git patch-id --stable` does, minus the context lines -- hunk headers and line numbers are ignored, only file paths and the added/removed lines count. It is empty when the diff could not be fetched or GitHub truncated it (see PatchID).
ChangeID is the cheap approximation for dependency-update PRs: the dependency name and target version parsed from the PR title, e.g. "typescript@v7". A rebase never changes it and a new version always does, but a version update that keeps the title (7.0.1 -> 7.0.2 under "to v7") is invisible to it, so it only decides when no patch ID is available. It is empty when the title is not a recognisable single-dependency update.
func (Fingerprint) Comparable ¶
func (f Fingerprint) Comparable() bool
Comparable reports whether the fingerprint carries anything to compare.
func (Fingerprint) Matches ¶
func (f Fingerprint) Matches(other Fingerprint) bool
Matches reports whether other describes the same change as f. The patch ID decides whenever both sides have one; the title-derived change ID is the fallback for when the diff could not be fingerprinted on either side. Nothing comparable means "not the same" -- a marker is only kept alive on positive evidence.
type PRStatus ¶
type PRStatus struct {
// contains filtered or unexported fields
}
func NewPRStatus ¶
func NewPRStatus() *PRStatus
func (*PRStatus) ActionRequired ¶
func (s *PRStatus) ActionRequired() []StatusEntry
ActionRequired returns the failure entries, oldest PR first: the longer a dependency PR has been open, the more sweeps it has already survived, so the old ones are the most likely to need manual work. Entries without a known creation time sort last, in insertion order.
func (*PRStatus) BlockedEntries ¶
func (s *PRStatus) BlockedEntries() []StatusEntry
BlockedEntries returns entries whose CI could not run because a GitHub Actions budget / spending-limit block prevented every job from starting. These are deliberately kept out of ActionRequired and the failed counts: the remedy is "raise or await the Actions budget", not "rescue the code".
func (*PRStatus) CancelledEntries ¶
func (s *PRStatus) CancelledEntries() []StatusEntry
CancelledEntries returns entries whose every failing check is a CircleCI build that CircleCI itself cancelled. Like StaleEntries these are kept out of ActionRequired and the failed counts -- the remedy is "retry the build and read the real verdict", not "rescue the code". Oldest PR first.
func (*PRStatus) FormatSummary ¶
func (*PRStatus) MergedEntries ¶
func (s *PRStatus) MergedEntries() []StatusEntry
func (*PRStatus) NoVerdictEntries ¶ added in v0.10.2
func (s *PRStatus) NoVerdictEntries() []StatusEntry
NoVerdictEntries returns entries whose every failing check established nothing about the code. Like BlockedEntries they are kept out of ActionRequired and the failed counts: there is nothing to rescue, and each entry's detail names its own remedy. Oldest PR first.
func (*PRStatus) RefreshedEntries ¶
func (s *PRStatus) RefreshedEntries() []StatusEntry
RefreshedEntries returns the stale entries whose branch was updated from its base during this run. Their CI is running again; the next sweep decides what they are.
func (*PRStatus) RescueAt ¶
func (s *PRStatus) RescueAt(idx int) *RescueMarker
RescueAt returns the rescue marker attached to the entry at idx, or nil when none is attached or idx is out of range.
func (*PRStatus) RetriedEntries ¶
func (s *PRStatus) RetriedEntries() []StatusEntry
RetriedEntries returns the cancelled entries whose builds were retried during this run. Their CI is running again; the next sweep decides what they are.
func (*PRStatus) SecurityFailedEntries ¶
func (s *PRStatus) SecurityFailedEntries() []StatusEntry
SecurityFailedEntries returns entries that failed specifically because a security-related check reported a problem.
func (*PRStatus) SetRescue ¶
func (s *PRStatus) SetRescue(idx int, marker *RescueMarker)
SetRescue attaches a prior rescue-attempt marker to an entry.
func (*PRStatus) SkippedEntries ¶
func (s *PRStatus) SkippedEntries() []StatusEntry
func (*PRStatus) Snapshot ¶
func (s *PRStatus) Snapshot() []StatusEntry
func (*PRStatus) StaleEntries ¶
func (s *PRStatus) StaleEntries() []StatusEntry
StaleEntries returns entries whose failure is stale: the PR head is behind its base branch and every failing check is green on the base branch head. Like BlockedEntries these are kept out of ActionRequired and the failed counts -- the remedy is "update the branch and let CI re-run", not "rescue the code". Oldest PR first, like ActionRequired.
func (*PRStatus) StateAt ¶
func (s *PRStatus) StateAt(idx int) StatusState
StateAt returns the current state of the entry at idx, or StatusPending when idx is out of range.
type RescueMarker ¶
type RescueMarker struct {
Tool string `json:"tool,omitempty"`
Outcome string `json:"outcome"`
Reason string `json:"reason,omitempty"`
HeadSHA string `json:"head_sha,omitempty"`
At time.Time `json:"at,omitempty"`
Fingerprint
// Stale and Rebased are computed by MarkStale, never serialized into
// the marker. Rebased is set when the head moved but the change did not.
Stale bool `json:"-"`
Rebased bool `json:"-"`
}
RescueMarker records a prior automated rescue attempt on a PR. It is embedded as a machine-readable HTML comment inside an ordinary PR comment, so any tool that can comment on a PR (a klaus agent, a Cursor agent, a GitHub Action) can participate without coupling to marge:
<!-- ai-rescue: {"tool":"klaus","outcome":"failed","reason":"...","head_sha":"...","at":"2026-06-09T18:40:00Z"} -->
HeadSHA ties the attempt to the code it was attempted against. Renovate force-pushes on rebase or version change, so a marker whose head_sha no longer matches the PR head describes code that may no longer exist. The embedded Fingerprint (patch_id, change_id; optional, absent from markers written before it existed) tells the two apart: when the head moved but the fingerprint still matches, the branch was only rebased and the attempt still stands; otherwise it is stale and the PR is fair game for another rescue.
func ParseRescueMarker ¶
func ParseRescueMarker(body string) *RescueMarker
ParseRescueMarker extracts the last ai-rescue marker from a comment body. It returns nil when the body contains no parseable marker; a malformed JSON payload is ignored rather than treated as an error so a mangled comment can never break a sweep.
func (*RescueMarker) CommentBody ¶
func (m *RescueMarker) CommentBody() string
CommentBody renders the full PR comment for this marker: a human-readable summary followed by the machine-readable marker.
func (*RescueMarker) MarkStale ¶
func (m *RescueMarker) MarkStale(currentHeadSHA string, current func() Fingerprint)
MarkStale sets Stale and Rebased by comparing the marker with the PR's current head. Same head (short-vs-full SHA prefixes match): fresh. Head moved: the marker stays fresh, flagged Rebased, when the change it was written for is still the change on the branch -- decided by the marker's fingerprint against the current one, which `current` computes on demand. It is only called when the head moved and the marker carries a fingerprint; nil means none can be computed. Otherwise the marker is stale. A marker without a recorded SHA cannot be aged out, so it stays fresh until a newer marker replaces it.
type StatusEntry ¶
type StatusEntry struct {
PR PRInfo
State StatusState
Detail string
// Rescue is the most recent prior automated rescue attempt found on
// the PR, if any. Only populated for failure-state entries.
Rescue *RescueMarker
}
func SplitActionRequired ¶
func SplitActionRequired(entries []StatusEntry) (security, other []StatusEntry)
SplitActionRequired partitions the action-required list into security failures and everything else, preserving the input order in each group.
type StatusState ¶
type StatusState int
const ( StatusPending StatusState = iota StatusChecking StatusApproving StatusMerging StatusRetrying StatusMerged StatusAlreadyMerged StatusAutoMerge StatusFailed StatusFailedSecurity StatusBlockedCI StatusSkipped StatusConflict StatusUntrustedAuthor // StatusStale marks a failing PR whose head is behind its base branch // and whose every failing check is green on the base branch head: the // failure was most likely fixed on the base branch after the PR's last // build, so the first move is to refresh the branch, not to rescue it. StatusStale // StatusRefreshed marks a stale PR whose branch was just updated from // its base (the "Update branch" button); CI is running again and the // next sweep decides. StatusRefreshed // StatusCancelled marks a failing PR whose every failing check is a // CircleCI build that CircleCI itself cancelled (a newer pipeline on the // branch, a redundant workflow) rather than one that failed a step. // There is no verdict on the code yet: the remedy is a retry, not a // rescue. StatusCancelled // StatusRetried marks a cancelled PR whose builds were just retried on // the same commit; CI is running again and the next sweep decides. StatusRetried // StatusNoVerdict marks a failing PR whose every failing check // established nothing about the code: the job was cancelled, or a // CircleCI project setting refused the pipeline. There is nothing to // rescue, and a security check in this shape is not a finding. The // detail names the remedy for each check. StatusNoVerdict )
func (StatusState) String ¶
func (s StatusState) String() string
type TableColumn ¶
type TableColumn struct {
Label string
Width int
Fn func(PRInfo) string
// Color optionally returns an ANSI color prefix for a value. The
// value is padded to Width first, then wrapped, so colored cells
// stay aligned with the rest of the table.
Color func(PRInfo) string
}
func DependencySelectedColumns ¶
func DependencySelectedColumns() []TableColumn
func FullColumns ¶
func FullColumns() []TableColumn
func RepoSelectedColumns ¶
func RepoSelectedColumns() []TableColumn