github

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package github is the typed adapter between yottacode and GitHub.

Shipped surface (v0.5.0):

  • Typed client (TypedClient) wrapping go-github/v66, with HTTPClient injection for tests.
  • Auth resolver chain: $GITHUB_TOKEN → `gh auth token` → ~/.yottacode/github.json.
  • PR surface: CreatePR, ReadPR, ReadPRDiff, ListPRChecks, ListFailedWorkflowJobLogTails, RerunFailedPRChecks, UpdatePR.
  • Issue surface: ReadIssue, ListOpenIssues.
  • PR comment surface: AddPRComment.

Callers depend on the Interface, not on TypedClient directly, so the cloud bot (SaaS Phase 2) can swap in a JWT-installation-token implementation behind the same surface without changing call sites.

Index

Constants

This section is empty.

Variables

View Source
var ErrGitHubUnavailable = errors.New("gh CLI unavailable or unauthenticated")

ErrGitHubUnavailable signals that the local environment cannot satisfy a GitHub call — either the `gh` binary isn't installed, or it is installed but unauthenticated. Callers branch on this so the procedural /create-pr can fall through to a draft-only preview instead of failing the turn opaquely.

View Source
var ErrGitHubUnreachable = errors.New("github API unreachable")

ErrGitHubUnreachable signals that the network couldn't reach api.github.com — DNS failure, refused connection, TLS error, timeout. Distinct from ErrGitHubUnavailable (auth) and ErrPRNotFound (logical) so callers can branch on the right recovery — auth failures want `gh auth login`, unreachable wants "check your network".

Surface bubbled up from net.OpError / *url.Error / net DNSError shapes returned by go-github. Tested in cache_test.go and typed_test.go.

View Source
var ErrIssueNotFound = errors.New("issue not found")

ErrIssueNotFound signals that the requested issue doesn't exist. Sibling of ErrPRNotFound — same semantic, different resource. Callers branch on this so /git-implement-issue can surface a clean "no issue found" and STOP instead of treating the missing issue as an opaque API failure.

View Source
var ErrNoToken = errors.New("no GitHub auth token configured (set $GITHUB_TOKEN, run `gh auth login`, or run `yottacode setup github`)")

ErrNoToken signals that the auth chain found no usable token. Distinct from ErrGitHubUnavailable: gh might be installed and authed but not on PATH (env var path), or vice versa.

View Source
var ErrPRNotFound = errors.New("pull request not found")

ErrPRNotFound signals that the requested PR doesn't exist (no open PR for the supplied branch, or the explicit number resolves to a missing PR). Callers branch on this to surface a clean "no PR found" instead of treating the missing PR as an opaque gh exit-non-zero.

Functions

func DetectRepo

func DetectRepo(ctx context.Context, cwd string) (owner, repo string, err error)

DetectRepo reads the cwd's `origin` remote URL and parses it into (owner, repo). Returns an error when no remote is configured, the remote isn't a GitHub URL, or the URL shape doesn't match the expected forms.

Specifically targets `origin` (not other remotes) because that's the convention every gh / git workflow assumes. Users with multi-remote setups can pass owner/repo explicitly via the request types, which short-circuits this lookup.

func IsGhAvailable

func IsGhAvailable(ctx context.Context) bool

IsGhAvailable reports whether the auth chain can resolve a usable token from any tier. The historical name is preserved for caller compatibility (the pr_context tool exposes a "GhAvailable" flag in its snapshot), but with the typed client in place the check is provider-agnostic: any of the three auth tiers ($GITHUB_TOKEN, gh, file) satisfying counts.

Cheap: doesn't make a GitHub API call. Returns false when every tier misses — same semantics as the old gh-only check but no longer dependent on gh specifically being installed.

func ParseRemoteURL

func ParseRemoteURL(remote string) (owner, repo string, err error)

ParseRemoteURL is the pure-function core of DetectRepo. Tries each known URL shape; returns the first match. Exposed for testability — callers shouldn't need to invoke it directly.

func RemoveTokenFile

func RemoveTokenFile() (path string, existed bool, err error)

RemoveTokenFile deletes the on-disk token. Returns the path removed and a bool indicating whether the file existed pre-call (so callers can render "Already empty" vs "Removed" distinctly). Missing-file is NOT an error — idempotent by design.

func SaveVerifiedToken

func SaveVerifiedToken(ctx context.Context, token string) (path string, login string, err error)

SaveVerifiedToken is the setup wizard's atomic "verify-then-persist" entry point. Doesn't write anything on verify failure — keeping the on-disk state consistent across failed setup attempts. Returns the on-disk path on success so the caller can surface it ("Saved to <path>").

func WriteTokenFile

func WriteTokenFile(token, user string) (string, error)

WriteTokenFile persists a token to the canonical location (~/.yottacode/github.json). Atomic via temp-file + rename so a crash mid-write never leaves a half-written file. Mode 0600 on the file, 0700 on the parent dir — the same posture other yottacode secret stores use (auth/openai-auth.json, etc.).

Caller passes the verified token + the GitHub login the token authenticated as (for the User field). Created is stamped here so callers don't have to remember.

Types

type AddPRCommentRequest

type AddPRCommentRequest struct {
	Owner string
	Repo  string
	Ref   string
	Body  string
}

AddPRCommentRequest is the typed payload for Interface.AddPRComment. Owner / Repo follow the same optional-inference semantics. Ref accepts a PR number or branch name, mirroring ReadPRRequest. Body is the Markdown source — required, non-empty.

type AddPRCommentResult

type AddPRCommentResult struct {
	URL string
	ID  int64
}

AddPRCommentResult is the typed envelope AddPRComment returns on success. URL is the comment's permalink — callers surface it so the user can jump to the posted comment on GitHub. ID is the comment's numeric ID, useful for future edit/delete flows (out of scope for v0.5.0 but exposed now to avoid a breaking change later).

type CacheStats

type CacheStats struct {
	PRs        int
	PRDiffs    int
	Checks     int
	Issues     int
	IssueLists int
}

Stats reports per-method cache occupancy. Useful for debugging and for the eventual doctor probe — we want to see whether the cache is actually hot during a session.

type CachingClient

type CachingClient struct {
	Inner Interface
	// contains filtered or unexported fields
}

CachingClient is a memoizing wrapper around any Interface. Reads are cached for the lifetime of the wrapper (which the runtime holds for the session). Writes pass through and invalidate matching read entries so the next read sees fresh state.

Cache shape is deliberately simple: per-method maps keyed by request fingerprints. No TTL — the runtime tears the wrapper down at session end, which is the only invalidation event the roadmap commits to.

Thread-safe via a single mutex. Sessions can have parallel tool calls in flight (the agent runs read tools concurrently when they're parallel-safe), so locking matters. The mutex is held only across map mutation, not across the Inner call — a cache miss releases the lock, makes the call, then re-acquires to store. Two concurrent misses on the same key make two calls; that's the cost of not holding the lock across the network. The double-fetch is rare in practice (the agent dispatches reads once per turn) and avoiding it would require per-key singleflight, which is more machinery than the current signal warrants.

func NewCachingClient

func NewCachingClient(inner Interface) *CachingClient

NewCachingClient wraps the supplied Interface with read caches. Returns a *CachingClient (not Interface) so wiring sites can reach the Stats / Reset methods for debugging without a type assertion. The struct satisfies Interface.

func (*CachingClient) AddPRComment

AddPRComment is a write — passes through. Doesn't invalidate any cached read: comments aren't carried in PRDetails (only the PR body is), so the next ReadPR is still accurate. If a future PRDetails grows a Comments field, this method must learn to evict matching ReadPR entries; the test suite pins that expectation so the regression is caught.

func (*CachingClient) CreateIssue

CreateIssue is a write — passes through, then drops the cached issue lists: the new issue belongs in any matching filter's results, so every cached list is now stale. Per-issue reads stay cached — an issue that existed before the create is unchanged by it.

func (*CachingClient) CreatePR

CreatePR is a write — passes through and invalidates nothing. A newly opened PR didn't exist before so there's no stale entry to evict. The PR's URL / number become known after this call, but the next ReadPR will populate the cache fresh.

func (*CachingClient) ListFailedWorkflowJobLogTails added in v0.4.0

func (c *CachingClient) ListFailedWorkflowJobLogTails(ctx context.Context, req FailedWorkflowLogsRequest) (FailedWorkflowLogsResult, error)

ListFailedWorkflowJobLogTails is intentionally not cached. Logs and failed-job state are fetched only after CI reports a failure, and a rerun can update that state while the head SHA stays stable.

func (*CachingClient) ListOpenIssues

func (c *CachingClient) ListOpenIssues(ctx context.Context, req ListIssuesRequest) ([]IssueSummary, error)

ListOpenIssues is cached by the full filter fingerprint — labels (order-independent), assignee, milestone, owner/repo. Two model calls with the same filter share one API request.

func (*CachingClient) ListPRChecks

func (c *CachingClient) ListPRChecks(ctx context.Context, req ReadPRRequest) ([]CheckRun, error)

ListPRChecks is intentionally NOT cached. Check runs transition from queued/in-progress to completed without a PR metadata or head-SHA change, and users often ask repeatedly whether CI has passed. Serving a session- cached pending result would make yottacode lie until restart, so every call goes to the inner client for a live CI snapshot.

func (*CachingClient) RateLimit

func (c *CachingClient) RateLimit() RateLimitSnapshot

RateLimit passes through to the inner. Cache layer has no independent rate state — the inner is what makes the API calls.

func (*CachingClient) ReadIssue

func (c *CachingClient) ReadIssue(ctx context.Context, req ReadIssueRequest) (IssueDetails, error)

ReadIssue is cached by (owner, repo, number, maxComments). Comments are part of the cached value, so the maxComments element of the key matters (different caps produce different result shapes).

func (*CachingClient) ReadPR

func (c *CachingClient) ReadPR(ctx context.Context, req ReadPRRequest) (PRDetails, error)

ReadPR is cached by (owner, repo, ref). Cache miss falls through to Inner; success populates the entry. Errors (including the typed sentinels) are NOT cached — re-fetching after a transient failure is the right behavior, and caching ErrPRNotFound would make subsequent retries impossible without a Reset.

func (*CachingClient) ReadPRDiff

func (c *CachingClient) ReadPRDiff(ctx context.Context, req ReadPRRequest) (string, error)

ReadPRDiff is cached by the same key shape as ReadPR. Diffs don't change without a new commit, and the cache lives only for the session — so a long diff fetched once carries through any follow-up reads in the same turn.

func (*CachingClient) RerunFailedPRChecks added in v0.4.0

func (c *CachingClient) RerunFailedPRChecks(ctx context.Context, req ReadPRRequest) (RerunFailedPRChecksResult, error)

RerunFailedPRChecks is a write — passes through without caching. Check snapshots and log tails are uncached, so no explicit invalidation is needed.

func (*CachingClient) Reset

func (c *CachingClient) Reset()

Reset drops every cached entry. Not used during normal session lifecycle (the cache lives for the session); exposed for tests and for a future "force fresh fetch" debug command.

func (*CachingClient) Stats

func (c *CachingClient) Stats() CacheStats

func (*CachingClient) UpdatePR

UpdatePR rewrites title/body — the next ReadPR must see fresh data, so we evict the matching ReadPR entry before passing through. Diff is unaffected (the head SHA hasn't moved), so that cache stays. Checks are not cached because CI state changes while a head SHA is stable.

type CheckRun

type CheckRun struct {
	Name        string
	State       string
	Conclusion  string
	StartedAt   time.Time
	CompletedAt time.Time
}

CheckRun is one row from ListPRChecks. Name is the check's label (e.g. "build", "test", "lint"). State is the lifecycle state ("QUEUED" / "IN_PROGRESS" / "COMPLETED"). Conclusion is the outcome once State is COMPLETED ("SUCCESS" / "FAILURE" / "CANCELLED" / "NEUTRAL" / "SKIPPED" / "TIMED_OUT" / "ACTION_REQUIRED"); empty before completion.

Times are zero-valued when the check hasn't started or completed yet — callers must IsZero-check before formatting.

type CreateIssueRequest

type CreateIssueRequest struct {
	Owner     string   // repo owner (optional; inferred from cwd when empty)
	Repo      string   // repo name  (optional; inferred from cwd when empty)
	Title     string   // issue title (required)
	Body      string   // issue body / description (optional)
	Labels    []string // labels to apply (optional)
	Assignees []string // assignees to assign (optional)
}

CreateIssueRequest is the typed payload for Interface.CreateIssue.

Owner and Repo are optional: when both are empty, the underlying implementation infers them from the working directory's git remote (the gh CLI's default behavior). Setting them explicitly is what the future cloud bot will need (it can't rely on cwd) and the local CLI can always use it for cross-repo cases.

type CreateIssueResult

type CreateIssueResult struct {
	URL    string
	Number int
}

CreateIssueResult is the typed envelope CreateIssue returns on success. Number is the GitHub issue number. URL is the canonical https://github.com/... issue URL.

type CreatePRRequest

type CreatePRRequest struct {
	Owner string // repo owner (optional; inferred from cwd when empty)
	Repo  string // repo name  (optional; inferred from cwd when empty)
	Base  string // base branch the PR merges into (required)
	Head  string // head branch / SHA the PR ships (optional; "" = current branch)
	Title string // PR title (required)
	Body  string // PR body / description (required)
	Draft bool   // open as draft (default: open as ready-for-review)
}

CreatePRRequest is the typed payload for Interface.CreatePR.

Owner and Repo are optional: when both are empty, the underlying implementation infers them from the working directory's git remote (the gh CLI's default behavior). Setting them explicitly is what the future cloud bot will need (it can't rely on cwd) and the local CLI can always use it for cross-repo cases.

type CreatePRResult

type CreatePRResult struct {
	URL    string
	Number int
}

CreatePRResult is the typed envelope CreatePR returns on success. Number is the GitHub PR number (best-effort: shellout impl returns 0 when it can't parse one; typed v0.5.0 impl will always populate it). URL is the canonical https://github.com/... PR URL.

type FailedWorkflowJobLog added in v0.4.0

type FailedWorkflowJobLog struct {
	WorkflowRunID int64
	WorkflowName  string
	WorkflowURL   string
	JobID         int64
	JobName       string
	JobURL        string
	Conclusion    string
	LogTail       []string
	LogError      string
}

FailedWorkflowJobLog is one failed GitHub Actions job plus the capped tail of its log. WorkflowName and JobName are separate so a renderer can group or display either without parsing check labels.

type FailedWorkflowLogsRequest added in v0.4.0

type FailedWorkflowLogsRequest struct {
	Owner     string // repo owner (optional; inferred from cwd when empty)
	Repo      string // repo name  (optional; inferred from cwd when empty)
	HeadSHA   string // commit SHA whose workflow runs should be inspected (required)
	TailLines int    // max lines per failed job; <=0 uses implementation default
	MaxRuns   int    // max workflow runs to inspect; <=0 uses implementation default
}

FailedWorkflowLogsRequest selects the failed GitHub Actions logs to fetch for a single PR head SHA. Owner / Repo follow the same optional inference semantics as the other GitHub request types.

type FailedWorkflowLogsResult added in v0.4.0

type FailedWorkflowLogsResult struct {
	HeadSHA string
	Jobs    []FailedWorkflowJobLog
}

FailedWorkflowLogsResult is the typed envelope for failed job log tails. Missing logs are reported per job via LogError rather than as a hard failure so callers still see which checks failed.

type Interface

type Interface interface {
	// CreatePR opens a pull request. Returns ErrGitHubUnavailable when
	// the local environment can't make the call (no gh, no auth)
	// so callers can fall back gracefully rather than reporting a
	// generic execution failure.
	CreatePR(ctx context.Context, req CreatePRRequest) (CreatePRResult, error)

	// ReadPR fetches typed metadata about a single pull request.
	// Ref accepts either a PR number ("17") or a branch name; gh
	// itself accepts both, and the v0.5.0 typed client will mirror
	// that ergonomics. Returns ErrPRNotFound when nothing matches.
	ReadPR(ctx context.Context, req ReadPRRequest) (PRDetails, error)

	// ReadPRDiff fetches the unified diff for a pull request as a
	// single string. Capped by the caller (tool wrapper trims for
	// model consumption); the Interface itself returns the full diff.
	ReadPRDiff(ctx context.Context, req ReadPRRequest) (string, error)

	// ListPRChecks returns the typed status of every check run on a
	// PR. Empty slice is a valid result (PR with no CI). The
	// `/git-review-pr` flow surfaces failing checks at the top of
	// the review, which is why typed access matters here.
	ListPRChecks(ctx context.Context, req ReadPRRequest) ([]CheckRun, error)

	// ListFailedWorkflowJobLogTails returns capped log tails for failed
	// GitHub Actions jobs on a specific head SHA. This is the typed
	// read-only counterpart to `gh run view --log-failed | tail`, used
	// only after checks report a failure.
	ListFailedWorkflowJobLogTails(ctx context.Context, req FailedWorkflowLogsRequest) (FailedWorkflowLogsResult, error)

	// RerunFailedPRChecks re-runs failed GitHub Actions jobs for every
	// failed workflow run attached to a PR's current head SHA. It uses
	// GitHub's failed-jobs endpoint rather than rerunning successful jobs.
	RerunFailedPRChecks(ctx context.Context, req ReadPRRequest) (RerunFailedPRChecksResult, error)

	// UpdatePR rewrites an existing PR's title and body. Used by
	// /git-update-pr after follow-up commits make the original
	// description stale. Other PR-level edits (labels, base,
	// reviewers, draft toggle) are intentionally out of scope —
	// the v0.5.0 spec defers them until a concrete workflow asks.
	// Returns ErrPRNotFound when nothing matches the ref;
	// ErrGitHubUnavailable when the local environment can't make the
	// call.
	UpdatePR(ctx context.Context, req UpdatePRRequest) (UpdatePRResult, error)

	// ReadIssue fetches typed metadata about a single issue —
	// title, body, state, labels, assignees, and the most-recent
	// comments. Powers /git-implement-issue's first step. Returns
	// ErrIssueNotFound when the issue doesn't exist.
	ReadIssue(ctx context.Context, req ReadIssueRequest) (IssueDetails, error)

	// ListOpenIssues returns lightweight summaries of open issues
	// matching the supplied filters. Empty filters return all open
	// issues (paginated by GitHub's defaults — we don't fetch all
	// pages here; callers that need pagination ask for it).
	// Surfaces issues for /git-implement-issue tab-completion and
	// for future planning surfaces.
	ListOpenIssues(ctx context.Context, req ListIssuesRequest) ([]IssueSummary, error)

	// AddPRComment posts a top-level conversation comment on a PR.
	// Approval-gated by the tool layer (see GHPRAddCommentTool).
	// Used to cross-link related issues, request reviewers via
	// @-mention, or post structured follow-ups after a /git-review-pr
	// run. Not for inline review comments (different endpoint;
	// different scope).
	AddPRComment(ctx context.Context, req AddPRCommentRequest) (AddPRCommentResult, error)

	// CreateIssue opens a new issue. Returns ErrGitHubUnavailable when
	// the local environment can't make the call (no gh, no auth)
	// so callers can fall back gracefully rather than reporting a
	// generic execution failure.
	CreateIssue(ctx context.Context, req CreateIssueRequest) (CreateIssueResult, error)

	// RateLimit returns the most recent rate-limit snapshot the
	// implementation has observed. Snapshot.IsSet() is false when
	// no API call has populated the tracker yet. Used by the doctor
	// probe and by mutation tools to surface low-budget warnings.
	// Implementations that don't track rate state (test doubles)
	// may return a zero-valued snapshot.
	RateLimit() RateLimitSnapshot
}

Interface is the typed surface yottacode uses to talk to GitHub. Kept minimal: only the methods at least one shipped caller needs. Growing it as new commands need new endpoints (rather than front-loading the entire `go-github` surface) keeps the test burden bounded — each method we add ships with at least one caller that exercises it.

type IssueComment

type IssueComment struct {
	Author  string
	Body    string
	Created time.Time
}

IssueComment is one row from IssueDetails.Comments. Body is the raw Markdown source the commenter wrote. Author is the login. Created is the UTC creation timestamp.

type IssueDetails

type IssueDetails struct {
	Number    int
	Title     string
	Body      string
	State     string // "OPEN" | "CLOSED"
	Author    string
	URL       string
	Labels    []string
	Assignees []string
	Comments  []IssueComment
}

IssueDetails is the typed envelope ReadIssue returns. Field shape mirrors PRDetails where it overlaps (Number, Title, Body, State, Author, URL, Labels) so the model brief and tool wrappers can render issues and PRs with shared template code.

Assignees is the login list (not display names) — matches the Author convention. Comments is most-recent-first, capped per MaxComments. Empty Comments doesn't mean "no comments existed" when MaxComments was negative; check the request to disambiguate.

type IssueSummary

type IssueSummary struct {
	Number    int
	Title     string
	Author    string
	URL       string
	Labels    []string
	Assignees []string
}

IssueSummary is the lightweight envelope ListOpenIssues returns. Subset of IssueDetails — enough for a picker / completion list. Callers that need bodies + comments follow up with ReadIssue.

type ListIssuesRequest

type ListIssuesRequest struct {
	Owner     string
	Repo      string
	Labels    []string
	Assignee  string
	Milestone string
}

ListIssuesRequest is the typed payload for Interface.ListOpenIssues. Owner / Repo follow the same optional-inference semantics as the other request types. Filters are AND-ed — e.g., Labels=["bug"] and Assignee="octocat" returns only issues with both.

Empty Labels means "no label filter" (any label OK, no label OK). Empty Assignee / Milestone means "no filter on that axis". State is fixed to "open" by the method semantics — callers that need closed issues use ReadIssue with a specific number.

type PRDetails

type PRDetails struct {
	Number    int
	Title     string
	Body      string
	State     string // "OPEN" | "CLOSED" | "MERGED"
	Draft     bool
	BaseRef   string
	HeadRef   string
	HeadSHA   string
	Mergeable string // "MERGEABLE" | "CONFLICTING" | "UNKNOWN"
	Author    string // login, not display name
	URL       string
	Labels    []string
}

PRDetails is the typed envelope ReadPR returns. Fields mirror the `gh pr view --json` schema yottacode needs today; growing it as callers ask for more fields keeps the contract surface pinned to actual use rather than front-loading the entire API.

State and Mergeable are uppercase strings matching the GitHub API's enum literals (OPEN / CLOSED / MERGED, MERGEABLE / CONFLICTING / UNKNOWN) so downstream pattern-matching against the wire format stays unambiguous.

type RateLimitSnapshot

type RateLimitSnapshot struct {
	Limit       int
	Remaining   int
	Reset       time.Time
	LastUpdated time.Time
}

RateLimitSnapshot is the typed envelope for GitHub's per-hour REST rate-limit budget. Returned by TypedClient.RateLimit after at least one API call has populated the tracker. Limit is the hourly cap (5000 for authenticated users); Remaining is what's left; Reset is when the budget refills.

Zero-value snapshot (LastUpdated.IsZero) means "no API call has populated this tracker yet" — callers should not infer anything from Remaining=0 unless LastUpdated is non-zero.

func (RateLimitSnapshot) IsLow

func (s RateLimitSnapshot) IsLow() bool

IsLow reports whether the remaining budget is at or below the soft-warn threshold (100). Matches the roadmap's "soft-warn at ≤ 100 remaining" target. Callers use this to decide whether to surface a warning to the user.

func (RateLimitSnapshot) IsSet

func (s RateLimitSnapshot) IsSet() bool

IsSet reports whether the snapshot reflects at least one observed API call. Callers branch on this before inferring anything from the other fields.

func (RateLimitSnapshot) WarningText

func (s RateLimitSnapshot) WarningText() string

WarningText is the canonical scrollback message for a low rate-limit state. Empty when the snapshot doesn't warrant a warning. Tools call this after a mutation and append the non-empty result to their output envelope so the user sees the budget pressure inline.

type ReadIssueRequest

type ReadIssueRequest struct {
	Owner       string
	Repo        string
	Number      int
	MaxComments int
}

ReadIssueRequest is the typed payload for Interface.ReadIssue. Owner / Repo follow the same optional-inference semantics as the PR request types. Number is required (issues don't have a branch fallback the way PRs do).

MaxComments caps the comment fetch — issues can be lengthy threads, and the model rarely needs every comment for context. Zero means "use the implementation's default" (currently 20 most-recent comments). Negative means "skip comments entirely".

type ReadPRRequest

type ReadPRRequest struct {
	Owner string
	Repo  string
	Ref   string // PR number or branch name; "" = current branch
}

ReadPRRequest is the typed payload for the read trio (ReadPR, ReadPRDiff, ListPRChecks). Owner / Repo follow the same optional-inference semantics as CreatePRRequest.

Ref is the PR identifier — either a number ("17") or a branch name ("feature/x"). Empty Ref tells the implementation to use the cwd's current branch, mirroring `gh pr view` with no arg.

type RerunFailedPRChecksResult added in v0.4.0

type RerunFailedPRChecksResult struct {
	RunIDs []int64
	Count  int
}

RerunFailedPRChecksResult reports which workflow runs were asked to rerun failed jobs. The endpoint is asynchronous; success means GitHub accepted the rerun request, not that CI has passed.

type TokenFile

type TokenFile struct {
	Token   string `json:"token"`
	User    string `json:"user,omitempty"`
	Created string `json:"created,omitempty"` // RFC3339
}

TokenFile is the on-disk shape WriteTokenFile produces and tokenFromFile reads. Token is the only field the resolver uses; User and Created are metadata for `yottacode doctor` and future audit/UX surfaces.

type TokenResolver

type TokenResolver struct {
	// contains filtered or unexported fields
}

TokenResolver caches the resolved token for a process. Safe for concurrent use — the underlying sync.Once + struct fields are immutable after the first successful resolve.

func NewTokenResolver

func NewTokenResolver() *TokenResolver

NewTokenResolver returns a fresh resolver. Each ShellOut / TypedClient instance carries its own so they don't share cached tokens across (theoretical) multi-tenant runs in the same process.

func (*TokenResolver) Resolve

func (r *TokenResolver) Resolve(ctx context.Context) (token, source string, err error)

Resolve runs the precedence chain. First call does the work; subsequent calls return the cached result (success or error). Returns (token, source, err) where source identifies which tier won — useful for `yottacode doctor` and for tests.

type TypedClient

type TypedClient struct {
	Cwd      string
	Resolver *TokenResolver

	// HTTPClient is an optional injection point for tests. When
	// nil, the typed client uses go-github's default
	// http.Client. Tests provide a transport that returns
	// canned responses.
	HTTPClient *http.Client
	// contains filtered or unexported fields
}

TypedClient is the Interface implementation backed by the go-github typed REST client. Replaces the gh-CLI ShellOut implementation we shipped first.

Three reliability wins over ShellOut:

  1. **No gh CLI dependency.** The auth resolver still opportunistically piggybacks on `gh auth token` if it's available (one-shot at first use), but API calls go direct over HTTPS. Users can `apt remove gh` and yottacode still works if $GITHUB_TOKEN is set or the `~/.yottacode/github.json` file exists.
  2. **No GraphQL field deprecation traps.** ShellOut hit `repository.pullRequest.projectCards` deprecation through gh's internal query. The REST endpoints we use here don't reference projectCards.
  3. **Faster per call.** No subprocess spawn; in-process HTTP.

Cwd is used for inferring (owner, repo) when the caller's request doesn't supply them explicitly. Resolver and the underlying *gogithub.Client are lazy-initialized via sync.Once so the first call pays the auth cost and subsequent calls reuse the client.

func NewTypedClient

func NewTypedClient(cwd string) *TypedClient

NewTypedClient builds a TypedClient wired with a fresh TokenResolver. Callers can also construct the struct directly (e.g. tests injecting an HTTPClient).

func (*TypedClient) AddPRComment

AddPRComment posts a top-level conversation comment on the PR. PR comments at the conversation level go through the Issues API (PRs are issues at the API level) — this is the GitHub API shape, not a yottacode quirk.

func (*TypedClient) AuthedUserLogin

func (c *TypedClient) AuthedUserLogin(ctx context.Context) (string, error)

AuthedUserLogin returns the login of the user the current token authenticates as. Powers the doctor probe — the call doubles as a connectivity check (the underlying HTTPS hit) and as the auth-validity check (a bad token returns 401 → ErrGitHubUnavailable). Not on the Interface — probe-specific, not a workflow operation.

func (*TypedClient) CreateIssue

CreateIssue opens a new issue via REST.

func (*TypedClient) CreatePR

CreatePR opens a pull request via REST. Body is sent as the JSON `body` field directly — no shell quoting concerns.

func (*TypedClient) ListFailedWorkflowJobLogTails added in v0.4.0

func (c *TypedClient) ListFailedWorkflowJobLogTails(ctx context.Context, req FailedWorkflowLogsRequest) (FailedWorkflowLogsResult, error)

ListFailedWorkflowJobLogTails finds GitHub Actions runs for a head SHA, filters failed jobs, and returns only the requested tail lines for each job. This intentionally fetches job logs only after the caller has already seen a failed check; it is not part of the normal PR review snapshot path.

func (*TypedClient) ListOpenIssues

func (c *TypedClient) ListOpenIssues(ctx context.Context, req ListIssuesRequest) ([]IssueSummary, error)

ListOpenIssues returns lightweight summaries of open issues matching the supplied filters. Pagination is GitHub's default (30 per page, first page only) — callers that need more refine the filter rather than asking for unbounded scrolling.

func (*TypedClient) ListPRChecks

func (c *TypedClient) ListPRChecks(ctx context.Context, req ReadPRRequest) ([]CheckRun, error)

ListPRChecks combines check-run rollup (the newer Checks API) and legacy status contexts into a single typed list. gh's statusCheckRollup field on the GraphQL side fuses both; the REST API exposes them separately so we merge here.

func (*TypedClient) RateLimit

func (c *TypedClient) RateLimit() RateLimitSnapshot

RateLimit returns the most recent rate-limit snapshot the typed client has observed. Snapshot.IsSet() is false until at least one API call has populated the tracker. Used by the doctor probe to surface remaining budget and by mutation tools to attach a low-budget warning to their output.

func (*TypedClient) ReadIssue

func (c *TypedClient) ReadIssue(ctx context.Context, req ReadIssueRequest) (IssueDetails, error)

ReadIssue fetches issue metadata + the most-recent comments. MaxComments controls the comment fetch: 0 → default (20), negative → skip comments entirely, positive → cap at that many. Comments are returned most-recent-first.

func (*TypedClient) ReadPR

func (c *TypedClient) ReadPR(ctx context.Context, req ReadPRRequest) (PRDetails, error)

ReadPR fetches PR metadata. Ref accepts a PR number string or a branch name. Number form goes direct; branch form lists PRs filtered by head and picks the first open match.

func (*TypedClient) ReadPRDiff

func (c *TypedClient) ReadPRDiff(ctx context.Context, req ReadPRRequest) (string, error)

ReadPRDiff fetches the unified diff. go-github exposes raw media-type access via PullRequests.GetRaw with RawOptions{Type: Diff}.

func (*TypedClient) RerunFailedPRChecks added in v0.4.0

func (c *TypedClient) RerunFailedPRChecks(ctx context.Context, req ReadPRRequest) (RerunFailedPRChecksResult, error)

RerunFailedPRChecks asks GitHub to rerun failed jobs for every failed workflow run attached to the PR's current head SHA.

func (*TypedClient) UpdatePR

UpdatePR rewrites an existing PR's title and body. Other fields stay locked — the v1 scope is title + body only.

type UpdatePRRequest

type UpdatePRRequest struct {
	Owner string
	Repo  string
	Ref   string
	Title string
	Body  string
}

UpdatePRRequest is the typed payload for Interface.UpdatePR. Owner / Repo follow the same optional-inference semantics as the other request types. Ref accepts a PR number or branch name. Both Title and Body must be non-empty — empty Body would clobber the existing description, which is almost never what the caller wants and is easy to do by accident.

type UpdatePRResult

type UpdatePRResult struct {
	URL    string
	Number int
}

UpdatePRResult is the typed envelope UpdatePR returns on success. URL is the canonical PR URL (unchanged by an edit), surfaced so callers can re-link to the updated PR.

type VerifyResult

type VerifyResult struct {
	Login string
}

VerifyResult is the typed outcome of a token check. Login is the authenticated user's GitHub handle (so the setup wizard can display "Authenticated as @octocat" for sanity check). Returned only on success — failure modes return an error.

func VerifyToken

func VerifyToken(ctx context.Context, token string) (VerifyResult, error)

VerifyToken makes one cheap GitHub API call (`/user` endpoint via Users.Get with empty string) to confirm the token is valid and capture the authenticated identity. Used by the setup wizard before writing the token to disk so we never persist a bad token.

Returns a typed error distinguishing the common failure modes:

  • ErrGitHubUnavailable: token rejected (401) or auth otherwise failed.
  • Anything else: network failure or unexpected GitHub-side error, wrapped with context.

Jump to

Keyboard shortcuts

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