Documentation
¶
Index ¶
- Constants
- func CentralProjectDir(projectName string) (string, error)
- func CentralRegistered(cfg Config, name string) bool
- func CentralStoreRoot() (string, error)
- func ConfigPath() (string, error)
- func DetectProjectPath(cwd string) string
- func IsConfigured() bool
- func ResolveName(cfg Config, cwd string, explicit string) (name string, source string)
- func ResolveWorkDir(ticketsDir string, cfg Config) string
- func Save(cfg Config) error
- func SharedConfigPath() (string, error)
- func SpawnCommand() (string, error)
- func StoreRootOverride() (string, bool, error)
- func ValidName(name string) bool
- func VerifyAllow() ([]string, error)
- type Config
- type ProjectConfig
- type VerifyAllowList
Constants ¶
const ( // StoreRootEnv names the environment variable that overrides the configured // central store root with an explicit one, so a test harness or an agent can // point tk — `tk serve` above all — at a throwaway store without touching the // real one. It is an environment variable rather than a cobra flag on purpose: // pkg/ticket and internal/mcp resolve config directly and never see cobra's // flags, and the env is the lever a harness has over a subprocess it spawns. StoreRootEnv = "TK_STORE_ROOT" )
Variables ¶
This section is empty.
Functions ¶
func CentralProjectDir ¶
CentralProjectDir returns <centralRoot>/tickets/<projectName>.
func CentralRegistered ¶ added in v7.7.1
CentralRegistered reports whether a project is registered with the central store. Presence in the merged config is not enough: `store: central` lives in the shared config alone — saveLocal writes only `path` per project once a central root is set — so a project whose shared config is missing (never cloned, or lost in a sync) merges to an entry with an empty store. Write authorization, read resolution, and the unregistered markers all key on this one predicate so they describe the same set of projects.
func CentralStoreRoot ¶
CentralStoreRoot returns the central ticket store root directory, or the TK_STORE_ROOT override when set — which is answered without reading any config file, so no configured store is touched. Returns an error if central_root is not configured — run `tk init` first.
func ConfigPath ¶
ConfigPath returns ~/.ticket/config.yaml, or the override root's own .ticket/config.yaml when TK_STORE_ROOT is set. Keeping the local config inside the override root mirrors the production layout and keeps an isolated run from reading or writing the machine's shared one.
func DetectProjectPath ¶
DetectProjectPath returns git top-level directory if available; otherwise cwd.
func IsConfigured ¶
func IsConfigured() bool
IsConfigured returns true if ~/.ticket/config.yaml exists and has central_root set, or if TK_STORE_ROOT is set — an override is configuration in itself, so an isolated run needs no local config file at all.
A set-but-unusable override still reports configured: it is a broken store root, not a missing configuration, and the CLI refuses it before this gate with an error naming the variable. Reporting it unconfigured here would send the reader to `tk init`, and falling through to the local config would resolve the real store.
func ResolveName ¶
ResolveName resolves project name with precedence: 1) explicit override 2) config path mapping 3) git remote name 4) directory name
Names are sanitized to prevent path traversal (no ".." or path separators).
func ResolveWorkDir ¶
ResolveWorkDir returns the project's real repo working directory for a tickets directory. With the central store, ticketsDir is <centralRoot>/tickets/<project> and its parent is the central tickets dir, NOT the repo — so the project's recorded `path` from config is used when available. Falls back to the parent of ticketsDir (correct for a local .tickets/ at the repo root) when the project has no configured path.
func Save ¶
Save writes the config to both local and shared files, splitting fields appropriately. Local gets top-level fields + per-project path. Shared gets per-project store, auto_link, auto_close, registered_at.
func SharedConfigPath ¶
SharedConfigPath returns <central_root>/config.yaml.
func SpawnCommand ¶ added in v7.8.0
SpawnCommand returns the TUI's spawn_command template, plus the error that made it unreadable. It reads ~/.ticket/config.yaml directly for the same reason VerifyAllow does: the template is handed to `sh -c`, so whoever supplies it runs code as the machine owner, and neither the synced shared config nor a TK_STORE_ROOT root the caller named may be that source.
An empty template is the caller's signal to use the built-in default, so a sandbox with no home config gets the machine owner's default rather than a template of its own.
func StoreRootOverride ¶ added in v7.8.0
StoreRootOverride returns the TK_STORE_ROOT store root, whether the variable is set at all, and the error a set-but-unusable value is. A value that is not an absolute path is that error and never falls back to the configured root: silently resolving the real central store from a broken override is the exact failure the override exists to prevent.
Presence is tested with LookupEnv rather than by comparing the value to "", because the empty string is a set-but-unusable value, not an absent one: a harness exporting a variable that expanded to nothing has named a store root tk cannot resolve, and treating it as unset would resolve the configured one.
func ValidName ¶ added in v7.7.1
ValidName reports whether a project name is safe to join into a filesystem path. Names containing path separators or ".." traverse out of the root they are joined into, and filepath.Join cleans those segments instead of failing; "" and "." collapse onto the root itself, rooting a project store at a directory no project owns. Names reach here from config, git remotes, and directory basenames as well as from the project half of a namespaced ticket ID.
func VerifyAllow ¶ added in v7.8.0
VerifyAllow returns the argv[0] allow-list a ticket's verify commands are checked against, plus the error that made it unreadable. It reads ~/.ticket/config.yaml directly rather than the merged config on purpose: the shared config lives inside the synced tickets repo, so whatever can push a malicious verify command there could widen the list meant to refuse it in the same push.
It reads the home path directly rather than ConfigPath for the same reason: TK_STORE_ROOT deliberately does not move this one setting. The override root belongs to whoever set the variable — a harness, or an agent — so relocating the allow-list there would let a sandbox widen it (`verify_allow: [sh]`), and a fresh sandbox with no config at all would silently restore the defaults over a list the machine owner had narrowed. The allow-list always comes from the machine owner's own config, so a sandbox can neither widen nor narrow it.
Three states are kept apart so the control fails closed. A local config that cannot be read or parsed — partial write, merge conflict markers, bad permissions — returns an empty list and the error, so nothing runs and the refusal can say why rather than silently restoring defaults over a list the user had narrowed. A `verify_allow` that is present but empty returns empty, which is how a user refuses everything. Only a genuinely absent key falls back to defaultVerifyAllow.
Types ¶
type Config ¶
type Config struct {
CentralRoot string `yaml:"central_root,omitempty" json:"central_root,omitempty"`
GitEmail string `yaml:"git_email,omitempty" json:"git_email,omitempty"`
GitName string `yaml:"git_name,omitempty" json:"git_name,omitempty"`
DefaultStore string `yaml:"default_store,omitempty" json:"default_store,omitempty"`
SyncInterval string `yaml:"sync_interval,omitempty" json:"sync_interval,omitempty"`
// SpawnCommand: read it via project.SpawnCommand(). The value here is the
// merged one, whose local half is the override root's config under
// TK_STORE_ROOT, and it exists for Save's round-trip only — never as a read
// path, because the template is the string handed to `sh -c` as the machine
// owner.
SpawnCommand string `yaml:"spawn_command,omitempty" json:"spawn_command,omitempty"`
// VerifyAllow: read it via project.VerifyAllow(), for the same reason and on
// the same terms — merged from a half the override root may own, kept here so
// Save round-trips it, and never a read path, because the list decides which
// programs run as the machine owner.
VerifyAllow VerifyAllowList `yaml:"verify_allow,omitempty" json:"verify_allow,omitempty"`
Projects map[string]ProjectConfig `yaml:"projects"`
}
Config stores tk project configuration (merged view of local + shared).
func Load ¶
Load reads both local (~/.ticket/config.yaml) and shared (<central_root>/config.yaml) configs, merging them into a single Config. Local fields (central_root, git_email, git_name, default_store, sync_interval, spawn_command, per-project path) come from local config. Shared fields (per-project store, auto_link, auto_close, registered_at) come from shared config. Missing files are not errors — returns what's available.
func (*Config) UpsertProject ¶
func (cfg *Config) UpsertProject(name string, project ProjectConfig)
UpsertProject inserts or updates a project entry.
type ProjectConfig ¶
type ProjectConfig struct {
Path string `yaml:"path,omitempty" json:"path,omitempty"`
Store string `yaml:"store,omitempty" json:"store,omitempty"`
AutoLink bool `yaml:"auto_link" json:"auto_link"`
AutoClose bool `yaml:"auto_close" json:"auto_close"`
RegisteredAt string `yaml:"registered_at,omitempty" json:"registered_at,omitempty"`
}
ProjectConfig stores per-project settings.
type VerifyAllowList ¶ added in v7.8.0
type VerifyAllowList []string
VerifyAllowList is the verify_allow setting. Absent and present-but-empty mean different things — fall back to the default, versus refuse everything — so it defines IsZero rather than letting omitempty collapse them: a save round-trip (project registration, for one) must not turn a user's explicit `verify_allow: []` back into an unset key, which would silently restore the defaults.
func (VerifyAllowList) IsZero ¶ added in v7.8.0
func (l VerifyAllowList) IsZero() bool
IsZero reports whether omitempty should drop the field. Only an absent list is dropped.