Documentation
¶
Index ¶
- Constants
- Variables
- func BuildEnv(ghToken, anthropicBaseURL, anthropicAuthToken, anthropicModel string) map[string]string
- func NewClaudeAuthStep(prober ClaudeProber) agentlib.Step
- func NewExecutionStep(runner claudelib.ClaudeRunner, ops git.GitOps, gh GhCli, gate GateRunner, ...) agentlib.Step
- func NewGHTokenCheckStep(ghToken string) agentlib.Step
- func NewPlanningStep(runner claudelib.ClaudeRunner, ops git.GitOps, ghToken string, ...) agentlib.Step
- func NewReviewStep(ops git.GitOps, gh GhCli, gate GateRunner, ghToken string, prTarget PRTarget) agentlib.Step
- type BulkUpdateResult
- type BulkUpdater
- type ClaudeProber
- type GateRunner
- type GhCli
- type GoBump
- type InstallationScope
- type PRTarget
- type PRTargets
- type PlanOutput
- type PlanVuln
- type ResultOutput
- type ReviewChecks
- type ReviewOutput
- type ScopeVerdict
Constants ¶
const ( PlanOutcomeReady = "ready" PlanOutcomeNoUpdateNeeded = "no_update_needed" PlanOutcomeNeedsInput = "needs_input" )
Outcome values for PlanOutput.Outcome.
const ( VulnActionFix = "fix" VulnActionPark = "park" )
Action values for PlanVuln.Action.
const ( ResultOutcomeOpened = "opened" ResultOutcomeAdopted = "adopted" ResultOutcomeNoUpdateNeeded = "no_update_needed" ResultOutcomeFailed = "failed" )
Outcome values for ResultOutput.Outcome.
const AgentLogin = "github-update-go-agent"
AgentLogin is the GitHub-task-system identity of this agent. Per the platform doctrine the CONTROLLER writes previous_assignee — the constant exists for logs and messages only; steps never mutate assignee.
Variables ¶
var AvailablePRTargets = PRTargets{PRTargetDraft, PRTargetReady}
AvailablePRTargets lists every PRTarget the agent accepts. Validate ranges over this collection — it is the single source of truth.
Functions ¶
func BuildEnv ¶
func BuildEnv( ghToken, anthropicBaseURL, anthropicAuthToken, anthropicModel string, ) map[string]string
BuildEnv assembles the env map forwarded into the Claude CLI subprocess. Only set values are forwarded so the subprocess sees a clean env. Shared by both the Kafka entry point (main.go) and the local-CLI entry point (cmd/run-task/main.go).
GH_TOKEN is threaded explicitly because the ClaudeRunner strips the pod env to an allowlist — os.Setenv alone does not reach the Claude subprocess.
func NewClaudeAuthStep ¶
func NewClaudeAuthStep(prober ClaudeProber) agentlib.Step
NewClaudeAuthStep wires the claude-auth preflight with its prober seam.
func NewExecutionStep ¶
func NewExecutionStep( runner claudelib.ClaudeRunner, ops git.GitOps, gh GhCli, gate GateRunner, bulk BulkUpdater, ghToken string, prTarget PRTarget, ) agentlib.Step
NewExecutionStep wires the execution step with its five seams: the Claude runner (repair + CHANGELOG sub-call), the GitOps seam (clone/branch/commit/ push), the gh CLI seam (PR create + adopt), the gate runner (deterministic green-gate re-run), and the bulk updater (deterministic `go get -u ./...` + `go mod tidy`, run in Go so the model cannot background it). prTarget selects draft or ready.
func NewGHTokenCheckStep ¶
NewGHTokenCheckStep constructs the preflight step. ghToken is the resolved token from startup auth (passed by main → factory). Empty means the step fails fast on the first run.
func NewPlanningStep ¶
func NewPlanningStep( runner claudelib.ClaudeRunner, ops git.GitOps, ghToken string, scope InstallationScope, ) agentlib.Step
NewPlanningStep wires the planning step with its Claude runner (inspection LLM), the GitOps seam (clone at ref), the GitHub token (HTTPS auth URL transformation), and the installation-scope allowlist check.
func NewReviewStep ¶
func NewReviewStep( ops git.GitOps, gh GhCli, gate GateRunner, ghToken string, prTarget PRTarget, ) agentlib.Step
NewReviewStep wires the ai_review verifier with its GitOps seam (fresh clone + tag/rev inspection), gh CLI seam (PR state), gate runner (independent gate re-run), the GitHub token, and the configured PRTarget the step compares observed draft-ness against.
Types ¶
type BulkUpdateResult ¶ added in v0.5.0
type BulkUpdateResult struct {
// Ran is false when the sequence could not complete. FailDetail then
// explains why, and the model is told to treat the update as NOT done.
Ran bool
// FailDetail is empty when Ran is true.
FailDetail string
// Output is the tail of the combined command output, for the prompt.
Output string
}
BulkUpdateResult reports what the deterministic bulk update did.
Like FunnelResult in github-pr-review-agent, this is returned instead of a bare error for the expected non-fatal outcomes, so the caller can surface a fail-closed condition into the prompt rather than aborting the pipeline. The model must be told the update did not run — never left to assume it did.
type BulkUpdater ¶ added in v0.5.0
type BulkUpdater interface {
Run(ctx context.Context, workdir string) (BulkUpdateResult, error)
}
BulkUpdater runs the mechanical dependency-update sequence (`go get -u ./...` + `go mod tidy`) in Go, before the execution model call.
The agent runs this itself rather than instructing the model to. Left to the model, a long-running `go get` invites backgrounding: on 2026-08-16 the execution model put it in a harness background task and then blocked on TaskOutput for 600s, timed out, and re-issued the identical blocking call on the same task_id — burning the Job's full 1800s activeDeadlineSeconds and producing nothing (bborbe/ip, job bc3c6599; also bborbe/run, bborbe/beactive).
Prompt rules could not prevent it: execution.md already forbade backgrounding, but named only the shell forms (`&`, `nohup`, detached jobs), not the harness's own run_in_background/TaskOutput. Running the step in Go removes the model's ability to express it in any form at all — the same reasoning that moved the ast-grep funnel into Go in github-pr-review-agent.
func NewBulkUpdater ¶ added in v0.5.0
func NewBulkUpdater() BulkUpdater
NewBulkUpdater constructs the production BulkUpdater.
type ClaudeProber ¶
type ClaudeProber interface {
// Probe returns nil when claude is authenticated and reachable, or an
// error describing the failure otherwise.
Probe(ctx context.Context) error
}
ClaudeProber runs a trivial claude invocation to confirm the CLI is authenticated before any prompt-bearing phase runs.
func NewClaudeProber ¶
func NewClaudeProber(claudeConfigDir claudelib.ClaudeConfigDir) ClaudeProber
NewClaudeProber constructs a ClaudeProber that shells out to `claude --print`. claudeConfigDir, when non-empty, is exported as CLAUDE_CONFIG_DIR so the probe reads the same login the agent will.
type GateRunner ¶
type GateRunner interface {
// RunTarget runs `make <target>` in workdir. Returns the bounded
// combined-output tail, the process exit code (0 on success), and a
// non-nil error when the target failed or could not be started.
RunTarget(ctx context.Context, workdir, target string) (tail string, exitCode int, err error)
}
GateRunner runs one repo gate target (`make -C <workdir> <target>`) and reports the exit code plus a bounded output tail. The execution step uses it as the deterministic green-gate after the Claude sub-call; the ai_review step re-runs the same targets independently.
func NewOSExecGateRunner ¶
func NewOSExecGateRunner() GateRunner
NewOSExecGateRunner returns a GateRunner shelling out to make. The gate inherits the full pod env (os.Environ()): repo Makefiles run scanners via `go run tool@version`, which needs GOPROXY/GOPATH/HOME plus GH_TOKEN for private module fetches — the design accepts in-pod arbitrary code execution for own repos (design § 7.2).
type GhCli ¶
type GhCli interface {
// CreatePR opens a pull request from head against base, running gh
// inside workdir so the repo is inferred from the git remote. target
// selects draft or ready-for-review at creation time. Returns the PR URL.
CreatePR(
ctx context.Context,
workdir, base, head, title, body string,
target PRTarget,
) (string, error)
// FindOpenPRByHead returns the URL of an open PR whose head is the
// given branch, or "" when none exists. repo is "owner/name".
FindOpenPRByHead(ctx context.Context, repo, head string) (string, error)
// ViewPR returns the state (e.g. "OPEN", "MERGED", "CLOSED") and
// draft flag of the PR identified by URL.
ViewPR(ctx context.Context, prURL string) (state string, isDraft bool, err error)
}
GhCli is the seam between the steps and the gh binary. Three methods cover the agent's entire PR surface: create a PR at a configurable target (draft or ready-for-review), adopt an already-open PR by head branch (crash-window replay guard), and view PR state for the ai_review checks.
The creation target is chosen per deployment; the agent itself only opens PRs and never changes their draft/ready state after creation. Deliberately absent: no Ready, no Merge — the agent never flips the draft flag of an already-open pull request and never merges; the human does.
func NewOSExecGhCli ¶
NewOSExecGhCli returns a GhCli implementation that shells out to the gh binary with a minimal allowlisted env (GH_TOKEN, HOME, PATH).
type InstallationScope ¶ added in v0.3.0
type InstallationScope interface {
// Allows reports whether repo ("owner/name") is covered by the token's
// installation. Unknown means the check itself was impossible — never
// treat that as denial.
Allows(ctx context.Context, repo string) ScopeVerdict
}
InstallationScope answers whether the GitHub App installation backing the agent's token covers a repo. The App installation's repository selection IS the fleet's per-stage allowlist (design § 7.2): a repo outside it must park in planning, before any clone/update work — not after ten minutes of work at the push step.
func NewGhInstallationScope ¶ added in v0.3.0
func NewGhInstallationScope(ghToken string) InstallationScope
NewGhInstallationScope returns an InstallationScope that shells out to `gh api /installation/repositories` with the agent's token. With an App installation token the endpoint lists exactly the accessible repos; with the local GH_TOKEN PAT fallback it 403s, which maps to ScopeUnknown (local runs are operator-supervised — no allowlist to enforce).
type PRTarget ¶ added in v0.4.0
type PRTarget string
PRTarget is the closed set of pull-request targets the agent may open a pull request as. It is chosen per deployment at creation time only — the agent never flips an already-open pull request and never merges.
func ParsePRTarget ¶ added in v0.4.0
ParsePRTarget resolves the configured pull-request target. An empty value means "unset" and resolves to PRTargetDraft, preserving the pre-configuration behaviour byte-for-byte. Any other unrecognised value is rejected.
type PRTargets ¶ added in v0.4.0
type PRTargets []PRTarget
PRTargets is a collection of PRTarget values.
type PlanOutput ¶
type PlanOutput struct {
Outcome string `json:"outcome"`
// HasWork is true when the execution phase has anything to do.
// omitempty is deliberately NOT applied so a `false` decision is
// always written explicitly.
HasWork bool `json:"has_work"`
// GoBump records the go-directive bump (from current directive to the
// image toolchain per design D5). Nil when the directive is already
// current.
GoBump *GoBump `json:"go_bump,omitempty"`
// DepUpdatesExpected is true when `go list -u -m` style inspection
// found outdated direct dependencies.
DepUpdatesExpected bool `json:"dep_updates_expected"`
// GateTargets are the Makefile targets forming the repo's own gate
// (e.g. precommit, check, vulncheck). Execution and ai_review re-run
// exactly these to exit 0 — never a hardcoded scanner.
GateTargets []string `json:"gate_targets,omitempty"`
// Vulns holds every scanner finding with its fix-vs-park classification.
Vulns []PlanVuln `json:"vulns,omitempty"`
// Reason carries the human-readable explanation on the
// needs_input / no_update_needed paths.
Reason string `json:"reason,omitempty"`
}
PlanOutput is the typed contract for the `## Plan` JSON section the planning step writes for every github-update-go task. Round-trips with agentlib.MarshalSectionTyped + agentlib.ExtractSection[PlanOutput].
Shapes (design § 4.3):
- Outcome="ready" — update work exists; GateTargets populated, HasWork=true
- Outcome="no_update_needed" — repo already current; HasWork=false
- Outcome="needs_input" — park path (unfixable finding / precondition); Reason populated
No `Details map[string]any`: concrete fields only. Future fields require a design amendment.
type PlanVuln ¶
type PlanVuln struct {
// ID is the finding identifier (GO-*, CVE-*, GHSA-*).
ID string `json:"id"`
// Package is the vulnerable module path.
Package string `json:"package,omitempty"`
// FixedVersion is the patched version the scanner reported; empty when
// no fix exists (the park case).
FixedVersion string `json:"fixed_version,omitempty"`
// Scanner names which gate scanner flagged the finding
// (govulncheck | osv-scanner | trivy).
Scanner string `json:"scanner,omitempty"`
// Action is "fix" (patched version exists — bump it) or "park"
// (no fix / out-of-scope major — task parks per design D4).
Action string `json:"action"`
// Reason is the one-line classification justification.
Reason string `json:"reason,omitempty"`
}
PlanVuln is one scanner finding with its planned action.
type ResultOutput ¶
type ResultOutput struct {
Outcome string `json:"outcome"`
Branch string `json:"branch,omitempty"`
PRURL string `json:"pr_url,omitempty"`
// GateExit is the exit code of the last gate target run (0 = green).
// omitempty is deliberately NOT applied — an explicit 0 documents the
// green gate.
GateExit int `json:"gate_exit"`
// DepsUpdated counts the module bumps the update sequence applied.
DepsUpdated int `json:"deps_updated"`
// VulnsFixed lists the finding IDs resolved by the update — always a
// subset of the plan's fix-action vulns.
VulnsFixed []string `json:"vulns_fixed,omitempty"`
// FailedTarget names the gate target that stayed red (failure path only).
FailedTarget string `json:"failed_target,omitempty"`
ErrorCategory git.ErrorCategory `json:"error_category,omitempty"`
Error string `json:"error,omitempty"`
}
ResultOutput is the typed contract for the `## Result` JSON section the execution step writes. Round-trips with agentlib.MarshalSectionTyped + agentlib.ExtractSection[ResultOutput].
Shapes (design § 4.3):
- Outcome="opened" — draft PR created; Branch/PRURL populated, GateExit=0
- Outcome="adopted" — crash-window replay found the PR already open; adopted as-is
- Outcome="no_update_needed" — the update sequence produced no effective change (workdir diff empty or CHANGELOG.md-only) once MVS settled; no commit/push/PR, workdir discarded (go-skeleton PR #51 guard)
- Outcome="failed" — any failure; ErrorCategory + Error populated
Invariant (design § 4.4): Branch == "fix/update-go-" + ref[:7]; VulnsFixed ⊆ {v.ID | v ∈ Plan.Vulns, Action=fix}.
type ReviewChecks ¶
type ReviewChecks struct {
// PROpen — `gh pr view` reports state OPEN.
PROpen bool `json:"pr_open"`
// PRDraft — the raw isDraft value reported by `gh pr view`. The review
// compares this against the configured PR_TARGET (draft default, ready
// opt-in); a mismatch is surfaced in the notes and causes approval to
// fail. This field always reports what was observed — the match verdict
// lives in the approval flag and the notes, not here.
PRDraft bool `json:"pr_draft"`
// GateGreen — every planned gate target re-ran to exit 0 on a fresh
// worktree at the branch.
GateGreen bool `json:"gate_green"`
// VulnsClear — the scanner-bearing gate targets came back green on the
// re-run (the gate IS the vuln verdict, per design § 7.3 the repo's
// gate wraps govulncheck/osv-scanner/trivy).
VulnsClear bool `json:"vulns_clear"`
// ChangelogUnreleased — CHANGELOG.md carries a bullet under
// ## Unreleased and introduces no new version header vs master.
ChangelogUnreleased bool `json:"changelog_unreleased"`
// NoNewTag — `git ls-remote --tags` shows no tag pointing at any
// branch commit.
NoNewTag bool `json:"no_new_tag"`
}
ReviewChecks holds the boolean verification results of the pure-Go ai_review step. Every field is derived from independent re-execution — never copied from ## Result (design § 4.4).
type ReviewOutput ¶
type ReviewOutput struct {
Approved bool `json:"approved"`
Checks ReviewChecks `json:"checks"`
Notes string `json:"notes"`
}
ReviewOutput is the typed contract for the `## Review` JSON section the ai_review step writes. Round-trips with agentlib.MarshalSectionTyped + agentlib.ExtractSection[ReviewOutput].
type ScopeVerdict ¶ added in v0.3.0
type ScopeVerdict string
ScopeVerdict is the outcome of an installation-scope check.
const ( // ScopeAllowed — the repo is in the App installation's repository list. ScopeAllowed ScopeVerdict = "allowed" // ScopeDenied — the installation token definitively cannot access the repo. ScopeDenied ScopeVerdict = "denied" // ScopeUnknown — the check could not be performed (PAT fallback, API // error). Callers proceed; a real permission gap still fails at push. ScopeUnknown ScopeVerdict = "unknown" )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package factory wires concrete dependencies for the github-update-go-agent binary.
|
Package factory wires concrete dependencies for the github-update-go-agent binary. |
|
Package git wraps git shell-outs behind the GitOps interface so the execution and ai_review steps can be tested with a counterfeiter mock.
|
Package git wraps git shell-outs behind the GitOps interface so the execution and ai_review steps can be tested with a counterfeiter mock. |
|
Package githubauth resolves the github-update-go agent's effective GitHub credential at startup.
|
Package githubauth resolves the github-update-go agent's effective GitHub credential at startup. |
|
Package prompts provides embedded prompt modules for the Claude-backed phases: planning (inspect + classify) and execution (update + repair).
|
Package prompts provides embedded prompt modules for the Claude-backed phases: planning (inspect + classify) and execution (update + repair). |