Skip to content

feat: user-global settings tier + hook gates (global enable, PR 1/3) - #1894

Open
peyton-alt wants to merge 2 commits into
mainfrom
feat/global-settings-tier
Open

feat: user-global settings tier + hook gates (global enable, PR 1/3)#1894
peyton-alt wants to merge 2 commits into
mainfrom
feat/global-settings-tier

Conversation

@peyton-alt

@peyton-alt peyton-alt commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

https://entire.io/gh/entireio/cli/trails/968

First of three PRs building global auto-enable (#1098). This PR adds the user-global settings tier and wires it into the hook gates.

Dark-launch state

No user-facing command writes ~/.config/entire/settings.json yet — that arrives in PR 3 as enable --global. Until then, no repo can observe the global tier unless the file is hand-authored, so behavior is unchanged for everyone.

IsActiveForRepo semantics

Repo-level setup Global tier Result
Present, enabled (ignored) Active
Present, explicitly disabled (ignored) Inactive — explicit disable is a veto, global cannot override
Present, unreadable / parse error (ignored) Inactive — fail closed on read errors
Absent Enabled, repo not excluded Active
Absent Enabled, repo excluded Inactive
Absent Disabled or absent Inactive

Repo-level setup always wins; the global tier is consulted only when no repo-level setup exists.

Exclude semantics

  • exclude_paths are ~-expanded doublestar globs matched against the worktree root. A bare-directory pattern also excludes everything under it: ~/oss excludes ~/oss/repo but cannot over-match ~/oss-other. Matching is case-folded on darwin/windows.
  • exclude_origins match against any configured origin URL, normalized to host/owner/repo.

Hook gates

The third gate (initHookLogging) was converted as well, so all hook gates now share one predicate.

Deliberately deferred follow-ups

  • Memoize GlobalModeActive per process before PR 3 ships enable --global. Cost today: exclude_origins forks git config per gate evaluation (~11ms × 2 per hook event). Memoization also makes the twice-evaluated gate atomic per invocation.
  • Cleanup pass on the three InitHookLogging tests (bare git inittestutil helpers).

🤖 Generated with Claude Code


Note

Medium Risk
Hook activation semantics change whenever a hand-authored global settings file enables the tier; mistakes in exclude rules or origin lookup failures disable global mode fail-closed, but repos without repo-level setup could start running Entire hooks unexpectedly once global is on.

Overview
Introduces a user-global settings tier (~/.config/entire/settings.json via ENTIRE_CONFIG_DIR) with a global section: enabled, exclude_paths (tilde-expanded doublestar globs on the worktree root, case-folded on darwin/windows), and exclude_origins (globs on normalized host/owner/repo). Loading is strict (DisallowUnknownFields); missing file is unconfigured; read/parse errors fail closed on the hook path.

Adds IsActiveForRepo: if any repo-level setup exists, repo enable/disable (and read errors) win and global is ignored; otherwise GlobalModeActive applies (enabled global + not excluded). Origin exclusion uses new GetRemoteURLsInDirIfSet (git config --get-all) so multiple origin URLs and “remote unset” vs real errors are handled correctly.

Git and agent hook entry points (hooks_git_cmd, hook_registry, initHookLogging) now gate on IsActiveForRepo instead of IsSetUpAndEnabled, including a test that hook logging initializes under global mode without .entire/settings.json. Adds doublestar/v4 and broad unit tests; no user-facing command writes the global file yet (dark launch until a later PR).

Reviewed by Cursor Bugbot for commit f6b4403. Configure here.

peyton-alt and others added 2 commits August 3, 2026 19:00
New ~/.config/entire/settings.json tier (via userdirs) with global.enabled,
exclude_paths (doublestar vs worktree root), exclude_origins (doublestar vs
normalized host/owner/repo). IsActiveForRepo is the new gate predicate:
repo-level setup always wins; the global tier is a fallback, never a merge
layer. All error paths fail closed.

Review hardening: strict JSON decoding, fail-closed origin lookup (new
gitremote.GetRemoteURLInDirIfSet distinguishes unset from failed), case-folded
path matching on case-insensitive filesystems, trailing-slash pattern
normalization, and debug logging on every skipped/failed input.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 01KZ4X27VH2530080P3WE92P1W
The agent-hook and git-hook gates now use settings.IsActiveForRepo: repos
with no repo-level setup proceed when global mode is enabled and the repo is
not excluded. Repos with any repo-level setup keep exactly their current
behavior. Dark-launched: no user-facing command writes the global settings
file yet (that arrives with enable --global in a later PR).

The defense-in-depth check inside initHookLogging converts too: all
hook-path gates must agree on a single predicate. A split gate would mean
global-mode repos do full lifecycle work with no hook logging and no
EnsureRedactionConfigured() call (privacy-relevant). The resulting
.entire/logs worktree write in global-mode repos is deliberately deferred to
PR 2, which reroutes .entire writes for invisible-mode repos; nothing can
trigger it in this PR's dark-launch state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 01KZ4Y5BBHBDE8W11125Y82SSE
@peyton-alt
peyton-alt requested a review from a team as a code owner August 3, 2026 23:38
Copilot AI review requested due to automatic review settings August 3, 2026 23:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new user-global settings tier (dark-launched) and updates hook gating to treat a repo as “active” either via existing repo-level setup or (when absent) via the global tier, enabling groundwork for “global auto-enable” (#1098).

Changes:

  • Add parsing and evaluation of ~/.config/entire/settings.json (via ENTIRE_CONFIG_DIR) with global enable + exclude rules, and a unified settings.IsActiveForRepo predicate.
  • Switch all hook gates (git hook entry + agent hook dispatch + hook logging init) to use the unified predicate.
  • Add supporting git-remote utilities and unit tests for global-mode matching and multi-URL remotes.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
go.mod Adds github.com/bmatcuk/doublestar/v4 dependency for glob matching.
go.sum Records checksums for the new doublestar dependency.
cmd/entire/cli/settings/global.go Implements user-global settings loading, exclude matching, GlobalModeActive, and IsActiveForRepo.
cmd/entire/cli/settings/global_test.go Adds unit tests covering global settings parsing and activation semantics (including excludes).
cmd/entire/cli/settings/global_darwin_test.go Adds a darwin-only test to pin case-folding wiring for exclude path matching.
cmd/entire/cli/hooks_git_cmd.go Updates hook command gating and hook logging init to use settings.IsActiveForRepo.
cmd/entire/cli/hooks_git_cmd_test.go Adds a test asserting hook logging initializes under global mode with no repo-level setup.
cmd/entire/cli/hook_registry.go Updates agent hook dispatch short-circuiting to use settings.IsActiveForRepo.
cmd/entire/cli/gitremote/gitremote.go Adds GetRemoteURLsInDirIfSet to support multi-URL origin matching and “unset vs error” semantics.
cmd/entire/cli/gitremote/gitremote_test.go Adds tests for GetRemoteURLsInDirIfSet behavior (missing, multiple URLs, errors).

Comment on lines +238 to +243
func IsActiveForRepo(ctx context.Context) bool {
if IsSetUpAny(ctx) {
return IsSetUpAndEnabled(ctx)
}
return GlobalModeActive(ctx)
}
Comment on lines +44 to +48
// ExcludeOrigins are doublestar globs matched against the origin remote
// URL normalized to host/owner/repo. A repo without an origin matches
// no origin pattern. Origins stored via git insteadOf shorthands (e.g.
// gh:acme/widgets) normalize to the shorthand form, not the expanded
// host — patterns match what git config stores.
Comment on lines +183 to +188
// Initialize git repo
gitInit := exec.CommandContext(context.Background(), "git", "init")
gitInit.Dir = tmpDir
if err := gitInit.Run(); err != nil {
t.Fatalf("failed to init git repo: %v", err)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants