Help us shape the future of Go! Take the 2026 Go Developer Survey

integration

package
v0.8.0 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package integration holds the client registry and the adapter contracts that make a coding agent (Claude Code, Codex, …) a local contribution: one descriptor, one registration. The package owns client identity, capability declarations, and native payload and configuration translation. It never owns lesson ranking, policy rules, or proposal validation; those stay in the shared engines.

Dependency direction is deliberate. This package may import agent, skills, approve, hooks, and gate contracts. It must not import the CLI, doctor, status, MCP, report, or distill packages, because they consume the registry and an import in the other direction is a cycle.

Index

Constants

View Source
const ClaudeID = "claude"

ClaudeID is the registry ID of Claude Code.

View Source
const CodexID = "codex"

CodexID is the registry ID of Codex.

View Source
const DefaultInvocationID = ClaudeID

DefaultInvocationID is the agent.cli value that an absent or empty key resolves to. The settings editor shows it as the default, so the UI does not repeat the literal.

View Source
const GateModeReportOnly = "report-only"

GateModeReportOnly is the mode of a gate hook whose definition discards the exit status of the hook, for example with "|| true" or "&". The hook runs and reports its verdicts. A verdict blocks only by exit status 2, so none blocks, whatever --enforce or the policy file says. Only a definition that setup does not manage has this mode.

View Source
const HardLinkReason = "replacing this file would break its hard link"

HardLinkReason is the blocker text of a file with more than one hard link (see HardLinked). Every editor of the file uses this one text, so the console states one reason for one cause.

View Source
const MaxHookPayload = 1 << 20

MaxHookPayload bounds one native hook payload in bytes. The cap applies before decoding, so an oversized payload costs no parse work. A legitimate edit event is far smaller, even with whole-file content.

Variables

Capabilities lists every capability in stable presentation order.

ConfiguredCapabilities lists the capabilities that have project configuration an inspection can classify, in presentation order. Setup is the umbrella for the three setup operations, and invocation has no project document: the invoker resolver reports it. Both are declared, never configured.

View Source
var ErrMalformedEvent = errors.New("malformed hook event")

ErrMalformedEvent reports a hook payload that a codec cannot decode: invalid syntax, a wrong field type, or a size above MaxHookPayload. It is distinct from a valid event with no paths, which decodes without an error. Advice callers treat both as "say nothing".

View Source
var ErrNotApplicable = errors.New("hook event not applicable")

ErrNotApplicable reports a well-formed event that the codec does not translate, for example a shell event given to an edit codec. The event is not broken: the hook has nothing to do with it.

View Source
var ErrPlanConflict = errors.New("setup plan conflict")

ErrPlanConflict reports two planners that want different content for one document. The coordinator never lets the last writer win.

View Source
var ErrStalePlan = errors.New("setup plan is stale")

ErrStalePlan reports an input that changed after it was planned. Apply stops and the caller must plan again: a silent re-plan could apply an intent the user never saw in the preview.

View Source
var ErrUnknownClient = errors.New("unknown client")

ErrUnknownClient reports a client ID the registry does not hold.

View Source
var ErrUnsupported = errors.New("unsupported by client")

ErrUnsupported reports an operation a client's descriptor does not declare. Callers must treat it as "not applicable", never as a broken installation.

View Source
var ErrUnsupportedGrammar = errors.New("unsupported edit grammar")

ErrUnsupportedGrammar reports an edit event whose body uses a form that the codec does not know. The codec then knows no complete set of paths. It must not report the paths it did read: a smaller set looks like complete coverage and is not.

Functions

func GateHookClients

func GateHookClients(reg *Registry, setups []ClientSetup) []string

GateHookClients returns the IDs of the requested clients for which setup installs a command gate hook, in the given order. init reads the installed gate mode from them, and it says so when the list is empty: a gate line must never describe a hook that no selected client has. A client with lesson hooks and no gate hook is not in the list.

func HardLinked added in v0.8.0

func HardLinked(root, rel string) (bool, error)

HardLinked reports whether the file rel under root has more than one hard link. The engine replaces a file by rename, and a rename gives the path a new file: every other name of the old file keeps the old bytes. So an editor refuses such a file and names the manual route. An absent file has no link and reports false.

func InstalledGateMode

func InstalledGateMode(reg *Registry, root string, clientIDs []string) string

InstalledGateMode returns the installed gate-hook mode of the clients: enforce when any of them enforces, else warn when any has a gate hook, else "". It reads only the managed hook document of each given client, through the adapter's ManagedGateMode. It never reads an unselected client. The plan reads every other document after this, so a full inspection here reads them twice. A definition that setup does not own never sets the mode of the run. The gate line names such a definition by its own mode.

Enforce wins because one enforcing hook blocks whatever the others do: the run's policy scaffold and gate line must never read weaker than an installed hook. Each adapter still keeps its own installed mode when no mode is requested, so a client can run a warn hook in an enforce run; init names such a hook beside the gate line.

func Landed added in v0.8.0

func Landed(root, rel string, want [32]byte) (bool, error)

Landed reports whether the file rel under root now holds the bytes with the SHA-256 want. An editor calls it after a guarded write that returned an error, because the write error alone does not prove the file state: a rename can succeed before a later step fails. The three results are the three outcomes that an editor reports:

  • true and no error: the planned bytes are in the file (written);
  • false and no error: the file holds other bytes or is absent (not written);
  • an error: the file cannot be read again, so the outcome is unknown. The caller must not report "written" or "not written".

It reads through ReadInput, so it follows a link for the read as the editors do, and it writes nothing.

func Missing added in v0.8.0

func Missing(root string, names []string) []string

Missing returns the names that name no file or directory, in the order of names. A relative name resolves against root, and an absolute name is checked as it is. A lesson can name a future file, so callers show the result as information only.

Only absence means missing (see absent). Another error, such as a denied directory, keeps the name out of the result, because the name can still exist. Missing uses Lstat, so a dangling link exists.

func ReadHookPayload

func ReadHookPayload(r io.Reader) ([]byte, error)

ReadHookPayload reads one native hook payload under the size cap. An oversized payload is an error and never a truncated prefix: a codec must not decode a part of an event as the complete event.

func SameGuard added in v0.8.0

func SameGuard(a, b FileGuard) bool

SameGuard reports whether two observations of one file describe the same state: existence, content, permission bits, link, and write access. It skips Path and Kind, because they name the file and the read rule, not the file state. An editor compares the guard of a reviewed plan with a fresh observation right before it writes. A change of the mode or of the access after the review is also a change that the user did not see.

The setup engine (verifyGuard) compares the whole guard too, but with the observation of its own setup plan, not with the reviewed guard. pinfile.Prepare compares only the existence and the content with the reviewed guard. So the editor compares the rest with SameGuard.

func SkillTargets

func SkillTargets(dests []SkillDestination) []skills.Target

SkillTargets converts destinations into installer targets. The client label is for narration only, so it names every consumer.

func UngatedHookClients

func UngatedHookClients(reg *Registry, setups []ClientSetup) []string

UngatedHookClients returns the names of the requested clients that get hooks and no command gate hook, in the given order. When another selected client has a gate hook, the gate line of the run describes that client only, and init must name the clients it does not cover.

func UnifiedDiff added in v0.8.0

func UnifiedDiff(rel string, before, after []byte) (diff string, added, removed int)

UnifiedDiff renders the display diff between two versions of the file at rel, with the same redaction and sanitizing as PreviewWrites. A planner calls it when its write is blocked, for example by a link, so the user still sees the exact change. Added and removed count the changed lines. The diff is for display only; never apply it.

func WriteRefusal added in v0.8.0

func WriteRefusal(guard FileGuard) string

WriteRefusal returns the reason the engine refuses to replace the file that guard observed, or an empty string. An editor that reads a file itself uses it to show the refusal before it plans a write. The engine and the editors share these texts, so they cannot drift apart.

Types

type ApplyOptions

type ApplyOptions struct {
	// Preview reports every write as planned and writes nothing.
	Preview bool
	// Observe receives each result in order, as soon as it is known:
	// every document, then every skill directory. The caller narrates
	// from it, so its lines keep the order of the run. Nil is allowed.
	Observe func(OpResult)
	// SkillsLog receives the skill installer's own line for each skill
	// directory, in init's words. Nil discards it. A caller that prints
	// these lines skips the OpSkill results in Observe.
	SkillsLog io.Writer
}

ApplyOptions controls one ApplySetup run.

type Capability

type Capability string

Capability names one optional thing a client adapter can do. A client declares a capability through the matching descriptor field; an empty field means the client does not support it. There is no blanket "supports hooks" flag, because edit advice, command gating, and context resets are separate native surfaces. There is no blanket "supports MCP" flag either: registration and tool grants are separate setup operations.

const (
	// CapabilitySkills consumes skills from the declared skill directories.
	CapabilitySkills Capability = "skills"
	// CapabilitySetup plans the client's native configuration documents.
	CapabilitySetup Capability = "setup"
	// CapabilityMCPRegistration registers the seamark MCP server natively.
	CapabilityMCPRegistration Capability = "mcp-registration"
	// CapabilityToolGrants adds the exact per-tool grants. It does not
	// imply CapabilityMCPRegistration: a client can grant access to a
	// registration that another tool or the user made.
	CapabilityToolGrants Capability = "tool-grants"
	// CapabilityEdits decodes edit events and encodes advisory context.
	CapabilityEdits Capability = "edits"
	// CapabilityCommands decodes shell events and encodes gate decisions.
	CapabilityCommands Capability = "commands"
	// CapabilityResets decodes context-reset events.
	CapabilityResets Capability = "resets"
	// CapabilityInvocation builds the one-shot inference command.
	CapabilityInvocation Capability = "invocation"
)

The capabilities a descriptor can declare, in presentation order.

type CapabilityInspection

type CapabilityInspection struct {
	Capability Capability `json:"capability"`
	// Supported is whether the descriptor declares the capability.
	Supported bool            `json:"supported"`
	State     CapabilityState `json:"state"`
	// Trust is the project's native trust state for this capability.
	Trust TrustState `json:"trust"`
	// Verification is the native compatibility evidence.
	Verification VerificationEvidence `json:"verification"`
	// Detail is a bounded, sanitized explanation for narration. It is
	// supplemental: consumers must read the typed fields, never parse it.
	// It never names the client: the consumer adds the client label.
	Detail string `json:"detail,omitempty"`
	// Action is the command or edit that changes the state, from the
	// same adapter knowledge that installs the artifact; empty when
	// nothing needs to change. init, doctor, and status print it as
	// the corrective action, so the three never disagree on the fix.
	Action string `json:"action,omitempty"`
}

CapabilityInspection is the state of one capability for one client. Supported, State, Trust, and Verification are independent dimensions: a current configuration can have unknown trust, and verified compatibility says nothing about this project's trust.

The JSON names are part of `seamark status --json`; they are additive and must stay stable.

func (CapabilityInspection) Describe

func (c CapabilityInspection) Describe() string

Describe renders the entry in a few words for narration: the adapter's detail when it has one, else the state in the vocabulary of the capability ("not installed", "not registered", "not configured"). init, doctor, and status all print it, so the three never phrase a state differently.

func (CapabilityInspection) Validate

func (i CapabilityInspection) Validate() error

Validate checks one capability inspection. It rejects incomplete verified evidence. It also rejects established trust or verified evidence on an unsupported capability, because both claim success for a surface the client does not have.

type CapabilityState

type CapabilityState int

CapabilityState classifies the on-disk configuration of one capability. It is separate from whether the client supports the capability at all, from project trust, and from native verification.

const (
	// StateAbsent means no seamark-owned configuration is present.
	StateAbsent CapabilityState = iota
	// StateCurrent means the owned configuration matches what init writes.
	StateCurrent
	// StatePartial means some owned entries are present or outdated.
	StatePartial
	// StateConflict means foreign or restrictive settings prevent setup.
	StateConflict
	// StateUnreadable means the native document cannot be read or parsed.
	StateUnreadable
)

The configuration states an inspection reports.

func (CapabilityState) MarshalText

func (s CapabilityState) MarshalText() ([]byte, error)

MarshalText writes the name, so JSON consumers read "current", not 1.

func (CapabilityState) String

func (s CapabilityState) String() string

String names a state for narration and test output.

func (*CapabilityState) UnmarshalText

func (s *CapabilityState) UnmarshalText(text []byte) error

UnmarshalText reads a name written by MarshalText.

type Client

type Client struct {
	// ID is the stable identifier used by `--client` and by `agent.cli`
	// in .seamark/config.yaml: lowercase letters, digits, and hyphens.
	ID string
	// Name is the display name for narration ("Claude Code").
	Name string
	// SkillDirs lists the repository-relative, slash-separated skill
	// directories the client reads. A directory can be shared between
	// clients; the destination identity is the directory, not the client.
	// A non-empty list declares CapabilitySkills; there is no separate
	// flag that could disagree with the list.
	SkillDirs []string
	// Setup plans native configuration. Nil when the client has none.
	Setup SetupAdapter
	// SetupOps declares the optional operations Setup can plan. The
	// registry rejects a declared operation when Setup is nil.
	SetupOps SetupSupport
	// Edits translates native edit events and advisory replies.
	Edits EditHooks
	// Commands translates native shell events and gate decisions.
	Commands CommandHooks
	// Resets translates native context-reset events.
	Resets ResetHooks
	// Invocation returns the one-shot inference command for a workspace
	// root. It is pure: no PATH lookup, no login, no process start.
	Invocation func(root string) (agent.CommandSpec, error)
}

Client describes one coding agent. Built-in descriptors are assembled explicitly in the registry; there is no init-time registration and no dynamic loading. Optional capabilities are nil when absent.

The registry copies SkillDirs at its boundaries, so callers cannot change registered data. The registry does not copy adapter values: reflection or a generic deep copy would add machinery for no current need. An adapter implementation must therefore be immutable after registration.

func (Client) Declared

func (c Client) Declared() []Capability

Declared lists the capabilities the descriptor declares, in the order of Capabilities, for diagnostics and contract tests.

func (Client) DescribeSupport

func (c Client) DescribeSupport() string

DescribeSupport names what the descriptor declares, for help text and diagnostics: the setup operations and the lifecycle surfaces, in a fixed order. Help, init, doctor, and status derive their support claims from it, so they agree.

func (Client) Require

func (c Client) Require(capability Capability) error

Require returns ErrUnsupported when the client does not declare the capability. It gives every caller one error shape for "this client has no such surface".

func (Client) Supports

func (c Client) Supports(capability Capability) bool

Supports reports whether the descriptor declares a capability.

type ClientPlan

type ClientPlan struct {
	Reads     []FileGuard
	Writes    []FileWrite
	Kept      []FileKeep
	GateHooks []GateHook
	Findings  []Finding
}

ClientPlan is one client's read guards, composed writes, kept documents, and findings. A native document appears once in Writes or once in Kept, never in both, and every such document has a guard in Reads. A file that was only read, for example to find a registration name, has a guard and no other entry.

type ClientSetup

type ClientSetup struct {
	ClientID string
	// Skills installs the client's skill directories.
	Skills bool
	// Hooks installs the client's lifecycle hooks.
	Hooks bool
	// RegisterMCP registers the seamark MCP server natively.
	RegisterMCP bool
	// ApproveTools adds the exact per-tool grants (opt-in).
	ApproveTools bool
	// GateMode is warn or enforce; empty preserves the installed mode.
	GateMode string
	// CheckHookSources makes the adapter inspect the client's other hook
	// sources for seamark handlers. An init run without --client leaves
	// it off: that form never read those files, and it must stay as it
	// was.
	CheckHookSources bool
}

ClientSetup is the per-client intent of one setup run. Legacy init forms and explicit `--client` selection both translate into it, so the orchestration never inspects flags.

func ExplicitSetups

func ExplicitSetups(reg *Registry, ids []string, installSkills, approveTools bool, gateMode string) ([]ClientSetup, error)

ExplicitSetups translates `--client` into per-client intent. Every selected client gets its hooks and its MCP registration; the skills and the tool grants stay opt-in flags. Repeated IDs collapse and the result follows registry order, so reordered flags give one plan. An unknown ID fails here, before anything is read. An operation that a client does not support stays in the intent: the coordinator removes it and reports it, so partial support is visible to the user.

func LegacySetups

func LegacySetups(root, skillsMode string, approveTools bool, gateMode string) ([]ClientSetup, error)

LegacySetups translates an init run without `--client` into the intent it always had. The intent is granular on purpose. It is not "fully configure every detected client":

  • Claude Code always gets its hooks, whatever --skills names. It never gets an MCP registration, because init never wrote .mcp.json.
  • --approve-tools grants Claude Code unless --skills=codex. For Codex it registers the server and approves the tools together, when --skills names Codex or, with no client named, a .codex/ directory exists.
  • The skills go to the directories the --skills mode addresses. A bare --skills detects Codex by .agents/, not by .codex/.

skillsMode is one of skills.Modes, or empty for "not requested". Codex is left out when the run asks nothing of it, so its files are never read.

type CommandEvent

type CommandEvent struct {
	EventMeta
	Command string
}

CommandEvent is one validated shell-command event. Command is the shell text the gate evaluates; patch text never reaches this type.

type CommandHooks

type CommandHooks interface {
	DecodeCommand(payload []byte) (CommandEvent, error)
	EncodeDecision(decision *gate.Decision) (HookReply, error)
	EncodeFailure(err error) HookReply
}

CommandHooks translates a client's shell-command lifecycle for the gate. DecodeCommand turns one native payload into the shell text; ErrNotApplicable names an event of another tool, which the gate never evaluates. EncodeDecision renders a decision: a reply with a nonzero ExitCode blocks the tool with Stderr as the reason, and `seamark gate` then exits 2 for every client. EncodeFailure renders a gate failure in the native blocking form; the caller decides whether the mode requires blocking.

type Document

type Document struct {
	// Path is repository-relative and slash-separated.
	Path string
	// CreateOnly marks a starter file. An existing path is kept without a
	// read, whatever it is; Compose then only supplies the new content.
	CreateOnly bool
	// Compose returns the complete file. exists is false for an absent
	// file; existing is then nil.
	Compose func(existing []byte, exists bool) ([]byte, error)
	// Detail says what the write adds, for narration. KeptDetail says
	// why an unchanged file needs nothing.
	Detail     string
	KeptDetail string
	// Narrate replaces the generic line when set.
	Narrate Narrator
}

Document is one client-independent file of a setup run. Compose receives the current bytes and returns the bytes the run leaves. When it returns the input unchanged, the coordinator keeps the file. Compose must be pure: the coordinator may call it for a preview.

type EditEvent

type EditEvent struct {
	EventMeta
	// Paths lists the affected paths as the client reported them. The
	// shared normalization resolves them against CWD and the workspace,
	// preserving order and removing duplicates.
	Paths []string
}

EditEvent is one normalized edit opportunity: the complete set of paths the native operation affects. A partially parsed patch must never become an EditEvent; the decoder returns an error instead.

type EditHooks

type EditHooks interface {
	DecodeEdit(payload []byte) (EditEvent, error)
	EncodeAdvice(text string) (HookReply, error)
	AdviceMechanism() string
}

EditHooks translates a client's edit lifecycle. DecodeEdit turns one native payload into the complete set of affected paths; EncodeAdvice turns rendered advisory text into the native, nonblocking reply.

AdviceMechanism names the native path that EncodeAdvice uses to reach the agent, for example "pre-tool-use-context". The firing log records it beside the client, so statistics can tell two delivery paths apart. The name is a short constant: lowercase letters, digits, and hyphens. It names the path only and claims nothing about attention.

type EventMeta

type EventMeta struct {
	// SessionID is the client's session identifier, useful for audit
	// even when it is insufficient for suppression.
	SessionID string
	// MatchID identifies the native tool call (Claude tool_use_id, Codex
	// tool_use_id) so repeated matches join in the audit log.
	MatchID string
	// NativeTool is the client's tool name as reported ("Edit",
	// "apply_patch"), recorded for statistics.
	NativeTool string
	// CWD is the event working directory, used to resolve relative paths
	// inside the workspace.
	CWD string
	// Context is the receiving context, or nil when the adapter cannot
	// identify one. Nil disables once-per-context suppression.
	Context *ReceivingContext
}

EventMeta carries the identity every native event shares. Values are bounded input from the client, kept in memory for one hook run.

type FileGuard

type FileGuard struct {
	Path   string
	Kind   GuardKind
	Exists bool
	// Linked is the first component of the path that is a symbolic link,
	// or empty. Setup never writes a linked path.
	Linked string
	SHA256 [32]byte
	Mode   fs.FileMode
	// Denied is true when this user may not write the existing file,
	// although the owner write bit can be set. An access control list or
	// another owner causes it. A plain write fails on such a file, so
	// setup never replaces it.
	Denied bool
}

FileGuard records the observed state of one input file, so apply can detect a change between plan and write and abort as stale.

func ReadGuarded

func ReadGuarded(root, rel string) (FileGuard, []byte, error)

ReadGuarded reads one native document that setup owns and records its observed state. Adapters plan from the returned bytes, so a plan and its guard always describe the same file state. A missing file is not an error: the guard records the absence, and a later creation makes the plan stale.

It rejects a symbolic link at any path component and a path that is not a regular file. A link committed in a cloned repository must never redirect a read or a write of a client's configuration.

func ReadInput

func ReadInput(root, rel string) (FileGuard, []byte, error)

ReadInput reads a file that setup only reads, or at most extends. The read may follow a symbolic link, as it always did for such files: a registration file that links elsewhere still names the server. The guard records the link, and the coordinator refuses every write to a linked path, so a link can never redirect a write.

func StatGuarded

func StatGuarded(root, rel string) (FileGuard, error)

StatGuarded observes a create-only file by its existence alone. An existing path is kept whatever it is: a directory, an unreadable file, or a link. Setup never looks inside a file it must not clobber. An absent path is about to be created, so no component of it may be a link.

type FileKeep

type FileKeep struct {
	Path      string
	Consumers []string
	// Detail says why nothing changes, for narration.
	Detail string
	// Narrate replaces the generic line when set.
	Narrate Narrator
}

FileKeep is one document the plan inspected and leaves unchanged, so the result can say "kept" with the reason.

type FileWrite

type FileWrite struct {
	Path  string
	After []byte
	// Mode is the permission for a new file. An existing file keeps its
	// own permission. Zero means 0644.
	Mode      fs.FileMode
	Consumers []string
	// Detail says what the write adds, for narration. It must never hold
	// file content: a preview must not print native configuration.
	Detail string
	// Narrate replaces the generic line when set.
	Narrate Narrator
}

FileWrite is one composed native document to write, with every client that contributed to it.

type Finding

type Finding struct {
	Level FindingLevel `json:"level"`
	// Path is the repository-relative document the finding is about.
	Path   string `json:"path,omitempty"`
	Reason string `json:"reason"`
	// Action is the command or edit that resolves the finding; empty
	// when nothing needs to change.
	Action string `json:"action,omitempty"`
}

Finding is one structured observation with its corrective action, so init, doctor, and status can narrate the same fact the same way.

type FindingLevel

type FindingLevel int

FindingLevel grades a finding for presentation.

const (
	// FindingInfo is informational; nothing needs to change.
	FindingInfo FindingLevel = iota
	// FindingWarning names a limitation the user should know.
	FindingWarning
	// FindingError names a state that stops setup or breaks a capability.
	FindingError
)

The finding levels.

func (FindingLevel) MarshalText

func (l FindingLevel) MarshalText() ([]byte, error)

MarshalText writes the name, so JSON consumers read "warning", not 1.

func (FindingLevel) String

func (l FindingLevel) String() string

String names a level for narration and test output.

func (*FindingLevel) UnmarshalText

func (l *FindingLevel) UnmarshalText(text []byte) error

UnmarshalText reads a name written by MarshalText.

type GateEffect

type GateEffect int

GateEffect is what the gate hooks of a workspace do with a blocking verdict, from strongest to weakest. One enforcing hook blocks whatever the others do, and one warn hook follows the policy file whatever a report-only hook beside it does. An unreadable hook document and a definition that may run a gate outrank a report-only hook. Either can hold an enforcing gate, so "nothing blocks" is not known.

const (
	// GateEffectNone means no gate hook runs.
	GateEffectNone GateEffect = iota
	// GateEffectReportOnly means every gate hook that runs discards its
	// exit status: verdicts are reported, nothing blocks.
	GateEffectReportOnly
	// GateEffectUnknown means no gate hook is known to block, and a hook
	// document cannot be read or a definition may run a gate, so one may.
	GateEffectUnknown
	// GateEffectFollowsPolicy means a warn hook runs: the policy file
	// decides whether a verdict blocks.
	GateEffectFollowsPolicy
	// GateEffectBlocks means an enforcing hook runs: blocking verdicts
	// exit 2.
	GateEffectBlocks
)

The gate effects, in order of strength.

type GateHook

type GateHook struct {
	// ClientID is filled by the coordinator.
	ClientID string
	// Path is the document that holds the hook, repository-relative.
	Path string
	// Mode is warn, enforce, or GateModeReportOnly. Only a hook that
	// setup does not manage can be report-only.
	Mode string
	// Managed is true for the hook setup installs and updates. False
	// means another source, which setup never edits.
	Managed bool
	// Uncertain is true when the reader cannot tell whether the
	// definition runs the gate. For example, another program gets the
	// gate command as its arguments. Mode is then the mode the
	// definition has when it runs.
	Uncertain bool
}

GateHook is one command-gate hook that the client really runs after the plan is applied. A plan lists every one it knows: the hook setup manages, and a hook in another source that setup found and left as it is. The caller's gate summary reads the list, because one enforcing hook blocks whatever the managed hook's mode says, and a summary that names only the managed mode would then be false.

type GateSummary

type GateSummary struct {
	// Enforce lists the hooks that carry --enforce. A verdict of theirs
	// blocks, whatever the policy file says.
	Enforce []string
	// Warn lists the hooks that follow the policy file. A verdict of
	// theirs blocks only when the policy file enforces.
	Warn []string
	// ReportOnly lists the hooks that discard their exit status. No
	// verdict of theirs blocks, whatever --enforce or the policy file
	// says.
	ReportOnly []string
	// Possible lists the definitions that can run a gate hook, where the
	// reader cannot tell whether they do. What they do is unknown, not
	// nothing.
	Possible []string
	// Unreadable lists the hook documents that cannot be read. What
	// their hooks do is unknown, not nothing.
	Unreadable []string
}

GateSummary groups the gate hooks of a workspace by what they do with a blocking verdict. init builds it from the hooks a run leaves, and status from the inspection of every client, so the two commands classify one state by one rule and differ only in their words. The labels are the caller's: document paths for init, client IDs for status. Each label appears once per group.

func SummarizeGateHooks

func SummarizeGateHooks(gateHooks []GateHook) GateSummary

SummarizeGateHooks groups the gate hooks of a setup run by their document path. A hook that may run is possible when it could block.

func SummarizeInspections

func SummarizeInspections(inspections []Inspection) GateSummary

SummarizeInspections groups the gate hooks of every client that has a command gate by its client ID. A client whose hook document cannot be read is unreadable, with the detail of its commands entry. A client with a definition that may run a gate is possible, beside the mode of the definitions that certainly run one.

func (*GateSummary) Add

func (g *GateSummary) Add(label, mode string)

Add records one hook that runs, by its mode. A mode other than enforce, warn, and report-only records nothing.

func (*GateSummary) AddPossible

func (g *GateSummary) AddPossible(label string)

AddPossible records one definition that may run a gate hook.

func (*GateSummary) AddUnreadable

func (g *GateSummary) AddUnreadable(label string)

AddUnreadable records one hook document that cannot be read.

func (GateSummary) Effect

func (g GateSummary) Effect() GateEffect

Effect returns the strongest effect among the hooks.

type GuardKind

type GuardKind int

GuardKind names how much of a file a guard observes. The kinds exist because the files of a setup run differ in what setup may do to them.

const (
	// GuardDocument guards a native document that setup owns and may
	// rewrite. No component of its path may be a symbolic link, for the
	// read too: a link committed in a cloned repository must never
	// redirect a read or a write of a client's configuration.
	GuardDocument GuardKind = iota
	// GuardInput guards a file that setup reads and at most extends, such
	// as a registration file it only takes a name from. The read may
	// follow a link. A write through a link is still refused.
	GuardInput
	// GuardPresence guards a create-only file by its existence alone. An
	// existing file is kept whatever it is, even when it cannot be read,
	// because setup never looks inside a file it must not clobber.
	GuardPresence
)

The guard kinds.

type HookReply

type HookReply struct {
	Stdout   []byte
	Stderr   []byte
	ExitCode int
}

HookReply is what a hook run writes back to the client. Advice is always nonblocking; only the gate uses a blocking exit code.

type Inspection

type Inspection struct {
	ClientID string `json:"client"`
	// Name is the display name, filled by the registry.
	Name string `json:"name,omitempty"`
	// Declared lists every capability the descriptor declares, filled by
	// the registry. It covers the capabilities without project
	// configuration too (setup, invocation), which Capabilities omits.
	Declared []Capability `json:"declared,omitempty"`
	// Setup is what setup installs for the client, filled by the
	// registry, so narration can tell a missing hook from one that setup
	// never installs.
	Setup        SetupSupport           `json:"setup"`
	Capabilities []CapabilityInspection `json:"capabilities"`
	// GateMode is the mode the client's gate hooks run in, from every
	// definition that certainly runs one, managed or not. It is enforce
	// when any enforces, and warn when any follows the policy file. It is
	// GateModeReportOnly when each one discards its exit status. It is
	// empty when none runs or the document cannot be read. One enforcing
	// definition blocks whatever the managed hook says. The repository
	// policy file can still enforce on top of a warn hook, and never on
	// top of a report-only one.
	GateMode string `json:"gate_mode,omitempty"`
	// PossibleGateMode is the mode of the definitions that can run a
	// gate hook, by the rule of GateMode. The reader cannot tell whether
	// they do. It is empty without such a definition. A reader must not
	// take it for GateMode. A gate that may enforce is not one that
	// enforces, and not one that never blocks.
	PossibleGateMode string `json:"possible_gate_mode,omitempty"`
	// ManagedGateMode is the mode of the hook setup manages, or empty.
	// Setup keeps it when no mode is requested; it never reads a mode
	// from a definition it does not own.
	ManagedGateMode string `json:"managed_gate_mode,omitempty"`
	// HookDocumentError is the reason when the hook document that setup
	// manages cannot be read, and empty otherwise. The reason names the
	// document and is sanitized. The hook entries carry the same reason
	// in their detail, after the word "unreadable". Status repeats it in
	// its legacy gate_hook_error field, so no consumer reads it back from
	// the detail.
	HookDocumentError string `json:"hook_document_error,omitempty"`
	// Findings name the limitations and the duplicate handlers the
	// adapter sees: trust it cannot read, a receiving context it does not
	// identify, a second source that runs the same hook.
	Findings []Finding `json:"findings,omitempty"`
}

Inspection is the offline, read-only view of one client's setup. An adapter reports the capabilities it configures; Registry.Inspect completes the view with the name, the declared capabilities, the setup operations, and the shared skill destinations, in the order of ConfiguredCapabilities. Narration reads the typed fields only: an absent hook is missing when Setup says setup installs it, and absent by design otherwise.

func (Inspection) DescribeHooks

func (i Inspection) DescribeHooks() string

DescribeHooks renders the lifecycle hooks of the client in the words doctor always used: "gate (warn) + lessons hooks installed", "lessons hook installed, gate hook missing", "no hooks installed". A hook is missing only when Setup says setup installs it; a capability the client does not declare is never missing. A partial hook carries its detail, because "installed" would overstate it.

func (Inspection) DescribeVerification

func (i Inspection) DescribeVerification() string

DescribeVerification renders the native evidence of the configured capabilities, grouped by evidence, so a reader learns in one line which installed surfaces a recorded native check covers. Absent and unreadable entries are left out: there is nothing to have verified.

func (Inspection) Entry

func (i Inspection) Entry(capability Capability) (CapabilityInspection, bool)

Entry returns the inspection of one capability, and false when the view holds none for it.

type Narrator

type Narrator func(w io.Writer, status OpStatus)

Narrator prints the lines for one finished operation in init's vocabulary. The status is kept, planned (a preview), or applied; the caller prints failed and not-attempted operations itself. An adapter supplies a narrator when its document needs more than one generic line, for example the list of hook commands. The text lives with the adapter, because only the adapter knows what it composed.

type OpKind

type OpKind int

OpKind names what one operation acts on.

const (
	// OpDocument is one configuration document or common document.
	OpDocument OpKind = iota
	// OpSkill is one skill directory.
	OpSkill
)

The operation kinds.

type OpResult

type OpResult struct {
	Kind OpKind
	// Path is the document, or the skill directory, repository-relative.
	Path   string
	Status OpStatus
	// Created is true when the path did not exist before the run.
	Created   bool
	Detail    string
	Consumers []string
	// Narrate is the planner's narrator for the operation; nil when the
	// generic line is enough.
	Narrate Narrator
	// Err is the cause of OpFailed; nil otherwise.
	Err error
}

OpResult is the outcome for one document or one skill directory.

type OpStatus

type OpStatus int

OpStatus is the outcome of one document operation.

const (
	// OpKept means the document needs no change.
	OpKept OpStatus = iota
	// OpPlanned means a preview: the document would be written.
	OpPlanned
	// OpApplied means the document was written.
	OpApplied
	// OpFailed means the write failed; Err holds the cause.
	OpFailed
	// OpNotAttempted means an earlier failure stopped the run first.
	OpNotAttempted
)

The outcomes ApplySetup reports.

func (OpStatus) String

func (s OpStatus) String() string

String names a status for narration and test output.

type ReceivingContext

type ReceivingContext struct {
	// ID is the client-native identity, kept in memory only. Persisted
	// state stores a repository-scoped digest, never the raw value.
	ID string
	// Resettable is true when the adapter's compatibility checks
	// established that a reset event reaches this exact context.
	Resettable bool
}

ReceivingContext identifies the conversation that can see injected advice. It is the actual receiver, not merely a shared parent session: a subagent reports the session id of its parent, and that id alone is not a context of its own. An adapter that cannot name the actual receiver leaves the context nil, and suppression stays off.

type Registry

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

Registry is the immutable, ordered set of known clients. Order is the registration order, which is the presentation order everywhere: help text, selection, and diagnostics.

func Builtin

func Builtin() *Registry

Builtin returns the registry of shipped clients in stable order. The descriptors are constants checked by the contract tests, so a validation failure here is a programming error, not a runtime state.

func NewRegistry

func NewRegistry(clients ...Client) (*Registry, error)

NewRegistry validates the descriptors and builds a registry. It rejects an invalid or duplicate ID, an empty name, a skill directory that is not a clean path inside the repository, and a setup operation declared without a SetupAdapter. It stores a copy of each descriptor, so a later change to the caller's slices cannot reach the registry.

func (*Registry) Clients

func (r *Registry) Clients() []Client

Clients returns the descriptors in registration order. The slice and every SkillDirs slice are copies; the registry stays immutable.

func (*Registry) IDs

func (r *Registry) IDs() []string

IDs returns the client IDs in registration order.

func (*Registry) Inspect

func (r *Registry) Inspect(root string) []Inspection

Inspect runs every client's offline inspection and completes each view with what only the registry knows: the display name, the declared capabilities, and the state of the shared skill destinations. The result follows registry order and covers exactly ConfiguredCapabilities per client, so init, doctor, and status read one account of support and setup. Nothing is written, no client binary runs, and no login starts.

A shared skill directory is inspected once and reported to every consumer with the same words, so no client can claim a state that another contradicts.

func (*Registry) InspectSkills

func (r *Registry) InspectSkills(root string) []skills.ClientState

InspectSkills reports the skill directories of the registry's clients, one record per destination, labelled by every consumer. It is the list init, doctor, and status print, and the source of each client's skills entry in Inspect, so the two views cannot disagree.

func (*Registry) InvocationIDs

func (r *Registry) InvocationIDs() []string

InvocationIDs lists valid agent.cli values in registry order.

func (*Registry) Lookup

func (r *Registry) Lookup(id string) (Client, bool)

Lookup finds a client by ID. The descriptor is a copy.

func (*Registry) ResolveInvocation

func (r *Registry) ResolveInvocation(cfg *agent.Config, root string) (agent.CommandSpec, error)

ResolveInvocation resolves the configured agent's command for root. It skips PATH and authentication checks so dry runs and diagnostics work without the client installed. Use agent.NewCommand to create the invoker, or agent.NewGroupCommand when no terminal interrupt reaches the caller.

Custom argv takes precedence, uses the name "custom", and inherits the caller's directory. Otherwise agent.cli selects a registered client, defaulting to Claude, independently of client setup. Unknown clients and clients without invocation support return an error listing valid choices.

func (*Registry) Select

func (r *Registry) Select(ids []string) ([]Client, error)

Select resolves user-supplied IDs into descriptors: duplicates collapse, unknown IDs fail before any work, and the result follows registration order regardless of the input order, so repeated runs with reordered flags produce the same plan. The descriptors are copies.

type ResetEvent

type ResetEvent struct {
	Context ReceivingContext
}

ResetEvent marks a context whose delivery generation becomes eligible again after a native reset such as compaction.

type ResetHooks

type ResetHooks interface {
	DecodeReset(payload []byte) (ResetEvent, error)
}

ResetHooks translates a client's context-reset lifecycle.

type SetupAdapter

type SetupAdapter interface {
	// Inspect reports the current configuration state without writing.
	Inspect(root string) Inspection
	// ManagedGateMode returns the mode of the gate hook that setup
	// manages: warn, enforce, or "" when none runs or the adapter has no
	// gate hook. It reads the managed hook document alone. init calls it
	// before it plans, and the plan reads every document again, so a full
	// Inspect here doubles the reads. The value must equal Inspect's
	// ManagedGateMode and the mode that Plan keeps, so the adapter reads
	// all three through one helper.
	ManagedGateMode(root string) string
	// Plan composes every edit to the client's native documents for one
	// request. Each native document appears at most once in Writes.
	Plan(root, binary string, request ClientSetup) (ClientPlan, error)
}

SetupAdapter plans a client's native configuration. Plan reads only; the shared setup coordinator guards inputs, deduplicates skill destinations, and applies writes. Inspect is offline and read-only.

type SetupPlan

type SetupPlan struct {
	// Root is the workspace the plan was made for.
	Root string
	// Reads guards every input. Its order is the document order of the
	// run: common documents first, then each client in registry order.
	Reads  []FileGuard
	Writes []FileWrite
	Kept   []FileKeep
	Skills []skills.Entry
	// SkillGuards is parallel to Skills: one guard per entry.
	SkillGuards  []SkillGuard
	Destinations []SkillDestination
	// GateHooks lists the gate hooks the selected clients run after apply.
	GateHooks []GateHook
	Findings  []Finding
}

SetupPlan is the complete, validated plan for one setup run. It is also the preview: it holds every change and nothing was written.

func PlanSetup

func PlanSetup(reg *Registry, req SetupRequest) (*SetupPlan, error)

PlanSetup builds the complete plan for one request and writes nothing. Every selected client and every common document is planned and validated here, so a malformed selected file stops the run before the first write. A client that is not in the request is never read: a broken configuration of an unselected client cannot block the run.

An intent the client does not support is removed and reported as an informational finding. A missing optional capability is not a broken installation.

type SetupRequest

type SetupRequest struct {
	Root    string
	Binary  string
	Clients []ClientSetup
	// Common lists the client-independent documents of the run, such as
	// the .seamark scaffolds and the .gitignore entries. The caller owns
	// their content; the coordinator guards, plans, and writes them with
	// the client documents, so one preflight covers every write.
	Common []Document
}

SetupRequest is one complete setup run. Selection is resolved once, in registry order, before planning.

type SetupResult

type SetupResult struct {
	Ops    []OpResult
	Skills []OpResult
}

SetupResult reports every operation of one run, so a partial failure says exactly what landed and what did not. Ops holds the documents in document order. Skills holds the skill directories in plan order; they are applied after the documents and report through the same type.

func ApplySetup

func ApplySetup(plan *SetupPlan, opts ApplyOptions) (SetupResult, error)

ApplySetup executes a plan. Before the first write it checks every guard, the skill directories included; a difference returns ErrStalePlan with nothing written. It checks the inputs once more right before each write.

A failed write stops the run. The result then names the failed operation and marks every later write as not attempted. Nothing is rolled back: an earlier write stays, and a repeated run converges, because the next plan starts from the files as they are.

type SetupSupport

type SetupSupport struct {
	// Hooks means the adapter can install the client's lifecycle hooks.
	// It is a setup operation, not a capability: the codecs that handle
	// the hook events are declared through Edits, Commands, and Resets.
	Hooks bool `json:"hooks"`
	// GateHook means the installed hooks include the command gate, so a
	// gate mode applies to the client. A client can install lesson hooks
	// and no gate hook, and init must then print no gate mode for it.
	GateHook bool `json:"gate_hook"`
	// ResetHook means the installed hooks include the context reset. A
	// client whose reset has no observable effect installs none, and
	// diagnostics must then not call the absent hook missing.
	ResetHook bool `json:"reset_hook"`
	// RegisterMCP means the adapter can register the seamark MCP server.
	RegisterMCP bool `json:"register_mcp"`
	// ApproveTools means the adapter can add the exact per-tool grants.
	// It is independent of RegisterMCP.
	ApproveTools bool `json:"approve_tools"`
}

SetupSupport declares which optional setup operations the client's SetupAdapter can plan. One adapter composes all native documents, so the operations are metadata, not one interface per operation. The field names match the ClientSetup intents they answer.

type SkillDestination

type SkillDestination struct {
	Dir       string
	Consumers []string
}

SkillDestination is one skill directory and every selected client that reads it. The directory is written once for all consumers.

func SkillDestinations

func SkillDestinations(clients []Client) []SkillDestination

SkillDestinations groups the selected clients' skill directories by directory, in first-appearance order, with every consumer listed in the given client order. A shared directory is planned and written once; its consumers are reported together so no client claims a status the other contradicts.

type SkillGuard

type SkillGuard struct {
	// Rel is the skill directory, repository-relative.
	Rel    string
	Digest [32]byte
}

SkillGuard records the observed state of one skill directory: a digest of everything a refresh can overwrite. The plan entry's classification is not enough, because an edited stale copy is still stale.

type TrustState

type TrustState int

TrustState classifies whether the user trusts the current project's native configuration in the client (for example, reviewed hooks). Seamark only reports trust; it never grants it.

const (
	// TrustUnknown means nothing is known; never treat it as trusted.
	TrustUnknown TrustState = iota
	// TrustPending means the client still waits for the user's review.
	TrustPending
	// TrustEstablished means the client records the project as trusted.
	TrustEstablished
)

The trust states an inspection reports.

func (TrustState) MarshalText

func (s TrustState) MarshalText() ([]byte, error)

MarshalText writes the name, so JSON consumers read "pending", not 1.

func (TrustState) String

func (s TrustState) String() string

String names a trust state for narration and test output.

func (*TrustState) UnmarshalText

func (s *TrustState) UnmarshalText(text []byte) error

UnmarshalText reads a name written by MarshalText.

type Verification

type Verification int

Verification classifies native compatibility evidence for a capability. Missing evidence is never reported as verified.

const (
	// VerificationUnknown means nothing is known; treat as unverified.
	VerificationUnknown Verification = iota
	// VerificationUnverified means no native check covers the capability.
	VerificationUnverified
	// VerificationPending means a check is defined but not yet run.
	VerificationPending
	// VerificationVerified means a recorded native check passed for the
	// tested client version and surface.
	VerificationVerified
)

The verification levels an inspection reports.

func (Verification) MarshalText

func (v Verification) MarshalText() ([]byte, error)

MarshalText writes the name, so JSON consumers read "verified", not 3.

func (Verification) String

func (v Verification) String() string

String names a verification level for narration and test output.

func (*Verification) UnmarshalText

func (v *Verification) UnmarshalText(text []byte) error

UnmarshalText reads a name written by MarshalText.

type VerificationEvidence

type VerificationEvidence struct {
	Level Verification `json:"level"`
	// ClientVersion is the client version the recorded check ran against.
	ClientVersion string `json:"client_version,omitempty"`
	// Surface names the native surface the check exercised, for example
	// "PreToolUse apply_patch".
	Surface string `json:"surface,omitempty"`
}

VerificationEvidence is the native compatibility evidence for one capability. Its scope is the tested client version and surface only. It does not show that the current project's hooks are trusted or operational; TrustState reports project trust separately. The zero value means unknown.

func VerifiedEvidence

func VerifiedEvidence(clientVersion, surface string) (VerificationEvidence, error)

VerifiedEvidence builds evidence at VerificationVerified. It rejects a blank version or surface, because a verified claim without its scope reads as a claim about every version.

func (VerificationEvidence) Describe

func (e VerificationEvidence) Describe() string

Describe renders the native evidence for narration: what was checked and on which version. An unverified level says so, because a reader must not take an installed hook for a checked one; an unknown level renders empty.

func (VerificationEvidence) Validate

func (e VerificationEvidence) Validate() error

Validate checks that verified evidence names its tested version and surface. Other levels carry optional scope, so they always pass.

type WritePreview added in v0.8.0

type WritePreview struct {
	// Path is the repository-relative path of the write.
	Path string
	// Created is true when the file does not exist yet.
	Created bool
	// Diff is a unified diff with explicit "+" and "-" line markers.
	Diff string
	// Added and Removed count the changed lines.
	Added, Removed int
	// Consumers names the clients that contribute to the write; empty
	// for shared Seamark documents.
	Consumers []string
	// Detail is the planner's one-line description of the write.
	Detail string
	// Stale is true when the file changed after planning, so the plan
	// can no longer apply.
	Stale bool
	// Err holds a read failure of the current file.
	Err error
}

WritePreview pairs one planned write with the current file content so a reviewer sees the exact change before apply. Native client files hold other servers' tokens, so the diff hides credential values by key and by table (see preview_redact.go), then applies redact.Secrets and a block sanitizer to every line.

func PreviewWrites added in v0.8.0

func PreviewWrites(plan *SetupPlan) []WritePreview

PreviewWrites reads the current content of every planned write and returns one preview per write, in plan order. It writes nothing.

Each file is read by the rule of its plan guard, the same rule apply uses. A file that differs from its guard makes the preview stale. A read failure also makes it stale, because apply refuses the plan for the same failure.

Directories

Path Synopsis
Package inspecttest holds the one fixture matrix that the integration, doctor, status, and init tests share.
Package inspecttest holds the one fixture matrix that the integration, doctor, status, and init tests share.

Jump to

Keyboard shortcuts

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