Documentation
¶
Overview ¶
Package hooks manages declarative, user-owned Git hook templates.
Index ¶
- Constants
- func AppendEvent(path string, event Event) error
- func AppendEvents(path string, events []Event) error
- func RefreshManagedShims(repoPath, configPath, wbExecutable, projectsRoot string) (bool, error)
- func ReplayPendingMetrics(repoPath, configPath, projectsRoot string) (int, error)
- func RepositoryRoot(path string) (string, error)
- func RunSecureHooksGitHelper(args []string) int
- func SecureExecutionWriteRoots(repoPath, configPath, projectsRoot string) ([]string, error)
- type ActiveProfile
- type AgentConfig
- type AgentPolicy
- type ApplyOptions
- type ApplyResult
- type BlockMetrics
- type BlockRunResult
- type CachedGHPRLookup
- type CheckReport
- type Classification
- type DailyMetrics
- type Event
- type ExecutionLayout
- type Finding
- type HookBlock
- type HookConfig
- type MetricsConfig
- type MetricsPolicy
- type MetricsSummary
- type PRLookup
- type PendingMetricsReceipt
- type Policy
- type ProfileCost
- type ProfileDefinition
- type ProfileDefinitionConfig
- type ProfileDelta
- type ProfileDetection
- type ProfilesConfig
- type RefUpdate
- type ResolvedHook
- type RunOptions
- type RunResult
Constants ¶
const ( BuiltinPreCommit = "builtin:pre-commit" BuiltinPrePush = "builtin:pre-push" )
const ( BuiltinGoPreCommit = "builtin:go-pre-commit" BuiltinGoPrePush = "builtin:go-pre-push" BuiltinNodePreCommit = "builtin:node-pre-commit" BuiltinNodePrePush = "builtin:node-pre-push" BuiltinWorktreeGuard = "builtin:worktree-guard" )
const ( // TierSkip runs no language validation. 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 // TierPublication marks the default branch, a tag, or a branch with an // open pull request. The hook still runs only static validation. TierPublication pushTier = 2 )
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.
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.
const EventSchemaVersion = 1
const PolicyVersion = 1
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 AppendEvents ¶ added in v0.4.0
func RefreshManagedShims ¶ added in v0.22.2
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 RepositoryRoot ¶
RepositoryRoot resolves path to the enclosing non-bare Git worktree.
func RunSecureHooksGitHelper ¶ added in v0.22.2
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
Types ¶
type ActiveProfile ¶ added in v0.4.0
type AgentConfig ¶ added in v0.123.0
type AgentConfig struct {
// AutoTags declares that this repository's own CI already tags it (see
// lesson l3-check-existing-tags-before-tagging-a-lower-version-with-newer-code-hides-the-fix
// and lesson l11-find-out-whether-the-repo-auto-tags-before-you-hand-tag-it),
// so a hand-pushed `git tag`/`git push --tags` is refused. A pointer so an
// explicit `false` at the repository can override a `true` global policy.
AutoTags *bool `yaml:"autoTags" json:"autoTags,omitempty"`
}
AgentConfig declares policy for `wb hooks agent pre-tool-use` — the PreToolUse guard, not a Git hook. It lives in the same file as the Git hooks policy because both are "how strict is this repository", but this package only carries the field through decode/validate so the guard's own package (internal/agentguard, which never spawns a process and reads this file itself) can read it; nothing here acts on it.
type AgentPolicy ¶ added in v0.123.0
type AgentPolicy struct {
AutoTags bool
}
AgentPolicy is the resolved (global-then-repository-layered) form of AgentConfig — see AgentConfig for what AutoTags means.
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 BlockRunResult ¶ added in v0.4.0
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) IsPublication ¶ added in v0.95.3
func (c Classification) IsPublication() bool
IsPublication reports whether the pushed refs require publication policy.
func (Classification) RunLint ¶ added in v0.65.0
func (c Classification) RunLint() bool
RunLint reports whether the Tier 1 lint/vet 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"`
// Ref is the REMOTE ref a push targeted. It is what a stream push must be
// attributed to: the checked-out branch is not necessarily the pushed
// one, so keying on Branch reported zero stream pushes after real ones.
Ref string `json:"ref,omitempty"`
// Tier is the classification the push hook acted on: 0 skip, 1 lint,
// 2 lint and test. It is recorded so the saving is measured from what
// actually ran rather than inferred from a branch name.
Tier *int `json:"tier,omitempty"`
OS string `json:"os"`
Arch string `json:"arch"`
Labels map[string]string `json:"labels,omitempty"`
}
func ReadEvents ¶
type ExecutionLayout ¶ added in v0.67.11
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 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 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"`
}
type PRLookup ¶ added in v0.65.0
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 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
Agent AgentPolicy
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 ¶
LoadPolicy loads the git_hooks section from ~/.config/wb/wb.yaml and the repository's .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 ProfileCost ¶ added in v0.90.0
type ProfileCost struct {
Runs int `json:"runs"`
Failures int `json:"failures"`
TotalDurationMS int64 `json:"total_duration_ms"`
AverageDurationMS int64 `json:"average_duration_ms"`
// MaxDurationMS is the measured budget: the slowest observed run.
MaxDurationMS int64 `json:"max_duration_ms"`
}
ProfileCost is the observed cost of one hook profile.
`every-profile-declares-a-measured-budget` requires a profile to carry a measured cold wall-time budget before it becomes a default. MaxDurationMS is that budget: the worst run actually observed, not an estimate.
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 ProfileDelta ¶ added in v0.90.0
type ProfileDelta struct {
From string `json:"from"`
Through string `json:"through"`
RepositoryFilter string `json:"repository_filter,omitempty"`
// Commit is the pre-commit profile: formatting and static checks over the
// files changed in that commit, never a test suite.
Commit ProfileCost `json:"commit"`
// StreamPush is every pre-push on a `stream/<name>` branch. These run no
// local verification: CI on the stream pull request is the gate.
StreamPush ProfileCost `json:"stream_push"`
// OtherPush is every pre-push elsewhere, which runs the current full
// profile unchanged.
OtherPush ProfileCost `json:"other_push"`
// SavedRuns is the number of stream-branch pushes that ran no local
// verification.
SavedRuns int `json:"saved_runs"`
// SavedDurationMS is SavedRuns priced at the measured average cost of a
// non-stream push. It is an estimate *derived from measurement*, and the
// basis is reported alongside it so a reader can check the arithmetic.
SavedDurationMS int64 `json:"saved_duration_ms"`
// SavedBasisMS is the average non-stream push duration the estimate used.
SavedBasisMS int64 `json:"saved_basis_ms"`
// Blocks are the per-profile block costs, so a slow profile can be named
// rather than guessed at.
Blocks []BlockMetrics `json:"blocks,omitempty"`
// Unmeasured names what this window could not price, so a zero saving is
// never readable as "the stream profile saved nothing".
Unmeasured []string `json:"unmeasured,omitempty"`
}
ProfileDelta is the evidence behind the hook-profile claim: what a commit costs, what a push costs, and what pushing to a stream branch saved.
The saving is measured rather than asserted, because the whole reason the push hook defers to CI on a stream branch is cost — and a cost claim with no measurement behind it is the assurance this Feature refuses to print.
Implements: dependency-streams#req:commit-hook-is-fast-and-scoped, dependency-streams#req:push-hook-defers-to-ci-on-stream-branches, dependency-streams#req:every-profile-declares-a-measured-budget.
type ProfileDetection ¶ added in v0.4.0
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
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
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 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.