github

package
v1.5.2 Latest Latest
Warning

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

Go to latest
Published: Apr 16, 2026 License: GPL-3.0 Imports: 14 Imported by: 0

Documentation

Overview

Package github provides GitHub CLI integration for PR and issue operations.

It wraps the gh CLI binary (https://cli.github.com/) via os/exec to provide pull request creation, review, merge, and CI/CD status checking. All GitHub interactions are abstracted behind testable interfaces.

Index

Constants

View Source
const RegistryFileName = "github-spec-registry.json"

RegistryFileName is the JSON file that maps GitHub issues to SPEC IDs.

View Source
const RegistryVersion = "1.0.0"

RegistryVersion is the current schema version of the registry file.

Variables

View Source
var (
	// ErrGHNotFound indicates the gh CLI binary is not in PATH.
	ErrGHNotFound = errors.New("github: gh CLI not found")

	// ErrGHNotAuthenticated indicates gh is not authenticated.
	ErrGHNotAuthenticated = errors.New("github: gh not authenticated")

	// ErrPRNotFound indicates the specified pull request does not exist.
	ErrPRNotFound = errors.New("github: pull request not found")

	// ErrPRAlreadyExists indicates a PR already exists for this branch.
	ErrPRAlreadyExists = errors.New("github: pull request already exists for branch")

	// ErrMergeBlocked indicates one or more merge prerequisites are not met.
	ErrMergeBlocked = errors.New("github: merge blocked by unmet prerequisites")

	// ErrMergeConflict indicates the PR has merge conflicts.
	ErrMergeConflict = errors.New("github: merge conflicts detected")

	// ErrCIFailed indicates CI/CD checks failed.
	ErrCIFailed = errors.New("github: CI/CD checks failed")

	// ErrReviewRequired indicates PR review has not been approved.
	ErrReviewRequired = errors.New("github: review approval required")

	// ErrAutoMergeNotRequested indicates the --auto-merge flag was not specified.
	ErrAutoMergeNotRequested = errors.New("github: auto-merge not requested")

	// ErrIssueNotFound indicates the specified issue does not exist.
	ErrIssueNotFound = errors.New("github: issue not found")

	// ErrCommentFailed indicates a failure posting a comment to an issue.
	ErrCommentFailed = errors.New("github: failed to post comment")

	// ErrCloseFailed indicates a failure closing an issue.
	ErrCloseFailed = errors.New("github: failed to close issue")

	// ErrLabelFailed indicates a failure adding a label to an issue.
	ErrLabelFailed = errors.New("github: failed to add label")

	// ErrMaxRetriesExceeded indicates all retry attempts have been exhausted.
	ErrMaxRetriesExceeded = errors.New("github: maximum retries exceeded")

	// ErrMappingExists indicates the issue is already linked to a SPEC.
	ErrMappingExists = errors.New("github: issue already linked to a SPEC")

	// ErrMappingNotFound indicates no SPEC is linked to the issue.
	ErrMappingNotFound = errors.New("github: no SPEC linked to issue")

	// ErrNilGHClient indicates a nil GHClient was provided to a constructor.
	ErrNilGHClient = errors.New("github: GHClient must not be nil")

	// ErrNilQualityGate indicates a nil quality.Gate was provided to a constructor.
	ErrNilQualityGate = errors.New("github: quality gate must not be nil")

	// ErrNilReviewer indicates a nil PRReviewer was provided to a constructor.
	ErrNilReviewer = errors.New("github: PRReviewer must not be nil")
)

Sentinel errors for the github package.

Functions

func NewGHClient

func NewGHClient(root string) *ghClient

NewGHClient creates a new GitHub CLI client rooted at the given directory.

func NewPRMerger

func NewPRMerger(gh GHClient, reviewer PRReviewer, logger *slog.Logger) (*prMerger, error)

NewPRMerger creates a merger that checks prerequisites before merging. Returns an error if gh or reviewer is nil.

func NewPRReviewer

func NewPRReviewer(gh GHClient, qualityGate quality.Gate, logger *slog.Logger) (*prReviewer, error)

NewPRReviewer creates a PR reviewer that validates quality gates and CI status. Returns an error if gh or qualityGate is nil.

Types

type Author

type Author struct {
	Login string `json:"login"`
}

Author represents a GitHub user.

type Check

type Check struct {
	Name       string `json:"name"`
	Status     string `json:"status"`
	Conclusion string `json:"conclusion"`
}

Check represents a single CI/CD status check.

type CheckConclusion

type CheckConclusion string

CheckConclusion represents the overall CI/CD check result.

const (
	// CheckPass indicates all CI/CD checks passed.
	CheckPass CheckConclusion = "pass"

	// CheckFail indicates one or more CI/CD checks failed.
	CheckFail CheckConclusion = "fail"

	// CheckPending indicates CI/CD checks are still running.
	CheckPending CheckConclusion = "pending"
)

type CheckStatus

type CheckStatus struct {
	Overall CheckConclusion
	Checks  []Check
}

CheckStatus holds the aggregated CI/CD check results for a PR.

type CloseResult

type CloseResult struct {
	// IssueNumber is the GitHub issue that was processed.
	IssueNumber int

	// CommentPosted indicates whether the success comment was posted.
	CommentPosted bool

	// LabelAdded indicates whether the resolved label was added.
	LabelAdded bool

	// IssueClosed indicates whether the issue was closed.
	IssueClosed bool
}

CloseResult holds the outcome of an issue closure operation.

type Comment

type Comment struct {
	Body      string    `json:"body"`
	Author    Author    `json:"author"`
	CreatedAt time.Time `json:"createdAt"`
}

Comment represents a GitHub issue comment.

type DefaultIssueCloser

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

DefaultIssueCloser implements IssueCloser using the gh CLI.

func NewIssueCloser

func NewIssueCloser(root string, opts ...IssueCloserOption) *DefaultIssueCloser

NewIssueCloser creates a new DefaultIssueCloser rooted at the given directory.

func (*DefaultIssueCloser) Close

func (c *DefaultIssueCloser) Close(ctx context.Context, issueNumber int, comment string) (*CloseResult, error)

Close posts a comment, adds the "resolved" label, and closes the issue. It retries each step up to maxRetries times with exponential backoff.

The operation is sequential: comment -> label -> close. If comment posting fails, label and close are skipped. If label fails, close is still attempted (label is non-critical). The result tracks which steps succeeded for partial failure recovery.

type ExecFunc

type ExecFunc func(ctx context.Context, dir string, args ...string) (string, error)

ExecFunc is the function signature for executing gh CLI commands. It matches the signature of execGH for seamless integration.

type GHClient

type GHClient interface {
	// PRCreate creates a pull request and returns the PR number.
	PRCreate(ctx context.Context, opts PRCreateOptions) (int, error)

	// PRView retrieves PR details by number.
	PRView(ctx context.Context, number int) (*PRDetails, error)

	// PRMerge merges a PR by number using the specified method.
	// If deleteBranch is true, the head branch is deleted after merge.
	PRMerge(ctx context.Context, number int, method MergeMethod, deleteBranch bool) error

	// PRChecks returns the CI/CD check status for a PR.
	PRChecks(ctx context.Context, number int) (*CheckStatus, error)

	// Push pushes the current branch to the remote.
	Push(ctx context.Context, dir string) error

	// IsAuthenticated checks whether gh is authenticated.
	IsAuthenticated(ctx context.Context) error
}

GHClient abstracts GitHub CLI (gh) operations for testability.

type Issue

type Issue struct {
	Number   int       `json:"number"`
	Title    string    `json:"title"`
	Body     string    `json:"body"`
	Labels   []Label   `json:"labels"`
	Author   Author    `json:"author"`
	Comments []Comment `json:"comments"`
}

Issue represents a parsed GitHub issue.

func ParseIssueFromJSON

func ParseIssueFromJSON(data []byte) (*Issue, error)

ParseIssueFromJSON parses an Issue from raw JSON bytes. Useful for testing and offline processing.

func (*Issue) LabelNames

func (i *Issue) LabelNames() []string

LabelNames returns a string slice of label names for this issue.

type IssueCloser

type IssueCloser interface {
	// Close posts a comment, adds the resolved label, and closes the issue.
	// Returns a partial CloseResult even on failure to indicate which steps succeeded.
	Close(ctx context.Context, issueNumber int, comment string) (*CloseResult, error)
}

IssueCloser posts a success comment, adds a resolved label, and closes a GitHub issue.

type IssueCloserOption

type IssueCloserOption func(*DefaultIssueCloser)

IssueCloserOption configures a DefaultIssueCloser.

func WithCloserLogger

func WithCloserLogger(l *slog.Logger) IssueCloserOption

WithCloserLogger sets the logger for the issue closer.

func WithExecFunc

func WithExecFunc(fn ExecFunc) IssueCloserOption

WithExecFunc sets a custom execution function (used for testing).

func WithMaxRetries

func WithMaxRetries(n int) IssueCloserOption

WithMaxRetries sets the maximum number of retry attempts per operation.

func WithRetryDelay

func WithRetryDelay(d time.Duration) IssueCloserOption

WithRetryDelay sets the base delay between retry attempts. Actual delay doubles with each attempt (exponential backoff). Zero disables backoff; negative values are ignored.

type IssueParser

type IssueParser interface {
	ParseIssue(ctx context.Context, number int) (*Issue, error)
}

IssueParser parses GitHub issues.

func NewIssueParser

func NewIssueParser(root string) IssueParser

NewIssueParser returns an IssueParser that uses the gh CLI. The root parameter sets the working directory for gh commands, which must be inside the target GitHub repository.

type Label

type Label struct {
	Name string `json:"name"`
}

Label represents a GitHub issue label.

type MergeMethod

type MergeMethod string

MergeMethod represents the Git merge strategy for a PR.

const (
	// MergeMethodMerge creates a merge commit.
	MergeMethodMerge MergeMethod = "merge"

	// MergeMethodSquash squashes all commits into one.
	MergeMethodSquash MergeMethod = "squash"

	// MergeMethodRebase rebases commits onto the base branch.
	MergeMethodRebase MergeMethod = "rebase"
)

type MergeOptions

type MergeOptions struct {
	// AutoMerge indicates whether --auto-merge was specified.
	AutoMerge bool

	// Method is the merge strategy (merge, squash, rebase).
	Method MergeMethod

	// DeleteBranch indicates whether to delete the branch after merge.
	DeleteBranch bool

	// RequireReview indicates whether review approval is required.
	RequireReview bool

	// RequireChecks indicates whether CI checks must pass.
	RequireChecks bool

	// SpecID is the SPEC identifier for quality validation context.
	SpecID string
}

MergeOptions configures the merge operation.

type MergeResult

type MergeResult struct {
	// Merged indicates whether the PR was actually merged.
	Merged bool

	// PRNumber is the pull request number.
	PRNumber int

	// Method is the merge strategy used.
	Method MergeMethod

	// BranchDeleted indicates whether the feature branch was deleted.
	BranchDeleted bool

	// MergedAt is the timestamp of the merge.
	MergedAt time.Time
}

MergeResult summarizes the merge outcome.

type PRCreateOptions

type PRCreateOptions struct {
	Title       string
	Body        string
	BaseBranch  string
	HeadBranch  string
	Labels      []string
	IssueNumber int
}

PRCreateOptions holds parameters for creating a pull request.

type PRDetails

type PRDetails struct {
	Number     int       `json:"number"`
	Title      string    `json:"title"`
	State      string    `json:"state"`
	Mergeable  string    `json:"mergeable"`
	HeadBranch string    `json:"headRefName"`
	BaseBranch string    `json:"baseRefName"`
	URL        string    `json:"url"`
	CreatedAt  time.Time `json:"createdAt"`
}

PRDetails holds information about an existing pull request.

type PRMerger

type PRMerger interface {
	// Merge attempts to merge a PR if all conditions are met.
	Merge(ctx context.Context, prNumber int, opts MergeOptions) (*MergeResult, error)

	// CheckPrerequisites verifies all merge conditions without merging.
	CheckPrerequisites(ctx context.Context, prNumber int, opts MergeOptions) (*PrerequisiteCheck, error)
}

PRMerger handles conditional PR merge operations.

type PRReviewer

type PRReviewer interface {
	// Review runs quality validation and CI checks, then generates a review decision.
	// Pass nil for input to have Review fetch all data itself.
	Review(ctx context.Context, prNumber int, specID string, input *ReviewInput) (*ReviewReport, error)
}

PRReviewer generates quality-based PR review reports.

type PrerequisiteCheck

type PrerequisiteCheck struct {
	// AllMet indicates all prerequisites are satisfied.
	AllMet bool

	// AutoMergeFlag indicates --auto-merge was specified.
	AutoMergeFlag bool

	// ReviewApproved indicates the PR review approved the changes.
	ReviewApproved bool

	// ChecksPassed indicates CI/CD checks passed.
	ChecksPassed bool

	// QualityPassed indicates TRUST 5 quality gates passed.
	QualityPassed bool

	// Mergeable indicates no merge conflicts exist.
	Mergeable bool

	// FailureReasons lists the unmet prerequisites.
	FailureReasons []string
}

PrerequisiteCheck lists which merge conditions are met.

type Registry

type Registry struct {
	Version  string        `json:"version"`
	Mappings []SpecMapping `json:"mappings"`
}

Registry holds the bidirectional mappings between GitHub issues and SPECs.

type RetryError

type RetryError struct {
	// Operation describes the operation that was retried.
	Operation string

	// Attempts is the total number of attempts made.
	Attempts int

	// LastError is the error from the final attempt.
	LastError error
}

RetryError wraps the final error after all retry attempts are exhausted.

func (*RetryError) Error

func (e *RetryError) Error() string

Error returns a human-readable description of the retry failure.

func (*RetryError) Unwrap

func (e *RetryError) Unwrap() error

Unwrap returns the underlying error for errors.Is/errors.As chain.

type ReviewDecision

type ReviewDecision string

ReviewDecision represents the outcome of a PR review.

const (
	// ReviewApprove indicates the PR passes all quality checks.
	ReviewApprove ReviewDecision = "APPROVE"

	// ReviewRequestChanges indicates the PR requires changes.
	ReviewRequestChanges ReviewDecision = "REQUEST_CHANGES"

	// ReviewComment indicates the review has observations but no blocking issues.
	ReviewComment ReviewDecision = "COMMENT"
)

type ReviewInput

type ReviewInput struct {
	// PRDetails is optional pre-fetched PR details. If nil, Review fetches it.
	PRDetails *PRDetails

	// CheckStatus is optional pre-fetched CI check status. If nil, Review fetches it.
	CheckStatus *CheckStatus
}

ReviewInput holds optional pre-fetched data to avoid redundant API calls. When fields are non-nil, Review uses them instead of calling the GH API.

type ReviewReport

type ReviewReport struct {
	// PRNumber is the pull request number.
	PRNumber int

	// Decision is the review outcome (APPROVE, REQUEST_CHANGES, COMMENT).
	Decision ReviewDecision

	// QualityReport is the TRUST 5 quality validation result.
	QualityReport *quality.Report

	// CheckStatus is the CI/CD pipeline check result.
	CheckStatus *CheckStatus

	// Summary is a human-readable review summary.
	Summary string

	// Issues lists specific problems found during review.
	Issues []string
}

ReviewReport holds the results of an automated PR review.

type SpecLinker

type SpecLinker interface {
	// LinkIssueToSpec creates a new mapping between an issue and a SPEC.
	LinkIssueToSpec(issueNum int, specID string) error

	// GetLinkedSpec returns the SPEC ID linked to the given issue number.
	GetLinkedSpec(issueNum int) (string, error)

	// GetLinkedIssue returns the issue number linked to the given SPEC ID.
	GetLinkedIssue(specID string) (int, error)

	// ListMappings returns all current mappings.
	ListMappings() []SpecMapping
}

SpecLinker manages bidirectional links between GitHub issues and SPEC documents.

func NewSpecLinker

func NewSpecLinker(projectRoot string) (SpecLinker, error)

NewSpecLinker creates a SpecLinker that stores mappings in the given project root. Registry file is stored at {projectRoot}/.ae/github-spec-registry.json.

type SpecMapping

type SpecMapping struct {
	IssueNumber int       `json:"issue_number"`
	SpecID      string    `json:"spec_id"`
	CreatedAt   time.Time `json:"created_at"`
	Status      string    `json:"status"`
}

SpecMapping represents a single issue-to-SPEC link.

Jump to

Keyboard shortcuts

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