This document captures the design decisions reached through iterative discussion for the new goal config command.
- Make the command surface more explicit and discoverable using subcommands (
list,get,set,unset,defaults). - Clearly expose the configuration layering:
env var > project config > global config > defaults. - Support distinct views:
- Effective/merged configuration (what the program actually uses).
- Strict global config file contents only (no overrides).
- Pure built-in defaults.
- Treat
configsubcommands as first-class commands (each in their own file, withmain/parseArgs/run). - Prioritize clean separation over backward compatibility.
- Keep writes surgical (only touch the key being changed) instead of snapshotting the merged state.
- Favor scripting-friendly behaviors where reasonable (e.g.,
geton missing key prints nothing and exits 0).
listgetsetunsetdefaults
goal config (with no subcommand) shows the config-specific help menu, listing the available subcommands and basic usage. Each subcommand will have its own help text.
None. The previous flat syntax (goal config <key>, goal config <key> <value>, goal config --list, -l, etc.) will not be supported. This is a deliberate redesign.
goal config list— Shows the effective configuration after all layers (environment variables > project.goal/config> global config file > built-in defaults).goal config list --global— Shows only the contents of the global config file (strict file contents; no project overrides, no environment variables, no defaults injected).- Does not accept a key as an argument. Use
getfor single-key retrieval. - Output: Keys and values, preceded by a description line explaining the view being shown.
goal config get <key>— Prints the raw value for the key from the effective (merged) configuration.goal config get <key> --global— Prints the raw value from the global config file only.- If the key has no explicit value in the requested scope: prints nothing and exits 0 (script-friendly).
- Output when present: just the raw value (no
key =prefix).
goal config set <key> <value>— Sets the key, writing to the project config file by default.goal config set <key> <value> --global— Writes to the global config file.- Surgical writes only: Only the specified key is modified in the target file. The rest of the file is left untouched. No snapshotting of the current effective config.
- Silent on success (no output, exit 0).
goal config unset <key>— Removes any explicit value for the key in the target scope (so higher layers or defaults will apply).- Supports
--global. - Idempotent: If the key is already absent in the target scope, it is a silent success (exit 0). The desired state is already achieved.
- Shows only the built-in default values that
goalknows about for all config keys. - No flags supported (in particular, no
--global). - Intended for triage/debugging ("what would goal use if nothing was configured?").
- Output: Keys and values, preceded by a description line.
- Supported on
list,get,set, andunset. - Controls the target scope (global config file vs. the default effective/project behavior).
- Each subcommand parses and handles
--globalitself (no central pre-processing in the dispatcher).
Dropped ideas:
--defaultflag (removed due to added complexity around scoping and fallbacks).
- List-style commands (
list,list --global,defaults): Use a "keys and values" format. A leading description line states what view/layer is being shown (e.g., effective merged, global-only, or built-in defaults). get: Raw value only (when present).- Writes (
set/unset): Silent on success.
Description line examples (directionally agreed):
- Effective list: something like "Effective configuration (env vars > project config > global config > defaults):"
--globallist: something like "Global configuration (from the global config file only):"defaults: something like "Built-in default values:"
- Removed entirely from
goal config. - Will move to
goal init. Re-runninggoal initin an already-initialized project should allow updating the project name (and other reasonable re-initialization actions in the future).
src/commands/config.zigbecomes a thin dispatcher (modeled after howmain.zigdispatches to top-level commands).- It looks at the first argument after
configand delegates to the appropriate subcommand module. - Bare
config(or unknown subcommand) leads to config-specific help. - No shared pre-parsing of flags (such as
--global) at the dispatcher level.
New files:
src/commands/config/list.zigsrc/commands/config/get.zigsrc/commands/config/set.zigsrc/commands/config/unset.zigsrc/commands/config/defaults.zig
Each subcommand module will be structured like other commands (main, parseArgs, run).
Config.zigwill focus on loading the effective (merged) runtime configuration for use by the rest of the program (other commands,Directories, etc.).- The new config subcommands will handle loading, parsing, and writing config files + environment variables directly, rather than primarily through the existing
Configstruct. - This supports the different required views (effective, strict global-only, pure defaults) and surgical writes cleanly.
- A common file
src/config_utils.zigis planned to hold shared config-related utilities (path resolution, file parsing, surgical write helpers, key definitions, etc.). - The user intends to implement the individual subcommands first and extract common logic into
config_utils.zigas overlap emerges, rather than designing the shared module up front.
The main keys remain:
base-direditorcommit
(project-name is leaving the config command.)
--defaultflag on list/get/set.- Backward compatibility with the old flat interface.
listaccepting a key argument (usegetinstead).- Complex flag ordering gymnastics from the old parser (subcommand style makes intent clearer).
- Exact final wording of description lines and precise output formatting details.
- Exact contents and API of
config_utils.zig(to be discovered during implementation). - Detailed error messages and validation behavior (unknown keys, invalid boolean values for
commit, missing files, etc.). - Where built-in default values are defined and how the
defaultssubcommand sources them. - Help text updates in
src/commands/help.zig. - Test strategy (following existing patterns with
TestEnvand programmatic command invocation).
| Command | View Provided | Includes Env? | Includes Project? | Includes Global? | Includes Defaults? |
|---|---|---|---|---|---|
list |
Effective (merged) | Yes | Yes | Yes | Yes |
list --global |
Strict global file only | No | No | Yes | No |
get / get --global |
Single key from chosen scope | (per scope) | (per scope) | (per scope) | (per scope) |
defaults |
Built-in defaults only | No | No | No | Yes (only) |
This structure makes the layering explicit and queryable.
Document created from design iteration in April 2026.