pkg

package
v0.3.6 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2026 License: BSD-2-Clause Imports: 21 Imported by: 0

Documentation

Overview

Package pkg provides the core domain types and logic for the github-dark-factory-watcher service: GitHub API integration, pull-request filtering, cursor persistence, and Kafka command publishing.

Index

Constants

View Source
const DefaultCursorPath = "/data/cursor.json"

DefaultCursorPath is the default path for cursor state persistence.

View Source
const DefaultMaxSlugLen = 80

DefaultMaxSlugLen is the default cap for the slugified PR-title segment alone. Override via MAX_SLUG_LEN.

View Source
const DefaultMaxTitleLen = 200

DefaultMaxTitleLen is the default safety cap for the whole vault filename, including segments and separators. Crosses Windows MAX_PATH=260 and ext4 NAME_MAX=255 with margin. Override via MAX_TITLE_LEN.

Variables

This section is empty.

Functions

func BuildCreateCommand

func BuildCreateCommand(
	pr PullRequest,
	details PRDetails,
	taskIDStr string,
	cfg TaskConfig,
	matchedSpecs []string,
) task.CreateCommand

BuildCreateCommand builds the dark-factory-implement CreateTaskCommand for a candidate PR. Exposed for direct testing of the emitted contract.

func DeriveTaskID

func DeriveTaskID(owner, repo string, number int, sha string) uuid.UUID

DeriveTaskID returns a deterministic task identifier for a (PR, SHA) pair. Input: "<owner>/<repo>#<number>@<sha>", e.g. "bborbe/maintainer#42@abc123...". The full SHA is used (not truncated) to keep the dedup keyspace collision-free. Same inputs → same UUID, so a re-emit of the same (PR, SHA) is a controller no-op.

func DeriveTaskIDForce added in v0.2.0

func DeriveTaskIDForce(owner, repo string, number int, sha, nonce string) uuid.UUID

DeriveTaskIDForce returns a salted task identifier for a (PR, SHA) pair plus an extra nonce. Used when an operator explicitly force-triggers a re-run via the /trigger command with force=true: the nonce segment "!<nonce>" makes the UUID differ from DeriveTaskID(...) for the same (PR, SHA), so the agent controller's task-file dedup does NOT skip it and a fresh task is created. The nonce is the caller's responsibility (typically a microsecond-resolution time value).

The nonce is expected to carry microsecond resolution (e.g. UnixMicro): two force-triggers landing in the same microsecond could theoretically derive the same identifier and collide. Future readers should not swap this for a coarser clock (second/millisecond resolution widens that collision window).

func NewMemDB added in v0.2.0

func NewMemDB() libkv.DB

NewMemDB returns a session-scoped, in-process libkv.DB implementation. It exists because libkv itself does not ship a public NewMemDB constructor (the upstream bborbe/kv package exposes only NewDBWithMetrics). The github-pr watcher's command consumer needs an offset store; this in-memory implementation gives it one without requiring a PVC (see spec 066 AC 9 rationale: replay-from-OffsetOldest on restart is safe because the downstream CreateTaskCommand is idempotent via derived task_id).

The implementation is intentionally minimal — it implements only the methods the offset-store / consumer wiring touches: Update, View, Sync, Close, Remove, Stats, StatsDetailed. Iterator / ListBucketNames return empty results because no caller in this package uses them.

func SaveCursor

func SaveCursor(ctx context.Context, path string, state Cursor) error

SaveCursor persists cursor state to path atomically via a temp file + rename.

Types

type Cursor

type Cursor struct {
	LastUpdatedAt libtime.DateTime  `json:"last_updated_at"`
	HeadSHAs      map[string]string `json:"head_shas"`
}

Cursor holds the watcher's persisted poll state, including the last-seen update time and a map of task-identifier to head SHA for force-push detection.

func LoadCursor

func LoadCursor(ctx context.Context, path string, startTime libtime.DateTime) (Cursor, error)

LoadCursor reads cursor state from path. Returns cold-start state with startTime if the file is missing or corrupt.

type GitHubClient

type GitHubClient interface {
	// SearchPRs issues a GitHub Search query for open PRs updated since cursor.
	// page=1 for the first call; use SearchResult.NextPage for subsequent calls.
	SearchPRs(
		ctx context.Context,
		scope string,
		since libtime.DateTime,
		page int,
	) (SearchResult, error)

	// GetPRDetails fetches head SHA, clone URL, branch, state and draft flag for
	// a single PR via pulls/N (re-confirming draft/state against search-index lag).
	GetPRDetails(ctx context.Context, owner, repo string, number int) (PRDetails, error)

	// ListPRFiles returns the paths of every file touched by the PR diff
	// (paginated pulls/N/files).
	ListPRFiles(ctx context.Context, owner, repo string, number int) ([]string, error)

	// GetContent returns the decoded bytes of a repo file at the given ref.
	// A missing file surfaces as an error for which IsNotFound reports true.
	GetContent(ctx context.Context, owner, repo, filePath, ref string) ([]byte, error)

	// ListDir returns the entry paths (files and subdirs) directly under the
	// given directory path at ref. A missing directory surfaces as an error for
	// which IsNotFound reports true.
	ListDir(ctx context.Context, owner, repo, dirPath, ref string) ([]string, error)

	// IsNotFound reports whether err is a GitHub 404 (missing file/dir/ref).
	IsNotFound(err error) bool
}

GitHubClient abstracts the GitHub API calls the watcher and the darkfactory candidate evaluator need.

func NewGitHubClient

func NewGitHubClient(httpClient *http.Client) GitHubClient

NewGitHubClient returns a GitHubClient backed by the real GitHub API. The httpClient must already carry authentication (App auth via lib/githubapp.NewClient).

type Metrics

type Metrics interface {
	// IncPollCycle increments the poll cycle counter with the given result label.
	// result: "success", "rate_limited", "github_error"
	IncPollCycle(result string)
	// IncPublished increments the published counter with the given status label.
	// status: "create", "skipped", "error"
	IncPublished(status string)
	// IncReposScanned increments the scanned-PR counter (sanity check on the scope filter).
	IncReposScanned()
	// IncFilterSkipped increments the filter-skipped counter with the given reason.
	// reason: "out_of_scope", "details_error", "empty_sha", "not_draft", "not_open",
	// "no_dark_factory_yaml", "no_spec_in_diff", "prompts_in_flight", "evaluate_error"
	IncFilterSkipped(reason string)
}

Metrics exposes counters for observable watcher behaviour.

func NewMetrics

func NewMetrics(registerer prometheus.Registerer) Metrics

NewMetrics returns a Metrics implementation backed by Prometheus counters registered against the given registerer. Passing nil resolves to prometheus.DefaultRegisterer (production); tests pass prometheus.NewRegistry() so repeated construction in the same process does not panic with a duplicate registration.

type PRDetails

type PRDetails struct {
	// HeadSHA is the commit hash of the PR's head branch. Used as the `ref`
	// the agent checks out and as the per-task dedup key.
	HeadSHA string
	// CloneURL is the HTTPS clone URL of the head repo (e.g.
	// `https://github.com/owner/repo.git`). Emitted as `clone_url`.
	CloneURL string
	// Branch is the head branch name (e.g. `dark-factory/foo`). Emitted as `branch`.
	Branch string
	// State is the PR state ("open" / "closed"), re-confirmed via pulls/N.
	State string
	// IsDraft indicates whether the PR is a draft, re-confirmed via pulls/N.
	IsDraft bool
	// Title is the PR title.
	Title string
	// UpdatedAt is the PR last-updated timestamp; drives the cursor watermark.
	UpdatedAt libtime.DateTime
}

PRDetails holds the per-PR fields the watcher needs to re-confirm state and materialize a task the agent can act on. The Search API does not expose most of these; they require a follow-up PullRequests.Get call (which also dodges search-index lag on the draft/state flags).

type PullRequest

type PullRequest struct {
	GlobalID  int64
	Number    int
	Owner     string
	Repo      string
	Title     string
	HTMLURL   string
	IsDraft   bool
	UpdatedAt libtime.DateTime
}

PullRequest holds the fields the watcher needs from a GitHub PR search result.

type SearchResult

type SearchResult struct {
	PullRequests  []PullRequest
	HasNextPage   bool
	NextPage      int
	RateRemaining int
	RateResetAt   libtime.DateTime
}

SearchResult is the result of a single paginated search call.

type TaskConfig

type TaskConfig struct {
	Stage       string
	MaxSlugLen  int
	MaxTitleLen int
	TaskSuffix  string
	// TargetVault routes the CreateTaskCommand to a specific vault controller
	// (matched verbatim against the controller's VAULT_NAME). Empty leaves it
	// unset so the controller's legacy default-vault fallback applies.
	TargetVault string
}

TaskConfig groups the per-task publishing configuration.

type Watcher

type Watcher interface {
	Poll(ctx context.Context) error
}

Watcher polls GitHub and publishes dark-factory-implement task commands to Kafka.

func NewWatcher

func NewWatcher(
	ghClient GitHubClient,
	createSender task.CreateCommandSender,
	metrics Metrics,
	cursorPath string,
	startTime libtime.DateTime,
	scope string,
	scopeFilter filter.TaskCreationFilter,
	cfg TaskConfig,
) Watcher

NewWatcher returns a Watcher that polls GitHub and publishes commands.

Directories

Path Synopsis
Package command defines the TriggerCommand payload and its Kafka sender for the github-dark-factory watcher's request topic.
Package command defines the TriggerCommand payload and its Kafka sender for the github-dark-factory watcher's request topic.
Package darkfactory holds the network-backed candidate evaluator that decides whether an open draft PR carries an approved-not-completed dark-factory spec in its diff and therefore warrants a dark-factory-implement task.
Package darkfactory holds the network-backed candidate evaluator that decides whether an open draft PR carries an approved-not-completed dark-factory spec in its diff and therefore warrants a dark-factory-implement task.
Package factory wires concrete dependencies for the github-dark-factory-watcher binary.
Package factory wires concrete dependencies for the github-dark-factory-watcher binary.
Package filter implements the scope filter chain — the mandatory defense-in-depth predicate that refuses to act on any repo outside the configured REPO_ALLOWLIST, even if the upstream GitHub search returns more.
Package filter implements the scope filter chain — the mandatory defense-in-depth predicate that refuses to act on any repo outside the configured REPO_ALLOWLIST, even if the upstream GitHub search returns more.

Jump to

Keyboard shortcuts

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