Summary
Replace .dual.toml with .dual/settings.json as the per-repo config file. The .dual/ folder is committed to git and becomes the extensible root for all Dual configuration.
Moving from TOML to JSON for long-term extensibility and ecosystem alignment (devcontainer.json is already JSON).
Breaking Change
.dual.toml is replaced by .dual/settings.json. The DualConfig struct switches from TOML to JSON serde.
Schema
{
"devcontainer": ".devcontainer/devcontainer.json",
"extra_commands": ["cargo", "go"],
"anonymous_volumes": ["node_modules", ".next"],
"shared": [".env.local", ".vercel"]
}
Key rule: devcontainer field must always be explicitly set in settings.json. No implicit auto-detection — the init flow handles setting it up.
Implementation
1. New DualConfig struct (JSON-based)
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct DualConfig {
pub devcontainer: String,
#[serde(default)]
pub extra_commands: Vec<String>,
#[serde(default = "default_anonymous_volumes")]
pub anonymous_volumes: Vec<String>,
#[serde(default)]
pub shared: Vec<String>,
}
Changes from current model:
devcontainer is String (required), not Option<String>
shared flattened from SharedConfig { files } to just Vec<String>
- Serialization via
serde_json instead of toml
2. File paths
- Config dir:
{repo_root}/.dual/
- Config file:
{repo_root}/.dual/settings.json
- Constants:
DUAL_DIR = ".dual", SETTINGS_FILENAME = "settings.json"
3. Functions to add/update
load_dual_config() — reads .dual/settings.json (error if missing, not defaults)
write_dual_config() — writes .dual/settings.json (pretty-printed JSON)
ensure_dual_dir() — creates .dual/ directory
- Remove TOML dependency for config (keep for global state)
4. load_hints() update
Still merges DualConfig + devcontainer.json → RepoHints, but:
- Reads from
.dual/settings.json instead of .dual.toml
- Uses the explicit
devcontainer path (no auto-detection fallback)
- Error if
.dual/settings.json is missing (user must run dual init)
Tests
- Parse/write roundtrip for JSON-based DualConfig
load_hints() merge with new paths
- Missing
.dual/settings.json returns appropriate error
- Schema validation (required devcontainer field)
Summary
Replace
.dual.tomlwith.dual/settings.jsonas the per-repo config file. The.dual/folder is committed to git and becomes the extensible root for all Dual configuration.Moving from TOML to JSON for long-term extensibility and ecosystem alignment (devcontainer.json is already JSON).
Breaking Change
.dual.tomlis replaced by.dual/settings.json. TheDualConfigstruct switches from TOML to JSON serde.Schema
{ "devcontainer": ".devcontainer/devcontainer.json", "extra_commands": ["cargo", "go"], "anonymous_volumes": ["node_modules", ".next"], "shared": [".env.local", ".vercel"] }Key rule:
devcontainerfield must always be explicitly set insettings.json. No implicit auto-detection — the init flow handles setting it up.Implementation
1. New
DualConfigstruct (JSON-based)Changes from current model:
devcontainerisString(required), notOption<String>sharedflattened fromSharedConfig { files }to justVec<String>serde_jsoninstead oftoml2. File paths
{repo_root}/.dual/{repo_root}/.dual/settings.jsonDUAL_DIR = ".dual",SETTINGS_FILENAME = "settings.json"3. Functions to add/update
load_dual_config()— reads.dual/settings.json(error if missing, not defaults)write_dual_config()— writes.dual/settings.json(pretty-printed JSON)ensure_dual_dir()— creates.dual/directory4.
load_hints()updateStill merges DualConfig + devcontainer.json → RepoHints, but:
.dual/settings.jsoninstead of.dual.tomldevcontainerpath (no auto-detection fallback).dual/settings.jsonis missing (user must rundual init)Tests
load_hints()merge with new paths.dual/settings.jsonreturns appropriate error