Documentation
¶
Overview ¶
Package approve manages the configuration that lets a coding agent call the seamark MCP tools without a permission prompt: Claude Code allow rules in .claude/settings.json and Codex per-tool approvals in .codex/config.toml. init writes both on --approve-tools; doctor and status read them. Both files are project configuration: evidence of intent, never a guarantee, because user-level and managed client policy still apply on top.
Index ¶
- Constants
- Variables
- func AllowSet(settings map[string]any) map[string]bool
- func ApplyCodex(w io.Writer, root string, p *CodexPlan, printOnly bool) error
- func ClaudeRules() ([]string, error)
- func ClaudeRulesFor(server string) ([]string, error)
- func KeptSuffix(conflicts []string) string
- func ServerRule(server string) string
- func Summary(states []ClientApproval) string
- func ToolRule(server, tool string) string
- type ClaudePlan
- type ClientApproval
- type CodexPlan
- type Registration
Constants ¶
const ( ClientClaude = "claude" ClientCodex = "codex" )
Client names, as narrated.
const ( StateUnreadable = "unreadable" StateConflicting = "conflicting" StateNotConfigured = "not configured" StatePartial = "partial" StateCurrent = "current" )
Approval states, from nothing to done. Conflicting and unreadable need a person; partial and not configured need --approve-tools.
const ClaudeServer = "seamark"
ClaudeServer is the server name the rules use when .mcp.json does not register seamark: the name `claude mcp add seamark` gives it.
const ClaudeSettings = ".claude/settings.json"
ClaudeSettings is the Claude Code project file that carries the rules.
const CodexConfig = ".codex/config.toml"
CodexConfig is the project configuration Codex reads for trusted projects; the per-tool approvals and the server registration live in it. Repository-relative, slash-separated.
const CodexServer = "seamark"
CodexServer is the registration name init uses when none exists.
const MCPConfig = ".mcp.json"
MCPConfig is the Claude Code project file that registers MCP servers. The server name in it is the prefix of every tool rule, so the rules must be derived from it, never assumed.
Variables ¶
var Tools = []string{"orient", "why", "change_set", "check", "expand"}
Tools is the seamark MCP tool surface, in definition order. The list lives here rather than in internal/mcp because status imports this package and mcp imports status; a test in internal/mcp pins the two lists to each other.
Functions ¶
func AllowSet ¶
AllowSet returns the string entries of permissions.allow, reading the settings without creating anything.
func ApplyCodex ¶
ApplyCodex writes the planned TOML and narrates in init's vocabulary. Every existing byte, comments included, stays where it is: the block is appended, and the two registration keys that complete a header-only table are inserted under that header. The path is checked for links again right before the write, because the plan may be older than the tree.
func ClaudeRules ¶
ClaudeRules returns the rules for the conventional server name. The benchmark and the tests use it; init, doctor, and status derive the name from .mcp.json through ClaudeRulesFor.
func ClaudeRulesFor ¶
ClaudeRulesFor returns the allow rules --approve-tools merges into .claude/settings.json for one server name: one exact rule per MCP tool and one exact Skill rule per shipped skill. Exact names, never Skill(seamark-*): a wildcard would pre-approve a skill that does not exist yet. The rules do not depend on whether the skills are installed, so an MCP-only setup can approve the tools alone; the benchmark's MCP-only arm needs exactly that.
func KeptSuffix ¶
KeptSuffix renders the conflicts for an init line, or "" when there are none. init's Claude and Codex lines share it, so the two read alike.
func ServerRule ¶
ServerRule spells the Claude Code rule that covers every tool of one server. Claude Code documents it beside the per-tool form.
func Summary ¶
func Summary(states []ClientApproval) string
Summary renders the one-line view status prints, for example "claude 8/8 rules · codex not registered". A partial configuration names the corrective command once at the end.
Types ¶
type ClaudePlan ¶
type ClaudePlan struct {
// Server is the name the tool rules are spelled with.
Server string
// Rules is the complete rule set for the server.
Rules []string
// Approved lists the rules permissions.allow covers; Missing the
// rules the merge appends; Conflicts the explicit deny or ask
// entries that keep a rule from working, described.
Approved []string
Missing []string
Conflicts []string
}
ClaudePlan is the outcome of inspecting the permissions in .claude/settings.json for one server name: what is approved, what --approve-tools adds, and what it leaves alone.
func PlanClaude ¶
func PlanClaude(settings map[string]any, server string) (*ClaudePlan, error)
PlanClaude classifies every rule against the three permission lists. Claude Code applies deny, then ask, then allow. A rule in deny or ask is therefore a conflict however allow reads. Appending an allow rule for it would change nothing, and counting it as approved would hide a prompt. The server-wide rule counts for every tool of the server, in each list. An entry that covers several tools is reported once.
func (*ClaudePlan) Conflicting ¶
func (p *ClaudePlan) Conflicting() int
Conflicting counts the rules the conflicts keep from working. It is not len(Conflicts): one server-wide deny entry covers five tool rules.
type ClientApproval ¶
type ClientApproval struct {
Client string `json:"client"`
// Path is the repository-relative configuration file.
Path string `json:"path"`
// Registered is the server name that runs `seamark mcp`: the
// .codex/config.toml table for Codex, the .mcp.json entry for Claude
// Code. Empty when nothing is registered.
Registered string `json:"registered,omitempty"`
// Approved counts the rules (Claude Code) or tools (Codex) present;
// Total is what a complete configuration holds.
Approved int `json:"approved"`
Total int `json:"total"`
// Conflicts lists explicit settings seamark leaves alone, such as a
// tool set to prompt, a denied rule, or a server name in use.
Conflicts []string `json:"conflicts,omitempty"`
// Err reports a file that cannot be read or parsed.
Err string `json:"error,omitempty"`
}
ClientApproval summarizes one client's approval configuration for init, doctor, and status.
func Inspect ¶
func Inspect(root string) []ClientApproval
Inspect reports both clients regardless of detection, so a partial configuration in a directory auto would skip stays visible. Errors are recorded on the client, never returned: status must describe a broken setup, not fail on it.
func (ClientApproval) Describe ¶
func (c ClientApproval) Describe() string
Describe renders one client in a few words, for example "claude 8/8 rules" or "codex registered as \"seamark\", 5/5 tools approved". init, doctor, and status all print it. A Claude Code server under a name other than the conventional one is named, because the rules are spelled with it. "not configured" is reserved for a client with no registration and no rules, so the words agree with State: a registered client with no rules is partial and reads "claude 0/8 rules", the same way Codex reads "0/5 tools approved".
func (ClientApproval) State ¶
func (c ClientApproval) State() string
State classifies the record by one rule for both clients. A client with no registration and no approvals is not configured, which is information. A registered client with fewer approvals than tools is partial, zero included: a registration alone approves nothing, so every call prompts and the re-run hint is due. Codex approvals without a registration are partial too, because the re-run adds the missing registration.
type CodexPlan ¶
type CodexPlan struct {
// Exists reports whether the file is present.
Exists bool
// Server is the registration the approvals attach to; empty when a
// conflict prevents both reuse and registration.
Server string
// Registered is true when Server names a registration the file
// already holds; Register is true when the block adds one. Both are
// false when the file cannot be extended.
Registered bool
Register bool
// Missing lists the tools the block approves; Approved the tools
// already approved, by their own entry or by an approve default.
Missing []string
Approved []string
// Conflicts lists explicit settings left alone, described.
Conflicts []string
// contains filtered or unexported fields
}
CodexPlan is the outcome of inspecting .codex/config.toml before any write: what exists, what the appended block adds, and what seamark leaves alone.
func PlanCodex ¶
PlanCodex inspects .codex/config.toml without writing. It rejects a symlinked path, malformed TOML, and wrong-typed or invalid values on the seamark server, because init must fail before its first write and Codex would refuse the file. A conflicting registration or an explicit restrictive setting, a disabled server or a server-wide approval default included, is reported in the plan and left alone, because a person put it there. The appended block is validated by parsing the would-be file, and inline tables are detected from the decoded keys, so a layout that appending cannot extend is reported instead of written.
type Registration ¶
type Registration struct {
// Exists reports whether the file is present.
Exists bool
// Server is the name whose command is seamark; empty when none.
Server string
}
Registration describes what .mcp.json says about seamark.
func ClaudeRegistration ¶
func ClaudeRegistration(root string) (Registration, error)
ClaudeRegistration reads .mcp.json and finds the seamark server by the same basename rule doctor and hooks use: exact "seamark", tolerating the Windows suffix. Several matches prefer the conventional name, then the first in name order, so repeated runs agree. A missing file is not an error; an unparseable one is, because a rule prefix guessed from a broken file would be reported as current while every call prompts.
func (Registration) ServerName ¶
func (r Registration) ServerName() string
ServerName returns the name the tool rules must use: the registered one, or the conventional name when nothing is registered yet.