curate

package
v0.8.0 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package curate is the Phase-2 grooming agent: it dedups the KB backlog, gates the decision-ready queue on incident resolution, surfaces recurring blind spots as knowledge-gap issues, and drives lifecycle/decay. It writes to the forge only — it never merges and never touches a human-labelled artifact.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Agent

type Agent struct {
	Passes []Pass
	Log    *slog.Logger
}

Agent runs the grooming passes in order. Forge-only writes; never merges.

func (Agent) Run

func (a Agent) Run(ctx context.Context)

Run executes every pass, logging (not propagating) per-pass errors.

type ClosedPRLister added in v0.4.0

type ClosedPRLister interface {
	ListClosedUnmergedPRsByLabel(ctx context.Context, label string) ([]providers.CuratedIssue, error)
}

ClosedPRLister lists closed-but-unmerged KB PRs carrying a label. Merged PRs are accepted entries (never suppressed) and are filtered out by the implementation.

type ClosedPRSuppression added in v0.4.0

type ClosedPRSuppression struct {
	Forge ClosedPRLister
}

ClosedPRSuppression derives the suppression set on every run from the forge's closed-unmerged KB PRs — no mutable store or watermark, mirroring Recurrence's idempotent, ledger-driven design. The forge retains closed PRs, and each carries the same hidden DupFingerprint marker the drafter stamped, so the set is reconstructable each pass. A markerless (legacy/hand-filed) close is skipped: there is no stable key to suppress or count it on.

func (ClosedPRSuppression) Suppressed added in v0.4.0

Suppressed implements SuppressionSource.

type Contested added in v0.8.0

type Contested struct {
	Forge  ContestedForge
	Ledger ContestedSource
	KBRepo string // configured forge.kb_repo ("owner/name"); KB links outside it are foreign and skipped
	Log    *slog.Logger
}

Contested surfaces standing 👎 votes on the OPEN KB PR they relate to. The gap it closes: a 👎 on a FRESH investigation weighs nothing in recall trust (there is no catalog entry yet to decay), so without this pass the one human signal that says "this diagnosis is contested" never reaches the person about to make it permanent knowledge — the KB PR reviewer. Ledger-driven and idempotent like the other passes: votes are recomputed from the ledger every run, and a hidden per-trigger marker in the posted comment is the "already surfaced" record — no mutable store. It only ever comments; labels, closes and merges stay with the other passes and with humans.

func (Contested) Run added in v0.8.0

func (p Contested) Run(ctx context.Context) error

Run posts one warning comment per (open KB PR, contested trigger). Per-item failures are logged and skipped so one flaky PR never starves the rest.

type ContestedForge added in v0.8.0

type ContestedForge interface {
	Comment(ctx context.Context, number int, body string) error
	ListIssueCommentBodies(ctx context.Context, number int) ([]string, error)
	IsPROpen(ctx context.Context, number int) (bool, error)
}

ContestedForge is the forge surface the Contested pass needs. Kept separate from Forge: comment listing and PR-state lookup exist for this pass alone, and widening the shared interface would force every other pass's test fake to grow two irrelevant methods.

type ContestedSource added in v0.8.0

type ContestedSource interface {
	ContestedTriggers() []outcome.ContestedTrigger
}

ContestedSource yields the triggers whose delivered conclusion has standing 👎 votes and a KB link (the outcome ledger's ContestedTriggers view).

type Dedup

type Dedup struct {
	Forge     Forge
	Threshold float64 // title-Jaccard fallback threshold for markerless PRs; default 0.6 when 0
	Log       *slog.Logger
}

Dedup collapses near-identical open KB PRs: the lowest-numbered PR in a cluster is canonical; the rest are closed with a back-ref comment. Conservative by design (high similarity threshold) — a missed merge is cheaper than a wrong close.

func (Dedup) Run

func (d Dedup) Run(ctx context.Context) error

Run clusters open PRs and closes duplicates — fingerprint-first (deterministic DupFingerprint marker), falling back to title-Jaccard for markerless PRs.

type Forge

type Forge interface {
	ListPRsByLabel(ctx context.Context, label string) ([]providers.CuratedIssue, error)
	ListIssuesByLabel(ctx context.Context, label string) ([]providers.CuratedIssue, error)
	Comment(ctx context.Context, number int, body string) error
	ReplaceLabel(ctx context.Context, number int, remove, add string) error
	Close(ctx context.Context, number int) error
	OpenIssue(ctx context.Context, inv providers.Investigation) (providers.Ref, error)
}

Forge is the forge surface the groomer needs (all read/label/close — never merge).

type LedgerResolutionChecker

type LedgerResolutionChecker struct {
	Ledger interface {
		Episodes() ([]outcome.Episode, error)
	}
}

LedgerResolutionChecker reports a curated PR's incident has resolved when the outcome ledger holds a matching resolved episode. The join is keyed on the deterministic dedup fingerprint (resource+cause) carried in the PR body and stamped on every ledger open — stable across the LLM's prose, so a reworded re-investigation of one incident still resolves the matching PR. A PR filed before the fingerprint was wired carries no marker; it falls back to a whitespace-normalized title join. Source-agnostic: it reads the ledger's episodes, never a trigger-specific API.

func (LedgerResolutionChecker) IsResolved

IsResolved implements ResolutionChecker.

type Lifecycle

type Lifecycle struct {
	Forge      Forge
	StaleAfter time.Duration    // 0 disables the sweep
	Now        func() time.Time // injectable clock; nil ⇒ time.Now
	Log        *slog.Logger
}

Lifecycle closes stale, unprotected KB artifacts — those with no forge activity within StaleAfter. A PR whose age is unknown (zero UpdatedAt) is never closed.

func (Lifecycle) Run

func (l Lifecycle) Run(ctx context.Context) error

Run closes stale, unprotected artifacts with a comment.

type Pass

type Pass interface {
	Run(ctx context.Context) error
}

Pass is one grooming pass (dedup, queue, lifecycle…). Each is independent and resilient: a pass error is logged, not fatal — the agent runs the rest.

type Queue

type Queue struct {
	Forge   Forge
	Checker ResolutionChecker
	Log     *slog.Logger
}

Queue applies the merge condition: a quality-passing (solved) PR becomes ready-to-merge when the incident is resolved, OR when a human has labelled it accepted. Unresolved + unaccepted PRs wait (surfaced, never auto-queued).

func (Queue) Run

func (q Queue) Run(ctx context.Context) error

Run gates each solved PR onto the decision-ready queue.

type Recurrence

type Recurrence struct {
	Forge  Forge
	Ledger interface {
		Episodes() ([]outcome.Episode, error)
	}
	Threshold int // default 3 when 0
	// Suppressed, when set, is the set of KB entries a human closed WITHOUT merging
	// (keyed by DupFingerprint). Their episodes are counted SEPARATELY — silently,
	// keyed on the fingerprint — and, past the threshold, escalate via a knowledge-gap
	// issue that LINKS the closed PR ("closed unmerged but recurred N times —
	// reconsider?") instead of the plain gap message. A close-without-merge is a
	// deliberate "no": we respect it and never reopen the PR. Nil disables the branch
	// (behaviour is then identical to the pre-suppression pass).
	Suppressed SuppressionSource
	Log        *slog.Logger
}

Recurrence opens ONE knowledge-gap issue when an unresolved incident pattern recurs at least Threshold times. It is ledger-driven and idempotent: it recomputes counts from the outcome ledger every run and opens an issue only when no knowledge-gap issue for that pattern already exists — so re-running never double-opens (no mutable store or watermark).

func (Recurrence) Run

func (r Recurrence) Run(ctx context.Context) error

Run opens a knowledge-gap issue for each pattern that crosses the threshold and has none open yet. Episodes belonging to a suppressed (closed-unmerged) entry are routed to a separate escalation that links the closed PR rather than the generic gap issue.

type ResolutionChecker

type ResolutionChecker interface {
	IsResolved(ctx context.Context, pr providers.CuratedIssue) (bool, error)
}

ResolutionChecker reports whether the incident behind a curated PR has cleared (alert resolved / resource healthy). Implemented read-only over the cluster.

type SuppressedEntry added in v0.4.0

type SuppressedEntry struct {
	Fingerprint string // curator DupFingerprint (resource+cause); the join key against ledger episodes
	PRNumber    int    // the closed-unmerged KB PR to reference in the escalation
	Reason      string // the close-reason label, when one distinguishes it (e.g. "wontfix"); "" otherwise
}

SuppressedEntry is a KB entry a human closed WITHOUT merging — a deliberate "not knowledge-base-worthy". It is keyed on the entry's DupFingerprint so the recurrence pass can keep counting the incident silently and, past the threshold, escalate via a knowledge-gap issue that LINKS the closed PR — never reopening it.

type SuppressionSource added in v0.4.0

type SuppressionSource interface {
	Suppressed(ctx context.Context) (map[string]SuppressedEntry, error)
}

SuppressionSource yields the set of suppressed fingerprints (closed-unmerged KB entries a human rejected), keyed by DupFingerprint. Recurrence consults it to escalate-via-issue instead of re-litigating a human "no".

Jump to

Keyboard shortcuts

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