Documentation
¶
Overview ¶
Package orch is the PR-AF review orchestrator — the Go port of src/pr_af/orchestrator.py. It coordinates the multi-phase review pipeline: intake → anatomy → meta-selectors → review+layer (streaming) → coverage ‖ consistency → synthesis → merge-gate → output. It owns the HITL revision loop, the streaming producer/consumer channel, the order-preserving fan-outs, the (inert) wall-clock budget gate, and the byte-exact Markdown output builders.
Concurrency parity: Python's asyncio is cooperative single-threaded, so its shared orchestrator state needs no locking. Go runs the fan-outs on real goroutines, so every field mutated from a parallel closure (agentInvocations, totalCostUSD, costBreakdown, the adversary/cross-ref counters, budgetExhausted) is guarded by o.mu.
Index ¶
- Variables
- func ExtractPRNumber(prURL string) (int, bool)
- func IsBudgetExhausted(err error) bool
- func PartialReviewPayload(review schemas.GitHubReview, body string) schemas.GitHubReview
- func PostReview(ctx context.Context, gh github.Client, pr schemas.GitHubPRData, ...) error
- func PostReviewEvent(ctx context.Context, gh github.Client, pr schemas.GitHubPRData, ...) (string, error)
- func ResolveRepo(ctx context.Context, access Access, repoPath, prURL string) (string, error)
- type Access
- type App
- type Deps
- type LocalCaller
- type Orchestrator
Constants ¶
This section is empty.
Variables ¶
var ErrBadInput = errors.New("bad input")
ErrBadInput is the sentinel wrapping every ValueError-class failure Python's review() maps to HTTP 400: the "One of pr_url, diff_text, or repo_path is required" guard and the _compute_repo_diff failure. The node layer (T4.2) maps errors.Is(err, ErrBadInput) to 400 and everything else to 500 with the "review execution failed: " prefix.
Functions ¶
func ExtractPRNumber ¶
ExtractPRNumber ports app.py::_extract_pr_number: the integer following "/pull/" in a github.com URL, or (0, false) when absent/unparsable.
func IsBudgetExhausted ¶
IsBudgetExhausted reports whether err is the pipeline stopping at its cost or wall-clock cap before a phase could start. The node maps it to a 500 like any other failure; a caller that words endings by cause (the delegate) reads it as "a ceiling was reached", not "the program broke".
func PartialReviewPayload ¶
func PartialReviewPayload(review schemas.GitHubReview, body string) schemas.GitHubReview
PartialReviewPayload keeps findings available without a clean merge claim. Its body is built from completed work by the host that knows what stopped.
func PostReview ¶
func PostReview(ctx context.Context, gh github.Client, pr schemas.GitHubPRData, review schemas.GitHubReview) error
PostReview posts review on the pull request pr names, against the commit that was reviewed (pr.HeadSHA), downgrading a 422 "own pull request" REQUEST_CHANGES to a COMMENT and retrying once (orchestrator.py's httpx handling). internal/praf's post command calls it with a saved review, on the person's yes. It answers the event that was posted.
func PostReviewEvent ¶
func PostReviewEvent(ctx context.Context, gh github.Client, pr schemas.GitHubPRData, review schemas.GitHubReview) (string, error)
PostReviewEvent is PostReview, answering the event GitHub took: the review's own, or COMMENT when GitHub refused a request for changes on the author's own pull request.
func ResolveRepo ¶
ResolveRepo ports app.py::_resolve_repo. It resolves repoPath / prURL to a local directory: an existing dir is returned as-is (resolved absolute), and an http(s)/git@ URL is (shallow) cloned into access.Workdir, with the PR branch checked out when a PR number is known. Anything else is refused: pr-af fell back to $PR_AF_REPO_PATH or its working directory.
The workspace is keyed by repo AND PR number when one is known (<repoName>-pr<N>): the shared per-repo dir re-pointed its pr-review branch on every review, so two concurrent reviews of different PRs of the same repo silently reviewed the wrong checkout. Plain <repoName> is kept when no PR number is known (repo_path / diff_text flows), preserving the old layout.
Empty strings stand in for Python's None. Errors mirror Python's ValueError ("git clone failed: …", plus the checkout strings via checkoutPRBranch).
Types ¶
type Access ¶
Access is what a review needs to reach a repository: the folder its checkouts go in, and the GitHub token for a private one (empty for none).
type App ¶
App is the agent capability surface the orchestrator depends on (appx.App), the single seam every sub-package's narrower interface is fed from: harnessx.HarnessCaller (Harness), reasoners.AICaller / gates.AICaller (AI), and the progress notes (Note). pr-af's served node also paused here for a person's approval before posting; inside codeaf a review never posts from the pipeline (internal/praf's post command does, on the person's yes), so that verb is gone.
type Deps ¶
type Deps struct {
App App
GH github.Client
NodeID string
// Local, when non-nil, routes every phase invocation through
// LocalCaller.CallLocal so the control plane records one child execution
// per phase (the pipeline DAG). nil falls back to plain in-process
// function calls — the pre-DAG behavior unit tests and stub harnesses
// rely on. Production (node/register.go) always wires the live agent.
Local LocalCaller
// SpentUSD, when non-nil, reports what the run has really spent so far,
// metered by the caller from the harness and AI results. The reasoner
// returns never carry cost_usd, so without it the cost cap is inert (the
// node's long-standing behavior, kept when this is nil); with it the cost
// cap trips on real spend and the review degrades the way the wall-clock
// cap already makes it, keeping what it has. The one-shot delegate wires it.
SpentUSD func() float64
}
Deps carries the injected capabilities. Divergence from design §C.6: the hax client is NOT a Deps field — Python builds it inside run() via build_hax_client_from_env (gated on pr_url && !dry_run), so the orchestrator mirrors that through the buildHaxClient seam. GH is the GitHub client interface (Python constructs GitHubClient() inline; Go injects it so tests can stub fetch/post).
type LocalCaller ¶
type LocalCaller interface {
CallLocal(ctx context.Context, reasonerName string, input map[string]any) (any, error)
}
LocalCaller is the SDK surface for same-process reasoner invocation with workflow tracking (Agent.CallLocal): each call builds a child execution context from ctx and emits running/succeeded/failed events to the control plane, so every pipeline phase renders as its own node in the run's DAG — the same orchestration graph the Python port produces via its tracked router-reasoner calls. Python's @router.reasoner() wrapper routes direct in-process calls through workflow instrumentation; CallLocal is the Go SDK's equivalent, and routing the phase seams through it is what keeps the Go node from collapsing into a single opaque `review` execution.
type Orchestrator ¶
type Orchestrator struct {
// contains filtered or unexported fields
}
Orchestrator holds one review's state and seams. New wires the default (production) seams; tests override individual fields to stub phases.
func New ¶
func New(d Deps, in schemas.ReviewInput, cfg config.ReviewConfig) *Orchestrator
New constructs an Orchestrator with production seams. startedAt is set now so the wall-clock budget gate measures from construction (parity with Python's time.monotonic() in __init__).
func (*Orchestrator) PRData ¶
func (o *Orchestrator) PRData() *schemas.GitHubPRData
PRData is the pull request the review fetched, nil before intake or for a review of a diff with no pull request. internal/praf keeps it beside the saved review so a later post lands on the commit that was reviewed.
func (*Orchestrator) Run ¶
func (o *Orchestrator) Run(ctx context.Context) (schemas.ReviewResult, error)
Run executes the pipeline once (orchestrator.py run(), without its HITL revision loop). ErrBadInput-wrapped errors mark a request the review could not take; anything else is an internal failure.
THE REVIEW POSTS ONLY WHEN IT IS NOT A DRY RUN AND NAMES A PULL REQUEST. pr-af's served node put a person's approval between the review and the post; inside codeaf every review runs dry and the post is its own step on the person's yes (internal/praf's post command), so the loop that waited for that approval is gone.