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 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
func (s ClosedPRSuppression) Suppressed(ctx context.Context) (map[string]SuppressedEntry, error)
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.
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.
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 ¶
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 ¶
func (c LedgerResolutionChecker) IsResolved(_ context.Context, pr providers.CuratedIssue) (bool, error)
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.
type Pass ¶
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).
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".