hooks

package
v0.67.22 Latest Latest
Warning

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

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package hooks manages declarative, user-owned Git hook templates.

Index

Constants

View Source
const (
	BuiltinPreCommit = "builtin:pre-commit"
	BuiltinPrePush   = "builtin:pre-push"
)
View Source
const (
	BuiltinGoPreCommit   = "builtin:go-pre-commit"
	BuiltinGoPrePush     = "builtin:go-pre-push"
	BuiltinNodePrePush   = "builtin:node-pre-push"
	BuiltinWorktreeGuard = "builtin:worktree-guard"
)
View Source
const (

	// TierSkip runs neither lint nor test.
	TierSkip pushTier = 0
	// TierLint runs lint/vet only. This is the fast lane: a feature branch
	// with no open pull request yet.
	TierLint pushTier = 1
	// TierFull runs lint/vet and the full test suite. This is a publication
	// push: the default branch, a tag, or a branch with an open pull request.
	TierFull pushTier = 2
)
View Source
const CheckpointRefPrefix = "refs/wb/checkpoints/"

CheckpointRefPrefix marks a WB checkpoint ref. A push confined to this namespace is a fast-persistence path, never a landing receipt: nothing about a checkpoint ref implies the work merged or landed anywhere.

View Source
const DefaultBranchEnv = "WB_DEFAULT_BRANCH"

DefaultBranchEnv lets a repository or a fleet policy assert its default branch explicitly, skipping local detection entirely. It never makes a network call either way; this is purely for the rare local checkout where origin/HEAD was never recorded.

View Source
const EventSchemaVersion = 1
View Source
const PolicyVersion = 1
View Source
const SecureHooksGitHelperArgument = "--wb-internal-hooks-git"

SecureHooksGitHelperArgument selects the private WB child-process path that enters an inherited repository descriptor before writing core.hooksPath. It is handled before normal CLI parsing and is not a user command.

Variables

This section is empty.

Functions

func AppendEvent

func AppendEvent(path string, event Event) error

func AppendEvents added in v0.4.0

func AppendEvents(path string, events []Event) error

func RefreshManagedShims added in v0.22.2

func RefreshManagedShims(repoPath, configPath, wbExecutable, projectsRoot string) (bool, error)

RefreshManagedShims upgrades an already-managed hook installation before a command creates a new worktree. It deliberately leaves repositories without WB-managed hooks alone; a conflicting or malformed managed installation fails the caller before it can create a split-layout checkout.

func ReplayPendingMetrics added in v0.67.11

func ReplayPendingMetrics(repoPath, configPath, projectsRoot string) (int, error)

func RepositoryRoot

func RepositoryRoot(path string) (string, error)

RepositoryRoot resolves path to the enclosing non-bare Git worktree.

func RunSecureHooksGitHelper added in v0.22.2

func RunSecureHooksGitHelper(args []string) int

RunSecureHooksGitHelper is the child-side handoff for configuring core.hooksPath. The parent supplies the already-open repository as fd 3; only this short-lived child changes directory via fchdir before executing an absolute Git path, so a path substitution cannot redirect the config write.

func SecureExecutionWriteRoots added in v0.67.11

func SecureExecutionWriteRoots(repoPath, configPath, projectsRoot string) ([]string, error)

Types

type ActiveProfile added in v0.4.0

type ActiveProfile struct {
	Name   string `json:"name"`
	Order  int    `json:"order"`
	Reason string `json:"reason"`
}

type ApplyOptions

type ApplyOptions struct {
	RepoPath     string
	ConfigPath   string
	WBExecutable string
	// ProjectsRoot is persisted in every shim so hooks keep the same checkout
	// policy when WB was invoked with a non-default --projects-root.
	ProjectsRoot string
	// WBHome is persisted in every shim so hooks preserve an explicit WB_HOME
	// and do not recreate mixed-home state after a later shell invocation.
	WBHome string
	// WBHomeAllowsLegacy marks a shim installed from the normal default layout.
	// Such a shim pins its default write home but must still guard legacy linked
	// worktrees until migration completes.
	WBHomeAllowsLegacy bool
	Repair             bool
	Force              bool
	Now                func() time.Time
	// contains filtered or unexported fields
}

type ApplyResult

type ApplyResult struct {
	Report  CheckReport
	Actions []string
}

func Apply

func Apply(options ApplyOptions) (ApplyResult, error)

Apply installs or repairs WB's local shims. It never overwrites unmanaged hook files unless Force is set, and forced replacements are backed up.

type BlockMetrics added in v0.4.0

type BlockMetrics struct {
	ID                string `json:"id"`
	Profile           string `json:"profile"`
	Hook              string `json:"hook"`
	Runs              int    `json:"runs"`
	Failures          int    `json:"failures"`
	TotalDurationMS   int64  `json:"total_duration_ms"`
	AverageDurationMS int64  `json:"average_duration_ms"`
}

type BlockRunResult added in v0.4.0

type BlockRunResult struct {
	ID       string
	Profile  string
	ExitCode int
	Duration time.Duration
}

type CachedGHPRLookup added in v0.65.0

type CachedGHPRLookup struct {
	RepoRoot  string
	RepoSlug  string
	CachePath string
	TTL       time.Duration
	Timeout   time.Duration
	Now       func() time.Time
	RunGH     runGHFunc
}

CachedGHPRLookup is the enrichment path in push-tier's PR-status decision. It is deliberately the SECOND thing consulted, never the first: a fresh positive cache entry (a signal WB itself already wrote) wins over asking GitHub again. Negative answers are revalidated because PR creation makes them stale immediately. Every gh call has a hard timeout.

func NewCachedGHPRLookup added in v0.65.0

func NewCachedGHPRLookup(repoRoot string) *CachedGHPRLookup

NewCachedGHPRLookup builds the production lookup for repoRoot: cache under the user's XDG state directory (matching hook-metrics' own convention), repository slug from the configured origin remote, gh invoked through console.Env() so it can never block on a prompt or a pager.

func (*CachedGHPRLookup) OpenPullRequest added in v0.65.0

func (l *CachedGHPRLookup) OpenPullRequest(branch string) (open bool, known bool)

OpenPullRequest answers whether branch has an open pull request in RepoSlug. known is false whenever the answer cannot be established without exceeding the bounded, offline-safe budget above: no cache entry, an expired one, gh missing, gh erroring, or the timeout firing. A failed or unknown lookup is never cached, so the next push tries again rather than freezing on a bad answer.

type CheckReport

type CheckReport struct {
	RepoRoot       string          `json:"repo_root"`
	ManagedPath    string          `json:"managed_path"`
	ConfigPaths    []string        `json:"config_paths,omitempty"`
	Hooks          []string        `json:"hooks"`
	ProfilesAuto   bool            `json:"profiles_auto"`
	ActiveProfiles []ActiveProfile `json:"active_profiles,omitempty"`
	// ExcludedProfiles makes explicit policy exceptions auditable even though
	// they intentionally contribute no hook blocks. In particular, disabling
	// the default worktree admission guard must never look like an ordinary
	// healthy installation with no explanation.
	ExcludedProfiles []string            `json:"excluded_profiles,omitempty"`
	HookBlocks       map[string][]string `json:"hook_blocks,omitempty"`
	MetricsPath      string              `json:"metrics_path,omitempty"`
	Findings         []Finding           `json:"findings,omitempty"`
}

func Check

func Check(repoPath, configPath, wbExecutable, projectsRoot string) (CheckReport, error)

Check validates config, core.hooksPath, generated shims, and executability without changing repository state.

type Classification added in v0.65.0

type Classification struct {
	Tier   pushTier
	Reason string
}

Classification is the tier decision for one pre-push invocation, always paired with a one-line, human-readable Reason so an agent can tell "fast tier, no PR" from "hung" or "full tier, publication push" without guessing.

func ClassifyPendingPush added in v0.65.0

func ClassifyPendingPush(stdin io.Reader, repoRoot string) (Classification, error)

ClassifyPendingPush is the entry point `wb hooks push-tier` calls: it reads Git's pre-push ref list from stdin (unless stdin is a terminal, in which case there is no ref list to read and reading it would hang forever waiting for a human who was never asked to type anything — see shouldReplicateStdin for the same hazard), determines the repository's default branch from purely local Git state, and classifies the push.

A terminal stdin (an agent or a human running the hook by hand) has no ref list to classify from, so it reports the tier as explicitly unknown rather than guessing: Tier 1, with a reason that says why, exactly like any other unresolved PR-status case. CI remains the real gate for a publication push either way.

func ClassifyPushTier added in v0.65.0

func ClassifyPushTier(updates []RefUpdate, defaultBranch string, lookup PRLookup) Classification

ClassifyPushTier decides the tier for one composed set of pushed-ref updates. It implements the founder-approved publication rule (option b): a publication push is one whose remote ref is the repository's default branch, OR is a tag, OR names a branch with an open pull request. Anything else pushed to refs/heads/* is the fast lane: lint only. A deletion or a WB checkpoint-ref push carries no requirement of its own; if every pushed ref is one of those two, the whole push skips both lint and test.

defaultBranch may be empty when it cannot be determined locally (never fetched, detached remote HEAD); an empty value simply never matches, so an unresolvable default branch degrades to the same publication test as any other branch (PR lookup) rather than guessing.

func (Classification) ExitCode added in v0.65.0

func (c Classification) ExitCode() int

ExitCode is the fixed process-exit encoding `wb hooks push-tier` uses to hand its decision to the calling shell template: 0, 1, or 2.

func (Classification) RunLint added in v0.65.0

func (c Classification) RunLint() bool

RunLint reports whether the Tier 1 lint/vet block should run.

func (Classification) RunTest added in v0.65.0

func (c Classification) RunTest() bool

RunTest reports whether the Tier 2 test block should run.

type DailyMetrics

type DailyMetrics struct {
	Date              string `json:"date"`
	Commits           int    `json:"commits"`
	PushAttempts      int    `json:"push_attempts"`
	CommitChecks      int    `json:"commit_checks"`
	HookFailures      int    `json:"hook_failures"`
	HookRuns          int    `json:"hook_runs"`
	TotalDurationMS   int64  `json:"total_duration_ms"`
	AverageDurationMS int64  `json:"average_duration_ms"`
}

type Event

type Event struct {
	SchemaVersion int               `json:"schema_version"`
	Timestamp     time.Time         `json:"timestamp"`
	Repository    string            `json:"repository"`
	Hook          string            `json:"hook"`
	Profile       string            `json:"profile,omitempty"`
	Block         string            `json:"block,omitempty"`
	Action        string            `json:"action"`
	Outcome       string            `json:"outcome"`
	DurationMS    int64             `json:"duration_ms"`
	Commit        string            `json:"commit,omitempty"`
	Branch        string            `json:"branch,omitempty"`
	OS            string            `json:"os"`
	Arch          string            `json:"arch"`
	Labels        map[string]string `json:"labels,omitempty"`
}

func ReadEvents

func ReadEvents(path string) ([]Event, error)

type ExecutionLayout added in v0.67.11

type ExecutionLayout struct {
	Root               string
	CacheRoot          string
	GoPath             string
	GoCache            string
	GoModCache         string
	GoTmpDir           string
	XDGCacheHome       string
	ReportRoot         string
	PendingMetricsRoot string
}

ExecutionLayout is the private writable state a hook may use without touching the repository under test or requiring authority to the whole home directory.

func ResolveExecutionLayout added in v0.67.11

func ResolveExecutionLayout(repoRoot, projectsRoot string) (ExecutionLayout, error)

type Finding

type Finding struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	Path    string `json:"path,omitempty"`
}

type HookBlock added in v0.4.0

type HookBlock struct {
	ID      string
	Profile string
	Hook    ResolvedHook
}

type HookConfig

type HookConfig struct {
	Template string `yaml:"template" json:"template,omitempty"`
	Disabled bool   `yaml:"disabled" json:"disabled,omitempty"`
}

HookConfig selects a script template for one Git hook. Relative template paths are resolved from the YAML file that declares them.

type MetricsConfig

type MetricsConfig struct {
	Enabled *bool             `yaml:"enabled" json:"enabled,omitempty"`
	Path    string            `yaml:"path" json:"path,omitempty"`
	Labels  map[string]string `yaml:"labels" json:"labels,omitempty"`
}

MetricsConfig controls local hook-event collection. Enabled is a pointer so a repository policy can explicitly override a global true/false value.

type MetricsPolicy

type MetricsPolicy struct {
	Enabled bool
	Path    string
	Labels  map[string]string
}

type MetricsSummary

type MetricsSummary struct {
	From              string         `json:"from"`
	Through           string         `json:"through"`
	RepositoryFilter  string         `json:"repository_filter,omitempty"`
	Commits           int            `json:"commits"`
	PushAttempts      int            `json:"push_attempts"`
	CommitChecks      int            `json:"commit_checks"`
	HookFailures      int            `json:"hook_failures"`
	HookRuns          int            `json:"hook_runs"`
	AverageDurationMS int64          `json:"average_duration_ms"`
	Days              []DailyMetrics `json:"days"`
	Blocks            []BlockMetrics `json:"blocks,omitempty"`
}

func Summarize

func Summarize(events []Event, days int, repositoryFilter string, now time.Time) MetricsSummary

type PRLookup added in v0.65.0

type PRLookup interface {
	OpenPullRequest(branch string) (open bool, known bool)
}

PRLookup answers whether one branch has an open pull request. known is false whenever the answer cannot be established locally or within a bounded, offline-safe check; a caller must treat that as "unknown", never as "no", and must never let "unknown" silently escalate to the full tier.

type PendingMetricsReceipt added in v0.67.11

type PendingMetricsReceipt struct {
	SchemaVersion int       `json:"schema_version"`
	RecordedAt    time.Time `json:"recorded_at"`
	TargetPath    string    `json:"target_path"`
	AppendError   string    `json:"append_error"`
	Events        []Event   `json:"events"`
}

type Policy

type Policy struct {
	RepoRoot           string
	ConfigPaths        []string
	Hooks              map[string]ResolvedHook
	ProfilesAuto       bool
	ProfileSelections  map[string]bool
	ProfileDefinitions map[string]ProfileDefinition
	ActiveProfiles     []ActiveProfile
	Metrics            MetricsPolicy
	ExplicitPath       string
}

Policy is the effective configuration after built-ins, the user's global policy, and the repository policy have been layered in that order.

func LoadPolicy

func LoadPolicy(repoPath, explicitPath string) (Policy, error)

LoadPolicy loads ~/.config/wb/hooks.yaml and .wb/hooks.yaml when present. An explicit path replaces those discovery locations but still layers on top of WB's conservative built-in templates.

type ProfileDefinition added in v0.4.0

type ProfileDefinition struct {
	Name       string
	Order      int
	Detection  ProfileDetection
	Hooks      map[string]ResolvedHook
	ConfigPath string
}

type ProfileDefinitionConfig added in v0.4.0

type ProfileDefinitionConfig struct {
	Order  *int                  `yaml:"order"`
	Detect *ProfileDetection     `yaml:"detect"`
	Hooks  map[string]HookConfig `yaml:"hooks"`
}

ProfileDefinitionConfig describes a language, toolchain, product, or other repository profile. Detection paths are repository-relative and may contain filepath.Glob patterns.

type ProfileDetection added in v0.4.0

type ProfileDetection struct {
	AnyFiles []string `yaml:"any_files" json:"any_files,omitempty"`
	AllFiles []string `yaml:"all_files" json:"all_files,omitempty"`
}

type ProfilesConfig added in v0.4.0

type ProfilesConfig struct {
	Auto        *bool                              `yaml:"auto"`
	Include     []string                           `yaml:"include"`
	Exclude     []string                           `yaml:"exclude"`
	Definitions map[string]ProfileDefinitionConfig `yaml:"definitions"`
}

ProfilesConfig controls automatic detection and profile composition. Each active profile contributes at most one block to each configured hook type.

type RefUpdate added in v0.65.0

type RefUpdate struct {
	LocalRef  string
	LocalSHA  string
	RemoteRef string
	RemoteSHA string
}

RefUpdate is one line of the pushed-ref list Git streams on pre-push stdin: "<local ref> <local sha1> <remote ref> <remote sha1>".

func ParseRefUpdates added in v0.65.0

func ParseRefUpdates(r io.Reader) ([]RefUpdate, error)

ParseRefUpdates reads Git's pre-push stdin protocol. Blank lines are skipped; anything else that does not carry exactly four fields is rejected rather than silently ignored, so a malformed invocation is never misclassified as "nothing to push".

type ResolvedHook

type ResolvedHook struct {
	Name       string
	Template   string
	Builtin    bool
	Disabled   bool
	ConfigPath string
}

ResolvedHook is a validated hook entry ready to execute.

type RunOptions

type RunOptions struct {
	RepoPath     string
	ConfigPath   string
	Hook         string
	Args         []string
	Stdin        io.Reader
	Stdout       io.Writer
	Stderr       io.Writer
	Now          func() time.Time
	WBExecutable string
	ProjectsRoot string
}

type RunResult

type RunResult struct {
	ExitCode     int
	Duration     time.Duration
	Blocks       []BlockRunResult
	MetricsError error
}

func Run

func Run(options RunOptions) (RunResult, error)

Run executes the configured base and active-profile blocks in the repository root and records compact local events. Hook arguments and streams are passed through unchanged; composed pre-push blocks each receive the complete stdin.

Jump to

Keyboard shortcuts

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