Documentation
¶
Overview ¶
Package config loads and persists per-project memcode configuration, stored under a .memcode directory at the project root.
Index ¶
- Constants
- Variables
- func EnsureGitignore(root string)
- func Init(root string, force bool) (created bool, err error)
- func ResolvePin(cfg *Config, override string) (label string, window int)
- func SaveUserPin(label string, window int)
- func UserPrefsPath() string
- type Config
- type Endpoint
- type ModelTiers
- type Source
- type SyncConfig
- type SyncTarget
- type SyncTargetMeta
Constants ¶
const ConfigFile = "config.json"
ConfigFile is the config filename inside DirName.
const DirName = ".memcode"
DirName is the per-project directory that holds memcode state.
Variables ¶
var ErrNotInitialized = errors.New("memcode is not initialized in this project")
ErrNotInitialized is returned when no .memcode directory is found.
var SyncTargetAll = []SyncTargetMeta{
{Name: "Claude", Path: "CLAUDE.md"},
{Name: "Codex", Path: "AGENTS.md"},
{Name: "Copilot", Path: ".github/copilot-instructions.md"},
{Name: "Cursor", Path: ".cursor/rules"},
{Name: "Gemini", Path: "GEMINI.md"},
{Name: "Windsurf", Path: ".windsurfrules"},
}
SyncTargetAll is the ordered list of all supported targets. AGENTS.md (Codex) is the emerging cross-editor standard; the rest are tool-specific. Add a row here + a SyncTarget constant to support a new editor — nothing else needs to change.
Functions ¶
func EnsureGitignore ¶
func EnsureGitignore(root string)
EnsureGitignore makes .memcode self-ignoring — a `*` .gitignore INSIDE the directory — so it fully disappears from the user's `git status` and never lands in their repo, WITHOUT touching the user's own root .gitignore (we don't edit files they curate). .memcode is memcode's local metadata, like .git: synced its own way, not committed. The rule is bare `*` (which ignores the .gitignore itself too) — an `!.gitignore` exception would leave a non-ignored file inside and the directory would still show in `git status`; memcode re-creates this file every run, so it never needs to be committed.
PUBLIC and called on EVERY launch (openProject), not just first-time Init — an already-initialized project still needs the self-ignore. Idempotent; no-op if .memcode doesn't exist yet; never clobbers a user-edited file; and migrates our own earlier `*`+`!.gitignore` template that didn't hide the dir.
func Init ¶
Init creates the .memcode directory and config file at root. It returns whether new configuration was written. Passing force overwrites an existing config file.
func ResolvePin ¶ added in v0.29.0
ResolvePin returns the model this session runs on, plus its context window.
override is a session-only choice (--model) and is never persisted: it is this invocation's model, not a new preference.
When resolution reaches the seed, the pin is persisted to BOTH stores before returning, so this is the only run that ever consults default_model.
func SaveUserPin ¶ added in v0.29.0
SaveUserPin records the PRIMARY model at the USER level, so a different repo starts on the same one. Best-effort by design: failing to remember a preference must never fail the operation the user actually asked for.
func UserPrefsPath ¶ added in v0.29.0
func UserPrefsPath() string
UserPrefsPath returns $XDG_CONFIG_HOME/memcode/prefs.json, else ~/.config/memcode/prefs.json. "" when no home directory can be determined — callers treat that as "no user-level memory", never as an error.
Types ¶
type Config ¶
type Config struct {
// Root is the absolute path to the project root (the dir holding .memcode).
Root string `json:"-"`
// Models selects the model per tier (see ModelTiers).
Models ModelTiers `json:"models"`
// Exclude is a list of glob patterns skipped during indexing.
Exclude []string `json:"exclude"`
// Sync controls which AI-editor context files memcode keeps in sync.
// "everything" means all detected files plus any added in the future.
// Otherwise it's a list of target names (see SyncTarget constants).
Sync SyncConfig `json:"sync,omitempty"`
// Mode is the remembered permission mode (ask|auto|allow-all) — persisted when
// the user cycles it (Shift+Tab) or sets it via /mode, so the choice survives
// across sessions. Empty = use the default. Explicit `--auto`/`--allow-all`
// flags are one-offs and do NOT overwrite this.
Mode string `json:"mode,omitempty"` // ask|auto|allow-all — persisted when cycled or /mode
Theme string `json:"theme,omitempty"` // color theme name; empty means aurora (default)
// Vendor (the remembered Automatic strong-tier vendor) is no longer read.
// Kept as an accepted-and-ignored field so an existing config file still
// parses; nothing writes it.
Vendor string `json:"vendor,omitempty"`
// PinnedModel is this workspace's model: a catalog label ("sonnet",
// "glm-5p2") the whole session runs on — main loop, plan drafting, delegated
// workers. Internal plumbing (classify, compact, shrinkwrap) rides the
// catalog's utility_model instead.
//
// Empty means "this workspace has never chosen": ResolvePin then falls
// through to the user-level store, and finally seeds from default_model and
// persists here, so it is only ever empty once.
PinnedModel string `json:"pinned_model,omitempty"`
// PinnedWindow caches the pin's context window (tokens) from the picker list,
// so the ctx meter is sized right on launch before the first serve reports one.
PinnedWindow int `json:"pinned_window,omitempty"`
// Personality is the chosen agent voice (a built-in key like "joker"/"mirror" or a
// free-text custom voice). It travels to the gateway as a fact and is realized there
// as a tone-only prose envelope; empty means the default neutral voice. Set via
// /personality. Voice ONLY — it never affects behavior or the permission floor.
Personality string `json:"personality,omitempty"`
// ExtraMile, when true, asks the agent to go above and beyond the literal request —
// checking edge cases and feature completeness — on every plan and execution. It travels
// to the gateway as a fact and is realized there as an extra prompt rule for the
// planner/executor. Off by default; set via /extramile. Consumes extra tokens.
ExtraMile bool `json:"extra_mile,omitempty"`
// ServingDefault caches the gateway's everyday (cheap-lane) model id — learned from
// /v1/models at startup and persisted, so the banner + footer show what actually runs
// (e.g. glm-5p2) immediately on the NEXT launch instead of the CLI's bootstrap identity
// (sonnet) until the async fetch returns. Refreshed each launch; cosmetic only.
ServingDefault string `json:"serving_default,omitempty"`
// CommitBeforeWork is the remembered answer to the "you have uncommitted
// changes — commit before starting a large work block?" prompt: "" = ask each
// time (default), "commit" = always checkpoint-commit the tree silently and
// continue, "skip" = never prompt, just proceed on the dirty tree. Persisted
// when the user picks either "don't ask again" option on the card.
CommitBeforeWork string `json:"commit_before_work,omitempty"`
// Endpoints is the named custom-endpoint list (one-wire Phase C): arbitrary
// OpenAI-compatible backends (Ollama, LM Studio, vLLM, a provider cloud)
// the CLI can run against without a memcode account. Selection: a memcode
// login always wins; else MEMCODE_ENDPOINT_URL wins; else the entry named
// by Endpoint below ("" = the first). Keys are NEVER stored here (see the
// package rule above) — KeyEnv names the env var that holds one.
Endpoints []Endpoint `json:"endpoints,omitempty"`
// Endpoint selects the active Endpoints entry by name ("" = the first).
Endpoint string `json:"endpoint,omitempty"`
}
Config is the per-project configuration.
NOTE: API keys are NEVER stored here. They are read from the environment only — the CLI holds no provider keys at all; the gateway holds them.
func Load ¶
Load reads the configuration for the project rooted at the canonical root for start (see Resolve). It is an error if the project hasn't been initialized.
func (*Config) ActiveEndpoint ¶
ActiveEndpoint resolves the config-selected endpoint entry: the one named by Config.Endpoint, else the first. ok=false for an empty list or a dangling name (a typo must not silently select a different backend).
func (*Config) RememberEndpointModel ¶
RememberEndpointModel records a /model choice for an endpoint in this project's config: the matching entry (by base URL) gets LastModel set, and an env-defined endpoint with no entry yet gets one appended so the choice survives relaunches. The caller Saves.
func (*Config) ResolveEndpoint ¶
ResolveEndpoint resolves the ACTIVE custom endpoint for this project as the provider dials it, env first: MEMCODE_ENDPOINT_URL names the endpoint (key / initial model from MEMCODE_ENDPOINT_KEY/_MODEL), enriched from a matching config entry (same base URL) — its remembered LastModel, curated Models, and KeyEnv fill whatever the env leaves unset; else the config-selected entry stands alone. The session model resolves remembered-last-model → env/initial → the first curated model ("" = the picker/boot autodetect decides). ok=false when neither env nor config configures an endpoint. A memcode login outranks both — that selection lives in provider.NewFromEnv/NewFromEnvLazy.
func (*Config) Validate ¶
Validate checks the config for values that would fail downstream — currently that every required model tier has a non-empty value. path is the config file path (passed in so the error names the file the user needs to edit). Empty tiers are sent to the gateway as empty model ids, which produce opaque errors; this catches them locally with an actionable message instead.
type Endpoint ¶
type Endpoint struct {
// Name is the entry's id — what Config.Endpoint selects and the UI shows.
Name string `json:"name"`
// BaseURL is the FULL compat base incl. any path prefix, e.g.
// http://localhost:11434/v1 ({base}/chat/completions is the turn endpoint).
BaseURL string `json:"base_url"`
// KeyEnv names the ENV VAR holding this endpoint's API key (dotenv chain
// applies). The key itself never lives in config.json.
KeyEnv string `json:"key_env,omitempty"`
// Models is an optional curated model list: when set, the /model picker
// offers exactly these (plus free-text) instead of GET {base}/models.
Models []string `json:"models,omitempty"`
// LastModel is the remembered /model choice for this endpoint — the
// session model on the next launch.
LastModel string `json:"last_model,omitempty"`
}
Endpoint is one named OpenAI-compatible endpoint in the config list.
type ModelTiers ¶
type ModelTiers struct {
Planner string `json:"planner,omitempty"` // legacy, ignored
Coder string `json:"coder"` // startup display model only
Classifier string `json:"classifier,omitempty"` // legacy, ignored
Explorer string `json:"explorer,omitempty"` // legacy, ignored
}
ModelTiers maps the engine's model roles to concrete model ids. Values may be aliases ("opus"|"sonnet"|"haiku") or full model ids; resolve with provider.ResolveAlias. ModelTiers is LEGACY. Planner/Classifier/Explorer selected nothing once the pin became the single selection authority, and are no longer read or defaulted — they remain only so an existing config file still parses. Coder is still consulted, but only as the session's DISPLAY model at startup.
Which model runs what is now two pins: PinnedModel (primary) and DelegatedModel (sub-agents and scouts). Do not add roles here.
type Source ¶
type Source string
Source describes how the project root was resolved (for `memcode doctor`).
type SyncConfig ¶
type SyncConfig struct {
// Everything syncs all detected targets plus any new ones automatically.
Everything bool `json:"everything,omitempty"`
// Targets is the explicit list when Everything is false.
Targets []SyncTarget `json:"targets,omitempty"`
}
SyncConfig stores the user's /sync preferences.
type SyncTarget ¶
type SyncTarget string
SyncTarget names an AI-editor context file that memcode can keep in sync.
const ( SyncTargetClaude SyncTarget = "claude" // CLAUDE.md SyncTargetCodex SyncTarget = "codex" // AGENTS.md (the cross-tool standard) SyncTargetCopilot SyncTarget = "copilot" // .github/copilot-instructions.md SyncTargetCursor SyncTarget = "cursor" // .cursor/rules SyncTargetGemini SyncTarget = "gemini" // GEMINI.md SyncTargetWindsurf SyncTarget = "windsurf" // .windsurfrules )
type SyncTargetMeta ¶
SyncTargetMeta is display metadata for a sync target. Name (lowercased) must equal the SyncTarget constant — that's how stored targets resolve back to a file.