hooksync

package
v1.0.0-rc.5 Latest Latest
Warning

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

Go to latest
Published: Nov 8, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package hooksync projects imported hooks into Claude Code settings. It is the hook tenant of pkg/project: scripts are copied into a gridctl-owned directory, and matcher groups are patched into the hooks key of ~/.claude/settings.json with recorded, region-level ownership. gridctl never executes a hook.

The ownership decision table follows pkg/wiring, with the identical-value adopt branch omitted. An unrecorded group is never claimed without --force. The settings write is an RFC 6902 patch on the parsed hujson tree, so bytes outside the patched elements, including comments, stay as they were found.

Index

Constants

View Source
const (
	ActionCopied             = "copied"
	ActionUpdated            = project.ActionUpdated
	ActionUnchanged          = project.ActionUnchanged
	ActionRemoved            = "removed"
	ActionSkippedDrift       = project.ActionSkippedDrift
	ActionSkippedForeign     = "skipped-foreign"
	ActionSkippedPinDrift    = "skipped-pin-drift"
	ActionSkippedUnmanaged   = "skipped-unmanaged"
	ActionSkippedUnavailable = project.ActionSkippedUnavailable
	ActionSkippedUnsupported = "skipped-unsupported"
	ActionWouldCopy          = "would-copy"
	ActionWouldUpdate        = project.ActionWouldUpdate
	ActionWouldRemove        = "would-remove"
	ActionAlreadyGone        = "already-gone"
	ActionError              = project.ActionError

	StateInSync        = project.StateInSync
	StateStale         = project.StateStale
	StateDrifted       = project.StateDrifted
	StateTargetMissing = project.StateTargetMissing

	// SettingsUnparsableDetail is the prefix used when a settings file is
	// not valid JSON or JSONC. Force does not override that case.
	SettingsUnparsableDetail = "settings file is not valid JSON or JSONC: "
)
View Source
const ChannelMergeKey = "merge-key"

ChannelMergeKey is the only hook channel: gridctl owns matcher groups inside a shared settings file, never the file itself.

View Source
const RenderIdentity = "identity"

RenderIdentity is the only render class. The projected groups are the definition's groups with absolute script paths, not a dialect translation.

Variables

View Source
var (
	ErrUnknownClient = errors.New("unknown client")
	ErrUnknownHook   = errors.New("unknown hook")
	ErrNotProjected  = errors.New("hook is not projected")
)

Sentinel errors callers branch on.

View Source
var ErrNewerLockVersion = project.ErrNewerLockVersion

ErrNewerLockVersion signals projection state written by a newer gridctl. Aliased from the engine so callers' errors.Is checks work.

Functions

func HasFailures

func HasFailures(results []SyncResult) bool

HasFailures reports whether any sync result needs the caller's attention. Every skipped or failed row counts. Dry-run would-* rows do not.

func KnownSlugs

func KnownSlugs() []string

KnownSlugs lists target and unsupported slugs, for the unknown-client error.

func NeedsAttention

func NeedsAttention(statuses []ProjectionStatus) bool

NeedsAttention reports whether any projection requires action.

func RemovalRefusal

func RemovalRefusal(name string, row UnsyncResult) string

RemovalRefusal is the operator message for a blocked registry delete. It names the client and the unsync --force command.

func SettingsUnparsable

func SettingsUnparsable(detail string) bool

SettingsUnparsable reports whether detail names an unparsable settings file.

Types

type Entry

type Entry struct {
	// ConfigPath is the settings file. Path on the engine entry is the
	// composite "<ConfigPath>#hooks/<name>" so several hooks share one file.
	ConfigPath string
	// ScriptDir is the absolute script copy.
	ScriptDir string
	// Hashes is the canonical hash history of the owned groups, newest last.
	Hashes []string
	// TreeHash is the hash of the script copy at sync time.
	TreeHash string
	// CreatedFile is true when gridctl created the settings file.
	CreatedFile bool
	Pack        string
	SyncedAt    time.Time
}

Entry is one recorded (hook, client) ownership record.

type LockFile

type LockFile struct {
	Records map[string]map[string]*Entry
}

LockFile is the hook-kind view over the unified project lockfile, keyed hook name → client slug. The engine owns the on-disk schema.

type Manager

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

Manager owns hook projections. All target paths resolve against home. Mutating operations serialize on mu and on the engine's cross-process lock.

func NewManager

func NewManager(registryDir string) (*Manager, error)

NewManager builds a Manager rooted at the user's home directory. It is for end-of-the-line CLI call sites only: any caller in pkg/ or internal/ that tests can reach must use NewManagerWithHome.

func NewManagerWithHome

func NewManagerWithHome(home, registryDir string) *Manager

NewManagerWithHome builds a Manager rooted at an explicit home. Tests use this to stay isolated from $HOME.

func (*Manager) DetectedUnsupported

func (m *Manager) DetectedUnsupported() []UnsupportedClient

DetectedUnsupported returns unsupported clients whose detect directory exists under the manager home. The CLI prints one stderr note per row when sync was not given --clients. Pack apply turns the same list into skipped-unsupported rows.

func (*Manager) HasProjections

func (m *Manager) HasProjections(ctx context.Context) (bool, error)

HasProjections reports whether any hook is currently projected.

func (*Manager) Home

func (m *Manager) Home() string

Home returns the manager home. Pack status uses it to join rows.

func (*Manager) LockPath

func (m *Manager) LockPath() string

LockPath returns the unified projection lockfile path.

func (*Manager) Statuses

func (m *Manager) Statuses(ctx context.Context) ([]ProjectionStatus, error)

Statuses returns one row per recorded hook projection.

func (*Manager) Sync

func (m *Manager) Sync(ctx context.Context, names []string, opts SyncOptions) ([]SyncResult, error)

Sync projects hooks into every available target, or into --clients. The settings file is read and written once per client, not once per hook.

func (*Manager) Unsync

func (m *Manager) Unsync(ctx context.Context, names []string, opts UnsyncOptions) ([]UnsyncResult, error)

Unsync removes recorded groups whose hash is still one gridctl wrote, or any located groups when Force is set. Groups gridctl did not record are never deleted. The settings file is read and written once per client.

type ProjectionStatus

type ProjectionStatus struct {
	Hook        string     `json:"hook"`
	Client      string     `json:"client"`
	Channel     string     `json:"channel"`
	Render      string     `json:"render"`
	State       string     `json:"state"`
	Target      string     `json:"target"`
	ScriptDir   string     `json:"script_dir,omitempty"`
	Pack        string     `json:"pack,omitempty"`
	SyncedAt    *time.Time `json:"synced_at,omitempty"`
	Detail      string     `json:"detail,omitempty"`
	Remediation string     `json:"remediation,omitempty"`
}

ProjectionStatus is one recorded (hook, client) row.

type SyncOptions

type SyncOptions struct {
	Clients []string
	Force   bool
	DryRun  bool
	// Pack tags recorded projections. Empty keeps any existing tag.
	Pack string
}

SyncOptions configure a sync pass.

type SyncResult

type SyncResult struct {
	Hook        string `json:"hook,omitempty"`
	Client      string `json:"client"`
	Channel     string `json:"channel,omitempty"`
	Target      string `json:"target,omitempty"`
	Action      string `json:"action"`
	Detail      string `json:"detail,omitempty"`
	Remediation string `json:"remediation,omitempty"`
	BackupPath  string `json:"backup_path,omitempty"`
	Error       string `json:"error,omitempty"`
	ScriptDir   string `json:"script_dir,omitempty"`
}

SyncResult is one (hook, client) outcome.

func (SyncResult) ActionWasCopy

func (r SyncResult) ActionWasCopy() bool

type Target

type Target struct {
	Slug string
	Name string
	// SettingsPath is a ~-template expanded against the Manager's home.
	SettingsPath string
	// DetectDirs mark the client as initialized on this machine.
	DetectDirs []string
}

Target describes one client's settings file. Later dialect targets are additive: this release has Claude Code only.

func FindTarget

func FindTarget(slug string) (Target, bool)

FindTarget returns the projection target for slug.

func Targets

func Targets() []Target

Targets returns the projection targets in display order.

type UnenforcedHook

type UnenforcedHook struct {
	Name   string `json:"name"`
	Reason string `json:"reason"`
}

UnenforcedHook is one required hook that is not in-sync for a client.

func Unenforced

func Unenforced(client string, required []string, statuses []ProjectionStatus) []UnenforcedHook

Unenforced reports required hooks that are not enforced on client. Reason is one of "not projected", "drifted", "stale", "pin drift", or "client has no hook target". State and exit codes are the caller's.

type UnsupportedClient

type UnsupportedClient struct {
	Slug       string
	Name       string
	Location   string
	DetectDirs []string
}

UnsupportedClient is a client with a hook mechanism this release does not write. Sync reports it; it never creates the native file.

func FindUnsupported

func FindUnsupported(slug string) (UnsupportedClient, bool)

FindUnsupported returns a known non-target client.

func UnsupportedClients

func UnsupportedClients() []UnsupportedClient

UnsupportedClients returns clients that have a hook mechanism and no target in this release, in display order. Location is the native path named in skipped-unsupported rows and the stderr note.

type UnsyncOptions

type UnsyncOptions struct {
	All     bool
	Clients []string
	Force   bool
	DryRun  bool
}

UnsyncOptions configure an unsync pass.

type UnsyncResult

type UnsyncResult struct {
	Hook       string `json:"hook"`
	Client     string `json:"client"`
	Target     string `json:"target"`
	Action     string `json:"action"`
	Detail     string `json:"detail,omitempty"`
	BackupPath string `json:"backup_path,omitempty"`
	Error      string `json:"error,omitempty"`
}

UnsyncResult describes one removal.

func BlocksRemoval

func BlocksRemoval(results []UnsyncResult) (UnsyncResult, bool)

BlocksRemoval reports the first unsync row that did not retire the projection. removed and already-gone allow a registry delete to proceed. skipped-drift, skipped-unmanaged, error, and any other action do not.

Jump to

Keyboard shortcuts

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