config

package
v0.0.382 Latest Latest
Warning

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

Go to latest
Published: Aug 1, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Category vocabulary (sty_b2315e17): controlled type list as embedded default + satelle.toml override. Values live in substrate/config/categories.toml (embedded data, not a Go literal). Enforcement phase is config-driven: off | warn | reject (default warn — upgrade never hard-breaks existing flows).

Package config is satelle's trimmed TOML configuration, ported from satellites' internal/cliconfig with the server/token/credstore surface dropped (the OSS tier is always local — no remote dispatch). What remains:

  • Repo-root resolution: walk up from CWD for .satelle/satelle.toml.
  • Defaults for every setting — a repo with no satelle.toml runs zero-config.
  • A gitignored satelle.local.toml overlay beside the committed config.
  • [substrate_roots]: per-kind authored-markdown dirs, which MAY live outside .satelle/ (the directory monitor watches whatever these point at).
  • data_dir / db: where the per-repo sqlite database lives.

globalagents.go — the machine-wide agent PROFILE catalog at $SATELLE_HOME/agents.toml (sty_c7dfeedf / epic:agent-configuration-operability).

The catalog holds named, provider-neutral EXECUTION profiles so an operator working across several repositories updates a Claude/Grok/Codex/other definition once. It holds NO workflow policy: a profile cannot name a skill, a gate, a state, an applies_to, or anything else that decides PROCESS. That boundary is mechanical, not advisory — the loader accepts only the known binding keys and refuses everything else, naming policy keys specifically. Workflows stay repo substrate (see the satelle-repo-agnostic principle and the constitution's configuration-over-code rule).

A repo consumes a profile only by naming it: `profile = "<name>"` on a binding in .satelle/agents.toml, or — opt-in — through the catalog's [roles] defaults. There is no implicit same-name merge, so adding a profile called "reviewer" can never silently change a repo that never asked for it.

migrate.go — surgical format-migration for operator-owned agents.toml (epic:substrate-convergence order:7). harness= → command=; expand bare claude|grok presets to full templates; add missing role=. Comment-preserving line edits only — never a TOML marshal round-trip.

resolveagents.go — the deterministic precedence engine that folds the machine-wide profile catalog into a repo's agents layer (sty_c7dfeedf).

The ladder, highest first:

  1. repo — an inline value on the binding in .satelle/agents.toml
  2. profile — the catalog profile the binding EXPLICITLY names via profile= (and, transitively, whatever that profile extends)
  3. global-role — the catalog's [roles] default for the binding's role, and ONLY when the repo opted in with [defaults] use_global_roles
  4. embedded — the binary's safe fallback (DefaultExecutorCommand, DefaultReviewerCommand, DefaultReviewerTools), applied as today by the *Binding resolvers

The critical negative: tier 2 and tier 3 are reached only by an explicit reference. A catalog profile named "reviewer" and a repo [reviewer] that never mentions it DO NOT merge — the repo resolves byte-identically with and without the catalog present. Every convenience that would blur this is deliberately absent, because a machine-wide file that can silently re-point a pinned repo's reviewer is exactly the failure this design exists to prevent.

Resolution returns the same AgentsConfig shape everything downstream already consumes, plus a Provenance side-table, so no dispatch or gate code changes.

runtime.go — home-keyed runtime plane (epic:substrate-planes / sty_4660bbe1).

Authored substrate stays under <repo>/.satelle (ResolveDataDir). Runtime state — the per-repo DB, logs, backups, and the stories attachment cache — lives under ~/.satelle/<repo-key>/ (ResolveRuntimeDir). One repo-key maps to one isolated dir (never a flat multi-repo bag); see decision-local-db-placement and decision-substrate-planes-local-first.

Index

Constants

View Source
const (
	AgentsConfigName = "agents.toml"
	ActorsConfigName = "actors.toml"
)

AgentsConfigName is the per-repo agents-binding file, beside satelle.toml under the data dir (.satelle/agents.toml). ActorsConfigName is the now-removed legacy filename — it is no longer loaded (sty_7db2ed7d); `satelle reindex` warns a repo still carrying it so the rename is enforced rather than silently honoured.

View Source
const (
	DefaultExecutorCommand = "in-loop"
	// DefaultReviewerCommand is the full claude command template (the canonical
	// seed for a [reviewer] that omits command). It carries the read-only
	// --disallowedTools denylist so the grant is a ceiling. A repo overrides it
	// with its own multi-token command template in agents.toml. Bare single-token
	// presets are no longer accepted on the agents.toml path.
	DefaultReviewerCommand = agentcli.DefaultClaudeCommand
	DefaultReviewerTools   = "Read,Grep,Glob"
)

Default agent grants — the BOOTSTRAP values a binding's empty fields resolve to: the executor drives in-loop (the agent itself); the reviewer runs as an isolated agent with a READ-ONLY tool grant (see the satelle-agent-model principle — the reviewer is limited to reviewing). They fill blanks INSIDE a loaded agents.toml; they are not a substitute for the file itself — an initialized repo without a loadable agents.toml refuses to run (the CLI bootstrap's requireAgents, sty_d0d6bb67).

View Source
const (
	RoleReviewer = "reviewer"
	RoleAgent    = "agent"
)

Role values for AgentBinding.Role — the declared identity of a binding (sty_fc670c9b / epic:agent-invoke-unify). Role is orthogonal to the section name: judge-vs-perform is derived from role, not from the literal "reviewer".

View Source
const (
	InterfaceCommand = "command"
	InterfaceACP     = "acp"

	// Dispatch marker environment keys identify an isolated performing step to
	// harness hooks. They let the dispatched child use its authored tool grant
	// during the transition's in-flight window without opening that window to
	// the parent driving session. They are an honest-posture boundary, not a
	// sandbox: a process that bypasses or spoofs hooks is outside this contract.
	DispatchAgentEnv = "SATELLE_DISPATCH_AGENT"
	DispatchStepEnv  = "SATELLE_DISPATCH_STEP"
	DispatchItemEnv  = "SATELLE_DISPATCH_ITEM"
)

Interface values for AgentBinding.Interface — how satelle runs the isolated worker subprocess (epic:agent-dispatch-transport). Orthogonal to role: command = full argv template (default; any CLI including Claude); acp = Agent Client Protocol over stdio (spawn line only; satelle is client).

View Source
const (
	PrinciplesSession = "session"
	PrinciplesAll     = "all"
	PrinciplesSystem  = "system"
	PrinciplesProject = "project"
	PrinciplesNone    = "none"
)

Principles selector tokens for AgentBinding.Principles — which principles ride in an isolated agent's briefing (design sty_69fd4e20 §5).

View Source
const (
	// DefaultDataDir is the per-repo home for satelle's data stores —
	// <repo>/.satelle. A relative data_dir resolves under the repo root.
	DefaultDataDir = ".satelle"
	// DefaultDBName is the sqlite database file inside data_dir.
	DefaultDBName = "satelle.db"
	// DefaultConstitutionName is the project constitution file at the data-dir
	// root (.satelle/constitution.md) — the order-zero doc injected every session
	// (epic:session-context). It lives OUTSIDE the authored-kind dirs, so it is
	// read directly, not indexed as a kind.
	DefaultConstitutionName = "constitution.md"
	// DefaultWebPort is the local web server's listen port.
	DefaultWebPort = 8787
	// DefaultLogLevel is arbor's level when log_level is unset.
	DefaultLogLevel = "info"
	// DefaultLogsMaxSizeKB caps each flat evidence log (.satelle/logs/*.log) before
	// it rolls; DefaultLogsMaxFiles bounds how many rotations are kept. Daily
	// rolling is always on regardless of these (sty_a67e6e8c).
	DefaultLogsMaxSizeKB = 5120 // 5 MiB
	DefaultLogsMaxFiles  = 7
	// ConfigName / LocalConfigName are the committed config and its gitignored
	// per-user overlay, both under <repo>/.satelle/.
	ConfigName      = "satelle.toml"
	LocalConfigName = "satelle.local.toml"
)

Defaults applied when a key is unset. Every setting has one, so a repo with an empty (or absent) satelle.toml runs on defaults for all keys.

View Source
const (
	// GlobalConfigName is the global config filename under the global dir.
	GlobalConfigName = "config.toml"
	// DefaultServiceAddr is the bind address for the background service. Unlike
	// the transient `serve` default (127.0.0.1), the service defaults to all
	// interfaces so it is reachable across the WSL↔Windows boundary in any
	// networking mode. Restrict it to 127.0.0.1 in config to keep it off the LAN.
	DefaultServiceAddr = "0.0.0.0"
	// EnvServerEndpoint overrides [server] endpoint discovery and push
	// (sty_5aa08259). Values:
	//
	//	unset          — use config / auto-bootstrap probe as usual
	//	URL            — use that base URL; no default-port probe
	//	none|off|-|""  — disable discovery AND push (hermetic tests set this)
	//
	// It lives here, not in internal/cli, because the serve-side mirror
	// reconciler honours the same off-switch and cannot import cli
	// (sty_e6e467fe).
	EnvServerEndpoint = "SATELLE_SERVER_ENDPOINT"
)

Global config lives under ~/.satelle/ — the one machine-wide touchpoint the spec reserves (the future workspace registry lives here too). It is kept in a file named config.toml, deliberately NOT satelle.toml, so the per-repo walk-up (which looks for .satelle/satelle.toml) can never mistake the global home for a repo root.

View Source
const (
	// GlobalAgentsName is the profile catalog's filename under GlobalDir().
	// It shares agents.toml's basename deliberately — same shape, different
	// scope — and can never be confused with a repo file: the per-repo walk-up
	// only ever looks under .satelle/.
	GlobalAgentsName = "agents.toml"
	// GlobalAgentsLabel is the stable, path-independent name used in validation
	// messages so an error reads the same on every machine (and in tests, where
	// SATELLE_HOME is a temp dir).
	GlobalAgentsLabel = "~/.satelle/agents.toml"
)
View Source
const (
	SourceRepo     = "repo"
	SourceEmbedded = "embedded"
)

Source labels recorded in Provenance. Profile and role sources carry the profile name, so a display surface names WHICH profile a field came from.

View Source
const (
	// WorkspaceKindPersonal is the caller's own workspace (the default).
	WorkspaceKindPersonal = "personal"
	// WorkspaceKindTeam is a shared/team workspace multiple members sync to.
	WorkspaceKindTeam = "team"
)

Workspace kind values — the two kinds the hosted server returns (a sync "shared" SCOPE is a tier on the sync.go ladder, NOT a workspace kind; keep the two enums distinct).

View Source
const DefaultAgentCLI = "claude"

DefaultAgentCLI is the agent CLI the reviewer/summariser shell out to when none is selected — claude, whose flag surface satelle's runner mirrors.

View Source
const GrokConfigName = "config.toml"

GrokConfigName is Grok's user-level config filename under ~/.grok. [compat.*] lives only here (not project-scoped) — see Grok docs on harness compatibility.

View Source
const GrokTrustedFoldersName = "trusted_folders.toml"

GrokTrustedFoldersName is Grok's folder-trust store under ~/.grok. Project hooks under <repo>/.grok/hooks/ load only when the folder is trusted here (same file /hooks-trust writes).

View Source
const OperatingPrinciple = "satelle-agent-goals"

OperatingPrinciple is the one tight operating principle — injected at session start (it ships carrying the principles:session residency marker) and guaranteed into every reviewer's context, so the agent (and the reviewer) is driven to the result. Residency is otherwise authored via the principles:session marker (see internal/cli hook + internal/agentstep): a principle is session because it is tagged, or on-demand (the default) because it is not — never auto-injected (sty_53a4233c). Its content is overridable by a repo (.satelle/principles).

View Source
const PersonalWorkspace = "personal"

PersonalWorkspace is the zero-config active workspace: every developer's own workspace. The default when [hosted] workspace is unset — nothing to configure for the common "sync to my own area" case. A login records nothing for it.

View Source
const RepoPathMarkerName = "repo.path"

RepoPathMarkerName is the file written into a runtime dir recording the absolute repo root that owns it (sty_c36c211f AC2 — forward resolution).

Variables

View Source
var AuthoredKinds = []string{"documents", "workflows", "principles", "skills"}

AuthoredKinds are the markdown-source-of-truth artifact kinds the directory monitor watches and indexes. Each defaults to <repo>/.satelle/<kind> and is individually relocatable via [substrate_roots] (may be outside .satelle/).

View Source
var ConfigAreas = []string{"workflows", "principles", "skills", "constitution", "agents", "tasks", "settings"}

ConfigAreas are the .satelle areas eligible for the versioned config store (epic:scoped-sync, order:5): the authored kinds MINUS documents (which is its own sync kind), plus the project constitution, the agents layer, and task DEFINITIONS. The work-state areas (stories/ledger/executions) are order:7; documents is its own kind. Local-scope areas are skipped at push time — this list is the candidate set the walk resolves a tier for.

View Source
var ErrNotFound = errors.New("config: not found")

ErrNotFound signals no satelle.toml was found walking up from CWD. Callers fall back to the zero-value Config (zero-config still works).

View Source
var RuntimeKeyDirPattern = regexp.MustCompile(`^[^/]+-[0-9a-f]{8}$`)

RuntimeKeyDirPattern matches a home-keyed runtime directory name (`<sanitised-basename>-<sha256[:8]>`). Shared by CLI list and the integration host-surface guard (sty_c36c211f).

View Source
var Settings = []Setting{
	{Key: "web_port", Label: "Web port", Help: "Applies on the next serve start.", Kind: kindInt},
	{Key: "log_level", Label: "Log level", Help: "debug | info | warn | error", Kind: kindEnum, Enum: []string{"debug", "info", "warn", "error"}},
	{Key: "data_dir", Label: "Data dir", Help: "Authored substrate home under the repo (default .satelle). Runtime state is home-keyed separately. Applies on the next serve start.", Kind: kindString},
	{Key: "db", Label: "Database path", Help: "Override for the per-repo local ledger; default is ~/.satelle/<repo-key>/satelle.db (one project per DB). Applies on the next serve start.", Kind: kindString},
	{Key: "substrate_roots", Label: "Substrate roots", Help: "Authored-kind → parent dir overrides.", Kind: kindMap},
	{Key: "logs_max_size_kb", Label: "Log rotation size (KB)", Help: "Per evidence log before it rolls.", Kind: kindInt},
	{Key: "logs_max_files", Label: "Log files kept", Help: "Rotations retained.", Kind: kindInt},
	{Key: "stories_keep_closed", Label: "Keep closed stories (count)", Help: "0 = no count pruning.", Kind: kindInt},
	{Key: "stories_keep_days", Label: "Keep closed stories (days)", Help: "0 = no age pruning.", Kind: kindInt},
	{Section: "review", Key: "gate_create", Label: "Gate create", Help: "Run structure + create_review on story/task create (default on at init).", Kind: kindBool},
	{Section: "gate", Key: "edit_exempt_paths", Label: "Edit-gate exempt paths", Help: "Path prefixes exempt from the engaged-story edit gate. Init seeds .satelle/ (authored substrate) and .gitignore (satelle-managed output).", Kind: kindList},
	{Section: "gate", Key: "allow_outside_tree_edits", Label: "Allow outside-tree edits", Help: "Opt in to Bash/Edit mutations in another repo's working tree. Non-repo paths are never fenced. Default deny; only for a deliberate multi-repo install.", Kind: kindBool},
	{Section: "hosted", Key: "project", Label: "Hosted project", Help: "Project slug this repo maps to (personal sync target).", Kind: kindString},
	{Section: "hosted", Key: "workspace", Label: "Active workspace", Help: "Scoped-sync destination — personal default; a team-workspace name elects it.", Kind: kindString},
}

Settings is the ordered repo-key whitelist. The order and Label/Help strings are shared with the web settings page, so it renders identically.

View Source
var SyncAreas = buildSyncAreas()

SyncAreas is the canonical, de-duplicated list of .satelle area names a sync table may configure: the AuthoredKinds (documents/workflows/ principles/skills) plus the remaining epic-named areas — the project constitution, the agents layer, tasks, and the work-state areas (stories, ledger, executions). The reserved key syncAllKey ("all") is NOT a member — it only defaults areas that fall through unset (see ScopeFor).

Functions

func ClearGlobalHostedServer added in v0.0.123

func ClearGlobalHostedServer() error

ClearGlobalHostedServer removes the machine-wide hosted-server binding (the "remove server" action), leaving the other sections intact. Separate from SaveGlobalHostedServer, which guards against an empty URL.

func ConfigAreaLocation added in v0.0.153

func ConfigAreaLocation(cfg Config, repoRoot, area string) (location string, isDir bool)

ConfigAreaLocation resolves a config or documents area's on-disk location. isDir reports whether location is a directory to walk (vs. a single file to read directly). It mirrors the cli syncAreaPath resolution but lives in config so the walk is unit-testable without the local store. documents is resolved here too so DocumentFiles can reuse the same path helper.

func CurrentInstanceID added in v0.0.292

func CurrentInstanceID() string

CurrentInstanceID is InstanceID(GlobalDir()).

func DocumentFiles added in v0.0.160

func DocumentFiles(cfg Config, repoRoot string) (Bundle, Scope, error)

DocumentFiles walks the documents area under repoRoot with the same scope/ tier labeling as ConfigFiles (local skip; SharedTier marks publish eligibility only — sync destination remains personal). The returned Scope is the area's resolved scope so the CLI can print a clear "scope=local, skipping" message instead of an ambiguous empty list. Documents is its own sync kind (order:6) — not part of ConfigAreas. Paths matching LocalOnlyPath are withheld.

func EmbeddedCategories added in v0.0.328

func EmbeddedCategories() []string

EmbeddedCategories returns the default category list from the embedded substrate file. Parsed once. Empty on parse failure (tests assert it succeeds).

func EmbeddedCategoriesErr added in v0.0.328

func EmbeddedCategoriesErr() error

EmbeddedCategoriesErr is non-nil when the embedded file failed to load/parse. Exposed for tests; production call sites use the list (empty on failure).

func EnsureGrokFolderTrusted added in v0.0.167

func EnsureGrokFolderTrusted(repoRoot string) (changed bool, abs string, err error)

EnsureGrokFolderTrusted records abs(repoRoot) as trusted in ~/.grok/trusted_folders.toml so Grok loads project hooks for that folder. Only that path is written; other folder entries are preserved. Idempotent: already-trusted leaves the file bytes unchanged (changed=false).

Returns changed, absolute path written, error.

func ExpandVars added in v0.0.129

func ExpandVars(s string, vars map[string]string) (string, error)

ExpandVars replaces every ${NAME} in s with vars[NAME]. An unknown NAME is an error naming the missing var — never a silent empty, so a mistyped or absent key fails loudly rather than dispatching an agent with a blank credential. There is no escape syntax: a literal ${...} has no use case in an env value here.

func ExpandVarsInSettings added in v0.0.149

func ExpandVarsInSettings(settings map[string]any, vars map[string]string) (map[string]any, error)

ExpandVarsInSettings walks settings (map/slice/string leaves, as decoded from TOML) and ${NAME}-expands every string leaf against vars via ExpandVars — the same fail-fast unknown-var error, so a binding's settings table (mirroring claude's settings.local.json shape verbatim) resolves its secrets identically to Env. Non-string leaves (bool, int64, float64) pass through unchanged.

func FileShared added in v0.0.144

func FileShared(scope Scope, frontmatter string) bool

FileShared reports whether a file's frontmatter marks it shared. frontmatter is the file's raw body (or just its leading `---`-fenced YAML block) — either works, since only the leading fence is scanned. The flag is only meaningful inside a personal-scope area (AC2): local and shared areas apply uniformly to every file they hold, so any other scope reports false without inspecting frontmatter at all.

func FindDataDir added in v0.0.372

func FindDataDir(start string) (string, bool)

resolvePath finds the committed config: an explicit path, then the SATELLE_CONFIG env, then walking up from CWD for .satelle/satelle.toml. Returns "" (no error) when none is found. FindDataDir walks up from start (empty → CWD) for a `.satelle` DIRECTORY and returns its path and whether one was found. This is satelle's one notion of "this repo is initialised", and it deliberately keys on the DIRECTORY rather than on `.satelle/satelle.toml`: zero-config is supported, so an initialised repo may legitimately carry agents.toml and no satelle.toml, and keying on the toml would classify it as ungoverned.

resolvePath above walks for the CONFIG file and answers a different question ("which config governs here"), so the two intentionally coexist.

func GlobalAgentsPath added in v0.0.365

func GlobalAgentsPath() string

GlobalAgentsPath returns the path to the machine-wide profile catalog.

func GlobalConfigPath

func GlobalConfigPath() string

GlobalConfigPath returns the path to the global config file.

func GlobalDir

func GlobalDir() string

GlobalDir returns the machine-wide satelle home (~/.satelle), honoring the SATELLE_HOME override (used by tests). Falls back to ".satelle-global" in CWD only if the home directory cannot be resolved.

Under `go test` (testing.Testing()), resolving without SATELLE_HOME panics instead of writing the developer's real ~/.satelle (sty_c36c211f). That is the enforcement seam for every package: tests must call testutil.IsolateHome (or set SATELLE_HOME themselves). We import testing for testing.Testing() — available since go 1.22; this module is go 1.26 — rather than an Args[0] heuristic, so the signal is accurate for both `go test` and `go test -c`.

func GrantsContextChannel added in v0.0.360

func GrantsContextChannel(tools string) bool

GrantsContextChannel reports whether a binding's tool grant gives a DISPATCHED agent a context channel — the pull-context contract (sty_47d31300). A dispatched performer starts with no conversation history and reconstructs its context by PULLING the story, its documents, and the ledger. Two channels satisfy it (sty_565a0202):

  1. satelle CLI via shell: `Bash(satelle…)`, broad `Bash`/`Bash(*)`, or `*`.
  2. Disk reads of story documents under the home-keyed runtime plane (~/.satelle/<repo-key>/stories/<id>/) via the grok-native `read_file` tool (used when headless Grok cannot enable run_terminal_command). The in-repo `.satelle/stories/` path is obsolete (sty_58fa970e).

A grant with neither channel leaves the agent silently context-starved, so dispatch refuses it loudly. Claude-only `Read` (without Bash) is intentionally NOT accepted: the Claude pull path is the satelle CLI, not a disk-first rubric.

REVIEWERS need no channel — satelle injects the attachments into the transition payload's docs array and reviewer bindings never reach dispatch. A shell grant on a reviewer is therefore unused capability, not a requirement.

This is the SINGLE owner of the rule (sty_87c0ef37): the runtime dispatch refusal and `satelle agent validate` both call it, so they cannot disagree about any grant string. It carries its own quote-stripping tokenizer rather than reusing splitList so that a quoted TOML token ("Bash(satelle:*)") is judged identically on both paths.

func GrokConfigPath added in v0.0.163

func GrokConfigPath() (string, error)

GrokConfigPath returns ~/.grok/config.toml. os.UserHomeDir honors HOME, so tests can t.Setenv("HOME", tmp) without a satelle-specific override.

func GrokFolderTrusted added in v0.0.167

func GrokFolderTrusted(content, abs string) bool

GrokFolderTrusted reports whether content already marks abs as trusted=true.

func GrokTrustedFoldersPath added in v0.0.167

func GrokTrustedFoldersPath() (string, error)

GrokTrustedFoldersPath returns ~/.grok/trusted_folders.toml (HOME-honoring).

func HasKey added in v0.0.217

func HasKey(content, section, key string) bool

HasKey reports whether content already assigns `key` inside `section` (empty section = root block before the first table header). Distinguishes "key absent from the file" from "key present with empty value" — Load cannot (sty_c73f8905 AC6 config reconciliation).

func HostedServerFor added in v0.0.121

func HostedServerFor(gc GlobalConfig, repo Config) string

HostedServerFor applies the precedence rule: the global hosted server wins; the repo's committed [hosted] server is the read-only fallback for a repo that predates the global binding. Both are normalized so a "https://h/" value can never mismatch the credential-store key "https://h". Returns "" when neither is set.

func InstanceID added in v0.0.292

func InstanceID(globalDir string) string

InstanceID returns a stable short identity for a satelle home directory (GlobalDir). Serve exposes it on /healthz as X-Satelle-Instance so CLI auto-bootstrap only seeds a serve that belongs to the same home (sty_5aa08259 / epic:mirror-hygiene) — hermetic SATELLE_HOME runs cannot silently adopt the operator's live :8787 serve.

func IsInLoopCommand added in v0.0.360

func IsInLoopCommand(cmd string) bool

IsInLoopCommand reports whether a binding command is the in-loop preset (single token "in-loop", case-insensitive). An in-loop binding is performed by the driving session and never dispatched as a child, so it needs no context channel and cannot produce an isolated verdict. Empty command is NOT in-loop: tests and bootstrap leave command blank and wire a runner directly.

func IsRuntimeKeyDir added in v0.0.268

func IsRuntimeKeyDir(name string) bool

IsRuntimeKeyDir reports whether name is a home-keyed runtime key segment.

func LayerVars added in v0.0.365

func LayerVars(global, repo map[string]string) map[string]string

LayerVars returns the ${NAME} KV with the machine-wide catalog as the base and the repo's own [vars] (including its gitignored satelle.local.toml overlay) winning per key — the same per-key overlay shape the repo already uses for its own layers. Neither map is mutated.

func ListStringValues added in v0.0.272

func ListStringValues(content, section, key string) []string

ListStringValues returns the quoted string entries of a TOML array assignment for key inside section, in order. Returns nil when the key is absent or the RHS is not a bracketed list. Only double-quoted string elements are collected (the shape init seeds for edit_exempt_paths).

func ListValueContains added in v0.0.272

func ListValueContains(content, section, key, val string) bool

ListValueContains reports whether content's assignment of key inside section is a TOML string array that includes val (exact match after unquoting). False when the key/section is absent, the value is not a list of strings, or val is not among the quoted entries. Used by init analysis / migrate to detect a missing managed default entry without re-encoding the whole file.

func LocalOnlyPath added in v0.0.329

func LocalOnlyPath(p string) bool

LocalOnlyPath reports whether a path is one that must NEVER be transmitted: any path segment carrying a `.local` dot-component (satelle.local.toml, notes.local.md, secrets.local/keys.md). Unconditional — it takes no Config, so no setting can disable or narrow it. The restore-side counterpart is subsync.ExcludedLocal (which delegates here for the .local case).

A bare segment named "local" (skills/local/x.md) is an ordinary name and is not withheld; the match requires a multi-component segment whose non-stem component equals "local" (case-insensitive).

func MigrateAgents added in v0.0.205

func MigrateAgents(content string) (out string, changes []string, err error)

MigrateAgents rewrites format drift in an agents.toml body:

  • harness = X → command = X (skipped when command= already present)
  • bare command = "claude"|"grok" → full multi-token template
  • missing role= → role = "reviewer"|"agent" via ResolvedRole

Returns the migrated content and a list of change notes. When nothing is stale, out equals content byte-for-byte and changes is empty (idempotent). A parse error returns err; callers must leave the on-disk file intact.

func ReadRepoPathMarker added in v0.0.268

func ReadRepoPathMarker(runtimeDir string) string

ReadRepoPathMarker returns the recorded repo root for a runtime dir, or "" if absent.

func RemoveKey added in v0.0.330

func RemoveKey(content, section, key string) string

RemoveKey drops a single `key = …` assignment inside section, leaving every other line (including comments) verbatim. A missing section or key is a no-op. Used by redactForTransmit to strip [hosted] project from a settings push (sty_ea18294f) without a full TOML re-encode.

func RepoKey added in v0.0.261

func RepoKey(repoRoot string) string

RepoKey returns a stable directory key for repoRoot under GlobalDir(). Multiple worktrees / cwd forms of the same repository hash identically.

Derivation:

  1. Prefer git identity via `git rev-parse --git-common-dir` (worktree-collapsing: a linked worktree returns the main repo's .git).
  2. Fall back to symlink-resolved absolute path for non-git trees.

The key is `<sanitised-basename>-<sha256[:8]>` so `ls ~/.satelle` stays legible while collisions stay improbable. Never reads os.Getwd(); the root is an argument.

Note: a tree that gains a .git later (init → git init) can flip identity. Callers that need continuity across that flip should use ResolveRuntimeDir, which prefers an already-existing runtime dir under either key.

func RepoRootFromConfigPath

func RepoRootFromConfigPath(configPath string) string

RepoRootFromConfigPath derives the repo root (the dir holding .satelle/) from a <repo>/.satelle/satelle.toml path. Empty path → "." (CWD).

func ResolveAgents added in v0.0.365

func ResolveAgents(repo AgentsConfig, global GlobalAgentsConfig) (AgentsConfig, Provenance, error)

ResolveAgents folds the catalog into the repo layer and returns the effective agents config with its per-field provenance. A missing referenced profile, a reference cycle, or a repo/profile role conflict is an error — resolution never half-applies a broken reference.

func ResolveHostedServer added in v0.0.121

func ResolveHostedServer(repo Config) string

ResolveHostedServer is the convenience resolver for callers that hold only the repo config: it loads the global config (a malformed global degrades to the repo fallback so a render or read command never fails on it) and applies the precedence rule. The login WRITE path calls LoadGlobal directly so it can still surface a malformed-global error.

func ResolvedRole added in v0.0.189

func ResolvedRole(section string, b AgentBinding) string

ResolvedRole returns the binding's effective role: the declared Role when set to reviewer|agent (case-insensitive), else inferred from the section name (section "reviewer" → reviewer, otherwise agent). section is the agents.toml table name ([reviewer], [planner], …).

func RoleInferred added in v0.0.189

func RoleInferred(b AgentBinding) bool

RoleInferred reports whether role was not declared and will be inferred from the section name — used by agent validate/show to warn the operator to declare it.

func SafeCurrentInstanceID added in v0.0.292

func SafeCurrentInstanceID() string

SafeCurrentInstanceID is CurrentInstanceID but returns "" under go test when SATELLE_HOME is unset — so NewMirror in unit tests does not panic on GlobalDir's isolation fence (sty_c36c211f / sty_5aa08259).

func SaveConfigValues added in v0.0.115

func SaveConfigValues(configPath string, edits []KeyEdit) error

SaveConfigValues applies edits to the committed config at configPath, creating the file if absent. Each key is upserted in place; unrelated content is preserved. An empty configPath falls back to ./<DefaultDataDir>/<ConfigName>.

func SaveGlobal

func SaveGlobal(gc GlobalConfig) error

SaveGlobal writes the global config to ~/.satelle/config.toml, creating the dir as needed. It renders a documented template (a fixed, satelle-managed shape) from the resolved values rather than re-encoding, so the file stays readable and self-explanatory.

func SaveGlobalHostedServer added in v0.0.121

func SaveGlobalHostedServer(server string) error

SaveGlobalHostedServer records the machine-wide hosted-server URL in the global config, preserving the other sections (load-modify-save is correct here because SaveGlobal re-renders the whole managed template). It is the global analogue of the retired repo-file SaveHostedServer; `satelle login` calls it so one sign-in serves every repo. Tokens never pass through here — they go to the credential store.

func ServerEndpointEnv added in v0.0.368

func ServerEndpointEnv() (endpoint string, disabled bool, set bool)

ServerEndpointEnv inspects EnvServerEndpoint. disabled=true means discovery and push must not run. When disabled is false and endpoint is non-empty, callers should use it instead of config / probe.

func SettingDisplay added in v0.0.125

func SettingDisplay(cfg Config, s Setting) string

SettingDisplay returns the resolved value of a key as a display string (empty for an unset value; a bool renders "true"/"false"; a list/map renders one item per line). It is the single display entry point shared by the web page and the CLI.

func SettingIDs added in v0.0.125

func SettingIDs() []string

SettingIDs returns every settable repo key's FieldID, in schema order (for help and unknown-key errors).

func ShellGrantToken added in v0.0.360

func ShellGrantToken(tools string) string

ShellGrantToken returns the first token in a tool grant that confers shell access, or "" when none does. It is a REPORTING helper — it names which token a finding is about — and is deliberately not the channel decision, which GrantsContextChannel alone owns.

func SourceGlobalRole added in v0.0.365

func SourceGlobalRole(profile string) string

SourceGlobalRole labels a field won by the catalog's opt-in [roles] default.

func SourceProfile added in v0.0.365

func SourceProfile(profile string) string

SourceProfile labels a field won by an explicitly referenced catalog profile.

func StarterGlobalAgents added in v0.0.365

func StarterGlobalAgents(cli string) (string, error)

StarterGlobalAgents renders a documented starter catalog derived from the machine's selected agent CLI (`~/.satelle/config.toml [agent] cli`) — the backward-compatible migration path for an installation that predates the catalog (AC6). It is CONTENT ONLY: no repo is touched, no repo value is read, and no secret is embedded. Secrets stay in [vars] on this machine and reach a profile only as a ${NAME} reference, expanded in memory at dispatch wiring.

The generated catalog changes nothing on its own: a repo picks it up only by writing `profile = "<name>"`.

func UpsertKey added in v0.0.115

func UpsertKey(content, section, key, value string) string

UpsertKey sets `key = value` within section (empty section = the root block before the first table header), replacing an existing assignment in place, appending the key to an existing section, or appending a new [section]. Comments and unmodeled keys/tables are preserved.

func WriteRepoPathMarker added in v0.0.268

func WriteRepoPathMarker(runtimeDir, repoRoot string) error

WriteRepoPathMarker records repoRoot into runtimeDir/repo.path so `satelle runtime list` can reverse-map keys. Best-effort: no-op when runtimeDir is empty or equals the repo's data dir (legacy layout).

Types

type ActiveWorkspace added in v0.0.152

type ActiveWorkspace struct {
	Name string
	Kind string
}

ActiveWorkspace is the workspace the sync engine routes personal+shared areas through. Name is the server workspace handle (the workspace name) the sync engine prefixes; Kind is personal (the default) or team.

func (ActiveWorkspace) IsPersonal added in v0.0.152

func (a ActiveWorkspace) IsPersonal() bool

IsPersonal reports whether the active workspace is the developer's own — the zero-config default, needing no binding.

type AgentBinding added in v0.0.12

type AgentBinding struct {
	// Interface is "command" | "acp". Empty means command. Unknown values fail at load.
	Interface string `toml:"interface"`
	// Command is the agent's spawn/template string.
	//   command transport: multi-token full argv template with {system}/{tools}/
	//     {model}/{settings}/{payload} (each its own argv token). Bare single-token
	//     only "in-loop"; bare claude/grok/codex rejected by agentvalidate.
	//   acp transport: ACP stdio spawn only (e.g. "grok agent stdio") — no
	//     {system}/{payload} placeholders (those ride the protocol).
	// Prefer over retired harness= (no runtime fallback; MigrateAgents rewrites).
	Command string `toml:"command"`
	// Harness is retired: still decoded for MigrateAgents; CommandTemplate ignores it.
	Harness string `toml:"harness"`
	Tools   string `toml:"tools"`
	Model   string `toml:"model"`
	// Role is "reviewer" | "agent". Empty means inferred from the section name
	// (see ResolvedRole). The binary's only hard determination for a reviewer is
	// the verdict contract; tool grant/model/command are user configuration.
	Role string `toml:"role"`
	// Principles selects which principles inject into the isolated briefing:
	// "session" (default) | "all" | "system" | "project" | "none" | comma-list.
	// Empty defaults to session. inject_principles is retired (MigrateAgents).
	Principles string `toml:"principles"`
	// Env sets environment variables on the dispatched agent's process (layered
	// onto os.Environ, binding keys winning). Each value may reference the [vars]
	// KV via ${NAME}, resolved at CLI wiring time (ResolveAgentEnvs) — how a step
	// points at an alternate model backend, e.g. env = { ANTHROPIC_BASE_URL =
	// "https://api.z.ai/api/anthropic", ANTHROPIC_AUTH_TOKEN = "${GLM_API_KEY}" }.
	// The in-loop executor never execs a child, so its Env is inert (sty_001558ce).
	Env map[string]string `toml:"env"`
	// Timeout bounds ONE dispatch of this binding — a Go duration string (e.g.
	// "45m"). Empty inherits the engine's default dispatch bound. A from-scratch
	// code-writing worker needs a longer window than the 20m default (a real
	// feature was SIGKILLed at exactly 20m — sty_b73c3236), so the bound is authored
	// config, not a compiled constant (sty_446c38b7). Applies to a DISPATCHED named
	// executor; reviewer/summariser gate invocations keep the engine's agent bound.
	Timeout string `toml:"timeout"`
	// InjectPrinciples is retired (MigrateAgents → principles=). Not used at runtime.
	InjectPrinciples *bool `toml:"inject_principles"`
	// Settings MIRRORS claude's settings.local.json schema (env, model, permissions)
	// verbatim — no derivation, no satelle-specific shape. It is materialised into
	// the {settings} placeholder: ${VAR}-resolved (ResolveAgentEnvs, fail-fast on an
	// unknown var, same as Env), JSON-marshalled, and passed INLINE as
	// `--settings <json>`. Because --settings is CLI-tier it OVERRIDES
	// .claude/settings.local.json, so a binding that authors Settings becomes the
	// authoritative provider/auth/permissions layer for that dispatch — e.g. moving
	// [retrospective]'s GLM env under settings.env fixes its silent clobber by the
	// repo's openrouter settings.local.json (that env block wins over a bare
	// process-env overlay, but never over --settings). A binding with no Settings
	// emits no --settings flag, exactly as an empty Model drops {model}.
	Settings map[string]any `toml:"settings"`
	// Effort is optional reasoning/thinking effort for the binding (sty_657f77b9):
	// e.g. "low" | "medium" | "high". Empty means peer default. Substituted into
	// {effort} on command templates (flag dropped when empty, like {model}) and
	// applied on ACP via session/set_config_option (failure-tolerant).
	Effort string `toml:"effort"`
	// Secondary names another agents.toml binding to retry once when this
	// binding's dispatch fails with a classified rate-limit/unavailable error
	// (sty_5bf61f89). Empty inherits [defaults] secondary. Empty both = no failover.
	Secondary string `toml:"secondary"`
	// Profile names a profile in the MACHINE-WIDE catalog ($SATELLE_HOME/
	// agents.toml) this binding builds on (sty_c7dfeedf). The reference is always
	// EXPLICIT: a catalog profile that happens to share this section's name is
	// never merged in on its own, so adding a profile can never silently change a
	// repo. Repo values on this binding win over the referenced profile field by
	// field; role is identity and must not disagree. A profile may itself set
	// profile= to extend another, with cycles refused at load. See ResolveAgents.
	Profile string `toml:"profile"`
}

AgentBinding binds one agent to a backend (how/where it runs) and its grant (the tool allowance, and an optional model). Empty fields take the defaults.

Role declares whether the binding is a reviewer (verdict contract) or an agent (performer). Principles declares which principles inject into the isolated briefing. InjectPrinciples is the DEPRECATED boolean alias for Principles (true→session, false→none); Principles wins when both are set.

Interface selects the dispatch transport (epic:agent-dispatch-transport): "command" (default) or "acp". Shared grant fields apply to both; spawn shape differs.

func (AgentBinding) CommandTemplate added in v0.0.180

func (b AgentBinding) CommandTemplate() string

CommandTemplate resolves the effective command template for this binding. Only `command` is read — the deprecated `harness` field is no longer a runtime fallback (breaking surface: run `satelle init` to MigrateAgents).

func (AgentBinding) InjectsPrinciples added in v0.0.18

func (b AgentBinding) InjectsPrinciples() bool

InjectsPrinciples reports whether this binding injects any principles (and the constitution order-zero block) into the isolated agent's context — true unless the resolved selector is none.

func (AgentBinding) IsACP added in v0.0.275

func (b AgentBinding) IsACP() bool

IsACP reports whether this binding uses the ACP transport.

func (AgentBinding) ResolvedInterface added in v0.0.275

func (b AgentBinding) ResolvedInterface() string

ResolvedInterface returns the effective dispatch transport: command (default) or acp. Unknown non-empty values are returned lowercased so LoadAgents / agentvalidate can reject them (epic:agent-dispatch-transport).

func (AgentBinding) ResolvedPrinciples added in v0.0.189

func (b AgentBinding) ResolvedPrinciples() string

ResolvedPrinciples returns the principles selector: Principles when set, else session. The deprecated inject_principles field is no longer a runtime fallback (breaking surface: run `satelle init` to MigrateAgents).

func (AgentBinding) TimeoutDuration added in v0.0.139

func (b AgentBinding) TimeoutDuration(def time.Duration) (time.Duration, error)

TimeoutDuration resolves this binding's dispatch bound: the parsed Timeout when set, else def. A malformed or non-positive Timeout is an error — LoadAgents validates it at load (validateTimeouts) so a dispatch never silently falls back on a typo (sty_446c38b7).

type AgentConfig added in v0.0.6

type AgentConfig struct {
	// CLI is the agent CLI identifier (claude | codex). Empty resolves to
	// DefaultAgentCLI.
	CLI string `toml:"cli"`
}

AgentConfig selects the headless agent CLI the quality-management spine uses for isolated reviews/summaries. Set once at install (`satelle agent`).

func (AgentConfig) ResolveCLI added in v0.0.6

func (a AgentConfig) ResolveCLI() string

ResolveCLI returns the selected agent CLI, defaulting when unset.

type AgentsConfig added in v0.0.12

type AgentsConfig struct {
	Defaults AgentsDefaults          `toml:"defaults"`
	Executor AgentBinding            `toml:"executor"`
	Reviewer AgentBinding            `toml:"reviewer"`
	Agents   map[string]AgentBinding `toml:"agents"`
}

AgentsConfig is the on-disk shape at .satelle/agents.toml — the agents layer. Every field is optional; the *Binding resolvers supply today's defaults, so the zero value (and an absent file) is the current behaviour. Agents holds OPTIONAL named agents (beyond the executor/reviewer roles) declared as flat top-level [<name>] sections — consistent with [executor]/[reviewer] — or the legacy nested [agents.<name>] (still read for back-compat). A workflow node may allocate a step to one, and a named agent is ALWAYS isolated (see satelle-agent-model). LoadAgents does the classification; the toml tag here is retained only for the legacy nested form.

func LoadAgents added in v0.0.12

func LoadAgents(dataDir string) (AgentsConfig, error)

LoadAgents reads the agents layer from <dataDir>/agents.toml. The legacy actors.toml is no longer read (sty_7db2ed7d); an absent agents.toml yields the zero AgentsConfig — defaults via the *Binding resolvers — and a nil error. Absence is judged by the CALLER: the CLI bootstrap treats a missing file in an initialized repo as broken and refuses to run (requireAgents, sty_d0d6bb67); pre-init surfaces (nothing to load yet) keep the zero-config bootstrap.

func ResolveAgentEnvs added in v0.0.129

func ResolveAgentEnvs(a AgentsConfig, vars map[string]string) (AgentsConfig, error)

ResolveAgentEnvs returns a copy of the agents layer with every binding's Env AND Settings values ${NAME}-expanded against vars (executor, reviewer, and each named agent). Resolution happens ONCE, at CLI wiring time (fail-fast, the repo's established style): a broken ${VAR} in any binding refuses the command with an actionable message rather than sitting undetected until a dispatch. A binding with no Env/Settings is a no-op, so a zero-config repo stays zero-config. The error names the binding section and key but NEVER the value (values may be secrets — kept out of all evidence, logs, and errors).

func (AgentsConfig) ExecutorBinding added in v0.0.12

func (a AgentsConfig) ExecutorBinding() AgentBinding

ExecutorBinding resolves the executor agent's backend, defaulting to in-loop (the driving agent itself).

func (AgentsConfig) NamedBinding added in v0.0.12

func (a AgentsConfig) NamedBinding(name string) (AgentBinding, bool)

NamedBinding resolves an optional named agent declared as a flat top-level [<name>] section (or the legacy nested [agents.<name>]). ok is false when none is declared, so a workflow node that allocates a step to an absent agent degrades gracefully to the in-loop executor. A named agent is always isolated; an unset command defaults to the isolated claude preset.

func (AgentsConfig) ResolveSecondary added in v0.0.294

func (a AgentsConfig) ResolveSecondary(section string, b AgentBinding) (sec AgentBinding, name string, ok bool)

ResolveSecondary returns the fallback binding for section/b when secondary is configured (per-binding wins over [defaults] secondary). ok is false when unconfigured or the named binding is missing (sty_5bf61f89).

func (AgentsConfig) ReviewerBinding added in v0.0.12

func (a AgentsConfig) ReviewerBinding() AgentBinding

ReviewerBinding resolves the reviewer agent's backend and grant, defaulting to an isolated agent with the read-only tool grant. The grant travels with the binding, so the reviewer's read-only limit holds whatever the backend.

type AgentsDefaults added in v0.0.294

type AgentsDefaults struct {
	// Secondary is the default fallback binding name for isolated agents when
	// a binding omits secondary= and the primary hits rate-limit/unavailable.
	Secondary string `toml:"secondary"`
	// UseGlobalRoles opts this repo into the machine-wide catalog's [roles]
	// defaults for bindings that name no profile= of their own (sty_c7dfeedf).
	// It is off by default and must be written by hand: without it, tier 3 of the
	// precedence ladder is skipped entirely and the catalog can only reach a
	// binding that explicitly asks for it.
	UseGlobalRoles bool `toml:"use_global_roles"`
}

AgentsDefaults is the optional [defaults] table in agents.toml (sty_5bf61f89).

type BackupConfig added in v0.0.230

type BackupConfig struct {
	// LocalOnly means keep backups under .satelle/backups/ only and do not emit
	// the "enable online backup" advisory. Prefer satelle.local.toml for a
	// per-developer choice.
	LocalOnly bool `toml:"local_only"`
	// Hosted opts into pushing pre-images to the bound project's personal
	// documents partition (path backups/<rel>). Default false: that partition
	// is also listed by documents pull, and backups/ is a restore exclusion —
	// auto-push used to permanently wedge sync (sty_84f14ace). Operators who
	// set hosted = true re-introduce poison into the partition but, post-
	// unwedge, pull only skips those paths rather than failing the whole sync.
	Hosted bool `toml:"hosted"`
}

BackupConfig is the operator policy for pre-mutation substrate backups (sty_873a5380, sty_84f14ace). Local floor is always on; this tunes the advisory and the opt-in hosted documents push.

type Bundle added in v0.0.329

type Bundle struct {
	Files    []ConfigFile
	Withheld []string // sorted, server-relative
}

Bundle is an assembled set of files destined for transmission, plus the server-relative paths withheld by LocalOnlyPath so a caller can REPORT them. Work-state push transmits DB rows (no on-disk filename), so it is out of scope for this exclusion by deliberate decision — not a gap.

func ConfigFiles added in v0.0.153

func ConfigFiles(cfg Config, repoRoot string) (Bundle, error)

ConfigFiles walks the ConfigAreas under repoRoot, resolving each file's scope via ScopeFor. Local-scope areas contribute nothing. Non-local files are labeled PersonalTier or SharedTier for operator messaging / publish eligibility — both still sync to the personal workspace only (epic:sync-publish). A shared-scope area is SharedTier wholesale; a personal-scope area is PersonalTier per file, promoted to SharedTier when FileShared (frontmatter shared:true). Reserved generated views (index.md/log.md/README) are excluded — they are not authored. Paths matching LocalOnlyPath are withheld (listed in Bundle.Withheld, never in Files). Files are returned sorted by Path. A non-existent area on disk is benign (nothing to push yet). An explicitly invalid scope is a hard error.

type CategoriesConfig added in v0.0.328

type CategoriesConfig struct {
	// Vocabulary, when non-empty, REPLACES the embedded default list entirely.
	Vocabulary []string `toml:"vocabulary"`
	// Extra appends to the resolved base (embedded or Vocabulary).
	Extra []string `toml:"extra"`
	// Enforce is off | warn | reject. Empty means warn.
	Enforce string `toml:"enforce"`
}

CategoriesConfig is the optional [categories] table in satelle.toml.

[categories]
enforce = "warn"                    # off | warn | reject (default warn)
extra = ["my-type"]                 # ADD to the embedded default list
# vocabulary = ["fix", "feature"]  # REPLACE the embedded default list

type Config

type Config struct {
	// DataDir is the per-repo store home; empty means DefaultDataDir. A
	// relative value resolves under the repo root, never the process CWD.
	DataDir string `toml:"data_dir"`
	// DB overrides the database path; empty means <data_dir>/satelle.db. A
	// relative value resolves under the repo root.
	DB string `toml:"db"`
	// SubstrateRoots maps an authored kind to the parent dir holding it. UNSET
	// for a kind means the default <data_dir>/<kind>. An absolute value lets a
	// kind's source live anywhere on disk.
	SubstrateRoots map[string]string `toml:"substrate_roots"`
	// WebPort is the local web server port; zero means DefaultWebPort.
	WebPort int `toml:"web_port"`
	// LogLevel is arbor's level (debug|info|warn|error); empty means info.
	LogLevel string `toml:"log_level"`
	// LogsMaxSizeKB caps each flat evidence log under .satelle/logs before it rolls;
	// zero means DefaultLogsMaxSizeKB. LogsMaxFiles bounds kept rotations; zero means
	// DefaultLogsMaxFiles. Daily rolling is always on.
	LogsMaxSizeKB int `toml:"logs_max_size_kb"`
	LogsMaxFiles  int `toml:"logs_max_files"`
	// StoriesKeepClosed keeps the N most-recently-updated CLOSED-story attachment
	// dirs under .satelle/stories; 0 (default) disables count-based pruning.
	// StoriesKeepDays prunes a closed story's dir when the story's terminal update
	// is older than N days; 0 (default) disables age-based pruning. The two
	// compose — either triggers pruning. A NON-terminal story's dir is ALWAYS kept
	// regardless of either setting. Pruning MOVES the dir to
	// .satelle/backups/stories/ (never deletes in place). (sty_aba7200c)
	StoriesKeepClosed int `toml:"stories_keep_closed"`
	StoriesKeepDays   int `toml:"stories_keep_days"`
	// Review opts this repo into reviewer-gated create. satelle init seeds
	// gate_create = true (misclassification is cheapest to catch at create —
	// sty_83782ffb); set false to opt out. Absent key stays false so older
	// configs without [review] do not silently change until re-init/edit.
	Review ReviewConfig `toml:"review"`
	// Gate tunes the PreToolUse edit gate (the `satelle hook gate` handler).
	Gate GateConfig `toml:"gate"`
	// Hosted records the hosted-server binding for `satelle login` — the server
	// URL and the project slug. Both are committed (secret-free); the OAuth
	// access/refresh TOKENS are NEVER stored here — they live in the user-level
	// credential store outside the repo (see internal/hosted). (sty_2fc93374)
	Hosted HostedConfig `toml:"hosted"`
	// Server is the LOCAL push-fed UI server endpoint the CLI publishes mutation
	// events to (epic:serve-split / sty_126228b2). Distinct from Hosted (the
	// remote satelle-server tier). Unset = change publisher inert (no network).
	Server ServerConfig `toml:"server"`
	// Backup tunes pre-mutation substrate backups (sty_873a5380). Local copies
	// under .satelle/backups/ are always written; LocalOnly suppresses the
	// advisory about enabling online/personal backup when no hosted channel is
	// configured.
	Backup BackupConfig `toml:"backup"`
	// Vars is the operator KV an agents.toml binding's env values substitute via
	// ${NAME} (sty_001558ce) — how a dispatched step points at an alternate model
	// backend (e.g. GLM's Anthropic-compatible endpoint) without a wrapper binary.
	// NON-secret vars MAY live in the committed satelle.toml; SECRETS (API keys)
	// go in the gitignored satelle.local.toml overlay, whose keys win per-key. The
	// KV is file-only — never persisted to the DB and never included in a substrate
	// push (satelle.local.toml is excluded), so secrets do not leave the machine.
	Vars map[string]string `toml:"vars"`
	// Sync maps a .satelle area name (see SyncAreas) to its scope on the
	// local|personal|shared ladder (epic:scoped-sync). Unset means "local" —
	// nothing syncs without explicit opt-in. A committed [sync] table sets team
	// defaults; satelle.local.toml overrides per-area for a single developer, the
	// same per-key overlay merge as Vars (sty_a2d2e057).
	Sync map[string]string `toml:"sync"`
	// Tags holds the optional controlled-vocabulary table for work-item tags
	// (sty_034d843c / epic:surface-scoped-steps). Declared namespaces reject
	// unknown values at story/task create and set; absent/empty means every tag
	// is free-form. The vocabulary is REPO CONFIG — the binary never hardcodes
	// which namespaces or values exist.
	Tags TagsConfig `toml:"tags"`
	// Categories holds the optional controlled category vocabulary (sty_b2315e17).
	// Default list ships embedded (substrate/config/categories.toml); satelle.toml
	// may extend (extra) or replace (vocabulary). Enforce is off|warn|reject
	// (default warn). Values are never a Go literal.
	Categories CategoriesConfig `toml:"categories"`
}

Config is the on-disk shape at .satelle/satelle.toml. Every field is optional; the Resolve* methods supply defaults so the zero value is valid.

func Load

func Load(explicitPath string) (Config, string, error)

Load resolves and parses the config, applying the satelle.local.toml overlay. It returns the Config, the resolved committed-config path (for repo-root derivation), and any error. A missing config is ErrNotFound — callers may treat that as "use the zero-value Config" for zero-config operation.

func (Config) CanonicaliseCategory added in v0.0.328

func (c Config) CanonicaliseCategory(cat string) (string, error)

CanonicaliseCategory validates cat against the resolved vocabulary and rewrites a match to the declared casing.

Rules:

  • Empty cat: pass through (structure still requires non-empty when gated).
  • EqualFold match: return the declared casing (every enforce mode).
  • No match + enforce reject: named error listing allowed values.
  • No match + warn/off: return cat unchanged (CLI surfaces warn separately).

func (Config) CanonicaliseTags added in v0.0.249

func (c Config) CanonicaliseTags(tags []string) ([]string, error)

CanonicaliseTags validates tags against the configured vocabulary and rewrites matching tags to the casing declared in config.

Rules:

  • No colon, or namespace not in Vocabulary → pass through unchanged.
  • Namespace present: value must EqualFold-match an allowed value; emit namespace:declaredValue (canonical casing from config).
  • No match: named error listing the allowed values (ParseScope-style).
  • Empty/absent Vocabulary: return tags unchanged, nil error.

The word for any particular namespace (e.g. surface) never appears here — only the generic mechanism. Values and namespaces come from config alone.

func (Config) CategoryEnforce added in v0.0.328

func (c Config) CategoryEnforce() string

CategoryEnforce returns the effective enforcement phase: off | warn | reject. Empty/unknown defaults to warn so an upgrade never silently hard-rejects.

func (Config) CategoryWarning added in v0.0.328

func (c Config) CategoryWarning(cat string) string

CategoryWarning returns a non-empty advisory when cat is unknown and enforce is warn; empty otherwise. Used by the CLI create/set path (stderr, exit 0).

func (Config) LegacyRuntimeNote added in v0.0.261

func (c Config) LegacyRuntimeNote(repoRoot string) string

LegacyRuntimeNote is the one-line deprecation message when a repo still has its database under the authored data dir. Empty when not legacy.

func (Config) ResolveActiveWorkspace added in v0.0.152

func (c Config) ResolveActiveWorkspace() ActiveWorkspace

ResolveActiveWorkspace resolves the active workspace from [hosted] workspace. Unset, blank, or the personal sentinel resolves to the personal default — zero-config. Any other value is a team workspace the developer elected: an explicit value is never the personal default, because that needs no binding, so Kind is derivable (personal iff the value is the sentinel).

The satelle.local.toml overlay is already merged by Load, so a per-developer choice recorded there overrides a committed team default transparently.

func (Config) ResolveAuthoredDirs

func (c Config) ResolveAuthoredDirs(repoRoot string) map[string]string

ResolveAuthoredDirs returns kind→absolute-dir for every AuthoredKind, with [substrate_roots] overrides applied over the <data_dir>/<kind> default. An override may be absolute, placing a kind's source anywhere on disk.

func (Config) ResolveCategoryVocabulary added in v0.0.328

func (c Config) ResolveCategoryVocabulary() []string

ResolveCategoryVocabulary returns the effective allowed category values: Vocabulary (if set) else EmbeddedCategories, then Extra, deduped case-insensitively (first casing wins). Never a Go-hardcoded list.

func (Config) ResolveConstitution added in v0.0.36

func (c Config) ResolveConstitution(repoRoot string) string

ResolveConstitution returns the absolute path to the repo's project constitution — <data_dir>/constitution.md — the order-zero doc injected every session (epic:session-context). Read directly (it is not an indexed kind).

func (Config) ResolveDB

func (c Config) ResolveDB(repoRoot string) string

ResolveDB returns the absolute sqlite database path. An explicit db wins; otherwise the home-keyed runtime plane (~/.satelle/<repo-key>/satelle.db), falling back to a still-unmigrated legacy <data_dir>/satelle.db when present (sty_4660bbe1 / epic:substrate-planes). See ResolveRuntimeDir.

func (Config) ResolveDataDir

func (c Config) ResolveDataDir(repoRoot string) string

ResolveDataDir returns the absolute data dir for repoRoot.

func (Config) ResolveEditExemptPaths added in v0.0.104

func (c Config) ResolveEditExemptPaths(repoRoot string) []string

ResolveEditExemptPaths returns the configured [gate] edit_exempt_paths as absolute prefixes under repoRoot. Blank entries are DROPPED (a blank prefix would classify every edit as exempt and silently disable the gate — see withinRoot's fail-open-toward-inside default), and an absolute entry passes through unchanged. Returns nil when nothing is configured, so the gate exempts nothing and every in-repo edit requires an engaged story (sty_8c3d345c).

func (Config) ResolveLogLevel

func (c Config) ResolveLogLevel() string

ResolveLogLevel returns the log level, defaulting empty to info.

func (Config) ResolveLogsMaxFiles added in v0.0.40

func (c Config) ResolveLogsMaxFiles() int

ResolveLogsMaxFiles returns how many rotated flat-log files to keep per log.

func (Config) ResolveLogsMaxSizeBytes added in v0.0.40

func (c Config) ResolveLogsMaxSizeBytes() int64

ResolveLogsMaxSizeBytes returns the per-file size cap (bytes) for the flat evidence logs under .satelle/logs, before a file rolls.

func (Config) ResolveRuntimeDB added in v0.0.261

func (c Config) ResolveRuntimeDB(repoRoot string) string

ResolveRuntimeDB returns the absolute path of the per-repo sqlite database under the resolved runtime dir. An explicit `db` config still wins via ResolveRuntimeDir.

func (Config) ResolveRuntimeDir added in v0.0.261

func (c Config) ResolveRuntimeDir(repoRoot string) RuntimeResolution

ResolveRuntimeDir returns where runtime state lives for repoRoot.

Policy (sty_4660bbe1):

  • An explicit `db` override wins: runtime dir is the parent of that path.
  • Prefer an existing home-keyed dir: first the current RepoKey (git-aware), then the path-only key (so a tree that was inited non-git and later gained a .git keeps its original ledger).
  • Else, if a legacy <data_dir>/satelle.db exists, return the legacy dir with Legacy=true so open keeps working and callers can emit a deprecation note (no silent move — migrate is an explicit command).
  • Else (fresh repo) return the home-keyed path under the current RepoKey.

data_dir overrides do NOT relocate runtime; they only relocate authored substrate.

func (Config) ResolveWebPort

func (c Config) ResolveWebPort() int

ResolveWebPort returns the web port, defaulting when unset.

type ConfigFile added in v0.0.153

type ConfigFile struct {
	Area    string     // the config-area name (skills, constitution, agents, tasks, ...)
	Path    string     // server-relative path under the workspace-config root
	Tier    ConfigTier // PersonalTier | SharedTier (label only; sync dest is personal)
	Content []byte     // verbatim file bytes
}

ConfigFile is one resolved authored-config file destined for the versioned store. Path is server-relative (forward slashes, no leading ".satelle/", e.g. "skills/my-skill.md", "agents.toml", "constitution.md", "tasks/tsk_x.md") — the key the server stores the file under, stable regardless of where a kind lives on disk via [substrate_roots].

type ConfigTier added in v0.0.153

type ConfigTier int

ConfigTier flags whether a file is eligible to be exposed via satelle publish (SharedTier) or stays purely personal (PersonalTier). It is NOT a sync push destination — satelle sync always writes to the personal workspace; Tier == SharedTier only triggers an operator note (epic:sync-publish). A LocalScope area never reaches tier resolution — it is skipped wholesale before any file is read, so there is no LocalTier.

const (
	// PersonalTier is ordinary personal-scope content (sync destination: personal).
	PersonalTier ConfigTier = iota
	// SharedTier marks [sync] area = shared or frontmatter shared:true. Sync still
	// pushes personal-only; the CLI emits a note pointing operators at satelle
	// publish for team catalog exposure (not dual-destination).
	SharedTier
)

func (ConfigTier) String added in v0.0.153

func (t ConfigTier) String() string

String renders the tier as its push/deploy label.

type EffectiveAgents added in v0.0.365

type EffectiveAgents struct {
	// Agents is the resolved layer — the same shape LoadAgents returns, so every
	// downstream consumer (dispatch, gates, validate) is unchanged.
	Agents AgentsConfig
	// Provenance is the per-field source table for display and validation.
	Provenance Provenance
	// Vars is the catalog's [vars] layered UNDER the repo's, repo keys winning.
	// Expansion still happens in memory at wiring time (ResolveAgentEnvs) — a
	// catalog secret is never written into a repository.
	Vars map[string]string
}

EffectiveAgents is the resolved agents layer plus what a caller needs to explain and expand it: where each field came from, and the layered ${VAR} KV.

func LoadEffectiveAgents added in v0.0.365

func LoadEffectiveAgents(dataDir string, repoVars map[string]string) (EffectiveAgents, error)

LoadEffectiveAgents is the SINGLE resolution site: read the repo layer, read the machine-wide catalog, and fold them by the documented precedence. Every surface that needs the effective agents layer calls this rather than merging on its own — two independently-merging call sites is the defect this exists to prevent. repoVars is the repo's [vars] KV (may be nil).

func ResolveEffectiveAgents added in v0.0.365

func ResolveEffectiveAgents(repo AgentsConfig, global GlobalAgentsConfig, repoVars map[string]string) (EffectiveAgents, error)

ResolveEffectiveAgents folds an already-loaded repo layer and catalog. Split from LoadEffectiveAgents so tests and multi-repo fixtures can resolve without disk, and so the precedence logic has no I/O in it.

type EmbeddedDefault added in v0.0.6

type EmbeddedDefault struct {
	Kind string // workflows | principles | skills | tasks (the substrate subdir)
	Name string // filename without the .md extension
	Body string // raw markdown
}

EmbeddedDefault is one canonical default artifact carried in the binary.

func EmbeddedDefaults added in v0.0.6

func EmbeddedDefaults() []EmbeddedDefault

EmbeddedDefaults returns every embedded default artifact, across all kinds. These are the canonical SEED + reference: init materialises them onto disk, the doc index resolves them by name as a Get fallback (e.g. the gating baseline), and validate compares against them — but they are NOT overlaid into List/Count, so an embedded default is never enumerated as a project doc (sty_94da9ac9).

type GateConfig added in v0.0.104

type GateConfig struct {
	EditExemptPaths       []string            `toml:"edit_exempt_paths"`
	AllowOutsideTreeEdits bool                `toml:"allow_outside_tree_edits"`
	CommandAllow          map[string][]string `toml:"command_allow"`
}

GateConfig tunes the PreToolUse edit gate and the single-story process rule. EditExemptPaths lists repo-relative (or absolute) path prefixes whose edits are exempt from the engaged-story gate. It is the SOLE exemption source: the binary does NOT special-case the data dir or any managed path (configuration over code, the constitution). `satelle init` seeds ".satelle/" and ".gitignore" here so authored substrate and satelle-managed .gitignore output (init/migrate write its managed block) stay editable without a release OOTB, but the operator owns the list — add a harness authoring dir (e.g. ".claude/", which holds authored skills, not product code) or drop either default to gate those paths too. Empty means everything in-repo requires an engaged story (sty_8c3d345c / sty_f115e6bf).

One performing story at a time is always enforced (sty_c7149f8a) — there is no allow_parallel opt-out (removed sty_a614a0ea).

AllowOutsideTreeEdits opts INTO Bash/Edit mutations whose targets resolve inside another git working tree (root differs from the session-home anchor; sty_a8454d10 / sty_aadd4d6c). Non-repo paths (temp, scratchpads) are never fenced. Default false = deny foreign-tree mutations (create stories in other repos stays allowed; progressing/mutating them from this session does not). true only for a deliberately multi-repo install — it relaxes containment only; the engaged-story rule is untouched.

CommandAllow is an OPT-IN step-scoped command policy (sty_c21490cc). Keys are git subcommands (e.g. "push", "commit"); values are story statuses that may run them while engaged. Empty/nil = no step restriction (today's engage-only commitgate). Example:

[gate.command_allow]
push = ["release"]

Not a satelle default — the operator authors the policy per repo.

type GlobalAgentsConfig added in v0.0.365

type GlobalAgentsConfig struct {
	// Vars is the machine-wide base of the ${NAME} KV. A repo's [vars] (and its
	// gitignored satelle.local.toml overlay) win per key. Secrets belong here on
	// the operator's machine — they are expanded in memory at wiring time and are
	// never written into a repository.
	Vars map[string]string
	// Profiles are the named reusable execution profiles, keyed by profile name.
	Profiles map[string]AgentBinding
	// Roles maps a logical role (reviewer|agent) to a default profile name. It is
	// consulted ONLY for a repo that opts in with [defaults] use_global_roles.
	Roles map[string]string
}

GlobalAgentsConfig is the on-disk shape of the machine-wide catalog.

[vars]                      # base KV for ${NAME} in profile env/settings;
                            # a repo's own [vars] override per key.
[profiles.<name>]           # one reusable execution profile (an AgentBinding)
[roles]                     # OPT-IN per-role defaults: reviewer = "<profile>"

Every section is optional; an absent file is the zero value and a nil error, so a machine that never creates one behaves exactly as before.

func LoadGlobalAgents added in v0.0.365

func LoadGlobalAgents() (GlobalAgentsConfig, error)

LoadGlobalAgents reads the machine-wide profile catalog. An absent file is the zero value and a nil error (zero-config machines are unchanged); a present but malformed or policy-carrying file is an error, so a broken catalog fails loud rather than silently resolving to embedded defaults.

func ParseGlobalAgents added in v0.0.365

func ParseGlobalAgents(content string) (GlobalAgentsConfig, error)

ParseGlobalAgents decodes and validates a catalog body. Exported so the migration path and tests can check content without touching disk.

type GlobalConfig

type GlobalConfig struct {
	Service   ServiceConfig      `toml:"service"`
	Agent     AgentConfig        `toml:"agent"`
	Workspace WorkspaceConfig    `toml:"workspace"`
	UI        UIConfig           `toml:"ui"`
	Hosted    GlobalHostedConfig `toml:"hosted"`
}

GlobalConfig is the machine-wide config at ~/.satelle/config.toml.

func LoadGlobal

func LoadGlobal() (GlobalConfig, error)

LoadGlobal reads the global config, returning a zero-value GlobalConfig (which resolves to defaults) when the file is absent. A present-but-malformed file is an error.

type GlobalHostedConfig added in v0.0.121

type GlobalHostedConfig struct {
	Server string `toml:"server"`
}

GlobalHostedConfig binds this MACHINE to one hosted satelle-server — the single account the operator signs in to, shared across every repo (sty_53ccf845). Like UIConfig.Theme it follows the operator rather than a repo, so one `satelle login` signs in everywhere. Secret-free: the OAuth tokens live in the per-user credential store outside any config (internal/hosted), never here.

func (GlobalHostedConfig) ResolveServer added in v0.0.121

func (h GlobalHostedConfig) ResolveServer() string

ResolveServer returns the global hosted-server URL, trimmed of whitespace and a trailing slash so it matches the credential-store key exactly. Empty when unset.

type HostedConfig added in v0.0.113

type HostedConfig struct {
	// Server is the hosted-server base URL (e.g. https://hosted.satelle.dev).
	Server string `toml:"server"`
	// Project is the hosted project slug this repo maps to. Personal sync
	// (config/documents/workstate) targets this project's collection on the
	// server — not a shared dump across every project under the personal
	// workspace (sty_0aa3df89). Written by `satelle project bind <slug>`.
	Project string `toml:"project"`
	// Workspace is the ACTIVE hosted workspace scoped sync routes personal+shared
	// areas through (epic:scoped-sync) — the handle the future sync engine
	// prefixes. Unset (or the personal sentinel) means the developer's OWN
	// workspace, the zero-config default; any other value is a team-workspace
	// NAME the developer elected. This is PER-DEVELOPER: `satelle login
	// --workspace <name>` records the choice in the gitignored satelle.local.toml
	// overlay, and a value committed HERE is a hand-authored TEAM DEFAULT the
	// overlay overrides per-key. Resolve via ResolveActiveWorkspace.
	Workspace string `toml:"workspace"`
}

HostedConfig binds a repo to a hosted satelle-server. Secret-free and committed in satelle.toml; the login tokens are stored per-user outside the repo (internal/hosted credential store), never here.

type KeyEdit added in v0.0.115

type KeyEdit struct {
	Section string
	Key     string
	Value   string
}

KeyEdit is one key to write. Section is the TOML table ("" = a root key, above the first table header). Value is the already-rendered TOML right-hand side (e.g. `"info"`, `8787`, `true`, `["a", "b"]`) — the caller owns typing/quoting.

type Provenance added in v0.0.365

type Provenance map[string]map[string]string

Provenance records, per binding section, the source of every effective field. Keys are the TOML field names (command, tools, model, …) so a display surface can render "field value (source)" without re-deriving anything.

func (Provenance) Fields added in v0.0.365

func (p Provenance) Fields(section string) []string

Fields returns the sourced field names for a section, sorted — a stable iteration order for rendering and snapshot tests.

func (Provenance) Source added in v0.0.365

func (p Provenance) Source(section, field string) string

Source returns the recorded source for one field, or "" when the field was never set by any tier (no value, so nothing to attribute).

type ReviewConfig added in v0.0.6

type ReviewConfig struct {
	// GateCreate runs the required-structure reviewer on story/task creation,
	// pushing non-conforming drafts back instead of persisting them.
	GateCreate bool `toml:"gate_create"`
}

ReviewConfig toggles the quality-management gates for a repo.

type RuntimeResolution added in v0.0.261

type RuntimeResolution struct {
	Dir    string // absolute path holding satelle.db, logs/, backups/, stories/
	Legacy bool   // true when Dir is still <data_dir> because migration has not run
}

RuntimeResolution is the result of ResolveRuntimeDir: the absolute runtime directory and whether it is the pre-migration legacy layout under data_dir.

type Scope added in v0.0.144

type Scope int

Scope is a position on the local -> personal -> shared sync ladder for a .satelle authored area.

const (
	// LocalScope never leaves this machine. The default for any area not
	// explicitly configured — nothing syncs without opt-in.
	LocalScope Scope = iota
	// PersonalScope syncs to the same operator's other machines.
	PersonalScope
	// SharedScope syncs to the whole team.
	SharedScope
)

func ParseScope added in v0.0.144

func ParseScope(s string) (Scope, error)

ParseScope parses a sync area value off the local|personal|shared ladder. An unrecognised value is a config error (satelle's fail-fast posture) rather than a silent fallback to local — a typo in an EXPLICITLY set area must not go unnoticed.

func ScopeFor added in v0.0.144

func ScopeFor(cfg Config, area string) (Scope, error)

ScopeFor resolves a configured area's scope. Precedence: an explicit sync <area> value wins; else the blanket sync all fallthrough; else LocalScope. A value outside local|personal|shared is a config error, never a silent local — for the per-area key when it is set, and for all only when an area actually falls through to it (a bad all beside a valid override does not error the overridden area).

func (Scope) String added in v0.0.144

func (s Scope) String() string

String renders the scope as its sync config value.

type ServerConfig added in v0.0.279

type ServerConfig struct {
	// Endpoint is the base URL of the local UI server (e.g. http://127.0.0.1:8787).
	// Empty means the change publisher is a no-op.
	Endpoint string `toml:"endpoint"`
}

ServerConfig points the CLI change publisher at a local read-only UI server (sty_126228b2). Secret-free; mechanism only — the binary carries no server opinion beyond POSTing events when Endpoint is set.

type ServiceConfig

type ServiceConfig struct {
	// Port the service listens on; zero means DefaultWebPort.
	Port int `toml:"port"`
	// Addr the service binds; empty means DefaultServiceAddr (0.0.0.0).
	Addr string `toml:"addr"`
	// Repo is the repository the service serves (its working directory). Empty
	// until set by `satelle service install`, which defaults it to the CWD.
	Repo string `toml:"repo"`
}

ServiceConfig configures the background web service (`satelle service`).

func (ServiceConfig) ResolveAddr

func (s ServiceConfig) ResolveAddr() string

ResolveAddr returns the service bind address, defaulting when unset.

func (ServiceConfig) ResolvePort

func (s ServiceConfig) ResolvePort() int

ResolvePort returns the service port, defaulting when unset.

type Setting added in v0.0.125

type Setting struct {
	Section string
	Key     string
	Label   string
	Help    string
	Kind    settingKind
	Enum    []string
}

Setting declares one committed-config key. Section "" is a root key (above the first table header). Enum holds the allowed values for kindEnum.

func SettingByID added in v0.0.125

func SettingByID(id string) (Setting, bool)

SettingByID looks up a Setting by its FieldID ("review.gate_create") OR its bare Key ("gate_create") — every bare key in the schema is unique across sections, so the short form is unambiguous and accepted for convenience.

func (Setting) EncodeValue added in v0.0.125

func (s Setting) EncodeValue(input string) (string, error)

EncodeValue validates a user-supplied value for this key and renders the TOML right-hand side for a KeyEdit (e.g. `"info"`, `8787`, `true`, `["a", "b"]`). Returns an error for an invalid value or a display-only key.

func (Setting) FieldID added in v0.0.125

func (s Setting) FieldID() string

FieldID is the dotted key path ("section.key", or the bare key for a root key).

type TagsConfig added in v0.0.249

type TagsConfig struct {
	// Vocabulary maps a tag namespace (without trailing colon) to the allowed
	// values for that namespace. Empty map / unset = no controlled namespaces.
	Vocabulary map[string][]string `toml:"vocabulary"`
}

TagsConfig holds the optional controlled-vocabulary table for work-item tags. Declared in satelle.toml as:

[tags.vocabulary]
surface = ["ui", "cli"]

A tag whose namespace is listed must use a declared value (matched case-insensitively, stored in the casing declared here). Namespaces absent from the table stay free-form.

type UIConfig added in v0.0.6

type UIConfig struct {
	Theme string `toml:"theme"` // "dark" | "light" (empty = light default)
}

UIConfig holds user-level UI preferences shared across every repo, so the light/dark choice follows the operator rather than a single browser origin.

type WorkspaceConfig added in v0.0.6

type WorkspaceConfig struct {
	Repos []string `toml:"repos"`
}

WorkspaceConfig is the connected-repo registry the workspace view aggregates. Per-repo databases stay the source of truth; this is just the list of paths.

func (*WorkspaceConfig) AddRepo added in v0.0.6

func (w *WorkspaceConfig) AddRepo(path string) bool

AddRepo adds an absolute repo path to the registry, de-duplicated. Reports whether it was newly added.

func (*WorkspaceConfig) RemoveRepo added in v0.0.6

func (w *WorkspaceConfig) RemoveRepo(path string) bool

RemoveRepo drops a repo path from the registry. Reports whether it was present.

Jump to

Keyboard shortcuts

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