vconfig#
import "github.com/jbcom/radioactive-ralph/internal/vconfig"
Package vconfig implements the virtual-layer config resolution engine described in docs/superpowers/specs/2026-07-16-supervisor-architecture-design.md §5a. Configuration is never a single committed file: it resolves through two virtual layers built by the supervisor at runtime.
Virtual USER config (low -> high precedence):
DB user-scope config < --config-file < --user-config-file
Virtual PROJECTS config (per project):
all DB project config < projects: stanza from the virtual USER config
viper does the mechanical defaults<file merge (TOML) per layer; this package owns the DB layer, the two-layer USER->PROJECTS composition, the projects: stanza extraction, the change-vs-override distinction (see effective.go), and conflict diffing (see diff.go).
Index#
Constants#
Flag name constants, exported so callers wiring up cobra commands or reading flag values by name elsewhere don’t need to hardcode strings.
const (
FlagConfigFile = "config-file"
FlagConfigFileShort = "C"
FlagUserConfigFile = "user-config-file"
FlagProjectConfigFile = "project-config-file"
)
func AddFlags#
func AddFlags(cmd *cobra.Command)
AddFlags registers the three virtual-config-layer flags described in spec §5a as persistent flags on cmd:
–config-file / -C : joint config file; may carry a projects: stanza.
–user-config-file : user-specific config file; may also carry a projects: stanza.
–project-config-file : config for one specific project.
–project-config-file is IGNORED in –supervisor mode: the supervisor operates at the user/XDG level with the working directory irrelevant (spec §4), so there is no single “current project” for it to apply to. The supervisor entry point simply never reads this flag’s value; callers building the supervisor command should not surface it in help text as meaningful there, but AddFlags registers it unconditionally so the same flag set can be shared between the supervisor and dumb-client commands.
func AutoRemove#
func AutoRemove(incoming map[string]any, conflicts []Conflict) map[string]any
AutoRemove returns a copy of incoming with every conflicting key deleted, leaving only the keys that didn’t collide with a stored value. incoming itself is left untouched.
func FlagsFrom#
func FlagsFrom(cmd *cobra.Command) (configFile, userConfigFile, projectConfigFile string)
FlagsFrom reads back the three virtual-config-layer flag values registered by AddFlags. Missing/unregistered flags resolve to “”.
func FormatMissing#
func FormatMissing(missing []MissingField) string
FormatMissing renders missing as an actionable multi-line exit message. Returns “” when missing is empty.
func LoadFileValues#
func LoadFileValues(path string) (map[string]any, error)
LoadFileValues loads a standalone TOML file (a –project-config-file) and returns its top-level settings as a flat map[string]any. Exported so callers that need the incoming values BEFORE deciding whether to call EffectiveProject — e.g. the –init path’s DiffConflicts pre-check against the stored project config — can load the same file the same way without duplicating viper setup.
func UserScopeProjectID#
func UserScopeProjectID(ctx context.Context, st *store.Store) (string, error)
UserScopeProjectID returns the reserved store project id that backs USER-level (as opposed to per-project) DB-resident config, creating the backing projects row on first use. It is idempotent: subsequent calls resolve the same row via its fingerprint rather than creating duplicates.
type Conflict#
Conflict records one config key where a previously stored value and an incoming value (typically from a projects: stanza key in –config-file or –user-config-file) disagree. Used by the supervisor to warn: “these keys would be overridden: …; keep passing –project-config-file OR remove the conflicts (auto-remove? y/N)”.
type Conflict struct {
Key string
Stored any
Incoming any
}
func DiffConflicts#
func DiffConflicts(stored ProjectConfig, incoming map[string]any) []Conflict
DiffConflicts compares stored (the project’s current effective config, e.g. from ResolveProjects) against incoming (candidate values headed for that project, e.g. a projects: stanza entry) and reports every key present in both where the values differ. Keys only in one side are not conflicts — there’s nothing to override.
type Fingerprint#
Fingerprint is a re-export of store.Fingerprint so callers of vconfig never need to import internal/store just to build fingerprints for UserScopeProjectID.
type Fingerprint = store.Fingerprint
type MissingField#
MissingField describes one required config key absent (or present but empty) from a merged ProjectConfig.
type MissingField struct {
Key string
Reason string
}
func Validate#
func Validate(cfg ProjectConfig, required []string) []MissingField
Validate checks cfg against required, reporting one MissingField per key that is absent or holds an empty value (“”, nil, or an empty string-keyed/slice value). The merged layer (DB < configFile < userConfigFile < projectConfigFile, per EffectiveProject) is what callers should pass in — Validate itself does no layering.
type Mode#
Mode distinguishes a persisted config CHANGE from a runtime-only OVERRIDE. See spec §5a “change vs. override — the load-bearing distinction”.
type Mode int
const (
// ModeChange persists merged project-config keys back to the store.
// Used for the headless/TUI init wizard and an explicit --init.
ModeChange Mode = iota
// ModeOverride merges config on top of the effective project config
// at runtime only; nothing is written to the store. Used for normal
// (non-init) client runs that pass --project-config-file.
ModeOverride
)
func (Mode) String#
func (m Mode) String() string
String renders the mode name for logs/errors.
type ProjectConfig#
ProjectConfig is a resolved virtual PROJECTS layer for one project: DB project config overlaid by the projects: stanza entry for that project (ResolveProjects), and optionally further overlaid by a –project-config-file (EffectiveProject).
type ProjectConfig struct {
Values map[string]any
}
func EffectiveProject#
func EffectiveProject(ctx context.Context, st *store.Store, projectsCfg ProjectConfig, projectID, projectConfigFile string, mode Mode) (ProjectConfig, error)
EffectiveProject computes the final, effective config for one project: projectsCfg (the virtual PROJECTS layer from ResolveProjects) optionally overlaid by projectConfigFile.
projectConfigFile is optional; an empty string returns projectsCfg unchanged. When non-empty, its keys are always merged into the returned config’s Values. The mode governs persistence, per spec §5a “change vs. override”:
ModeChange: the merged keys are ALSO persisted to the store via st.SetProjectConfig, so this becomes the project’s new baseline (used by the init wizard / explicit –init).
ModeOverride: runtime-only. Nothing is written to the store; the project’s stored initialization is left untouched.
–project-config-file is ignored in –supervisor mode (the supervisor path simply never calls EffectiveProject with a projectConfigFile) — see spec §5a and the AddFlags doc comment.
func EffectiveProjectFromValues#
func EffectiveProjectFromValues(ctx context.Context, st *store.Store, projectsCfg ProjectConfig, projectID string, overlay map[string]any, mode Mode) (ProjectConfig, error)
EffectiveProjectFromValues is EffectiveProject’s core: merge (and, under ModeChange, persist) overlay directly, without going through a file on disk. Exported so a caller that has already computed the overlay it wants applied — e.g. the –init path’s conflict UX, which may want to apply an vconfig.AutoRemove-filtered subset of an incoming –project-config-file rather than the file’s values verbatim — can reuse the exact same merge/persist semantics EffectiveProject uses, instead of round-tripping the filtered map back through a TOML file just to satisfy EffectiveProject’s file-path signature.
func ResolveProjects#
func ResolveProjects(ctx context.Context, st *store.Store, userCfg UserConfig, projectID string) (ProjectConfig, error)
ResolveProjects builds the virtual PROJECTS config for one project: DB project config as the base, overlaid by userCfg.Projects[projectID] (the projects: stanza entry for that project, if any).
type UserConfig#
UserConfig is the resolved virtual USER layer: top-level values merged from DB user-scope config < –config-file < –user-config-file, plus the projects: stanza extracted from that same merge (also present at any of those three levels), keyed by the project’s resolved store id.
type UserConfig struct {
Values map[string]any
Projects map[string]map[string]any
}
func ResolveUser#
func ResolveUser(ctx context.Context, st *store.Store, configFile, userConfigFile string) (UserConfig, error)
ResolveUser builds the virtual USER config: it seeds viper with defaults, merges in DB user-scope config (lowest precedence above defaults), then configFile, then userConfigFile (highest precedence). Both file paths are optional; an empty string skips that layer. Config files are TOML.
Generated by gomarkdoc