vouch install-mcp codex ships one file: adapters/codex/config.toml -> <project>/.codex/config.toml (project-local per the #179 scope decision). the installer's plain-copy path in src/vouch/install_adapter.py silently skips any destination that already exists:
if dst.exists():
result.skipped.append(entry.dst)
continue
.codex/config.toml is codex's primary config file — model, approval policy, and any other mcp servers all live there. so on any project where codex is already configured, the install is a silent no-op and vouch never gets wired. this is exactly the situation json_merge already solves for claude-code's .claude/settings.json; toml has no equivalent strategy.
proposed change
- add a
toml_merge: true entry flag in install.yaml manifests, next to fenced_append and json_merge (_FileEntry in src/vouch/install_adapter.py).
- semantics mirror
_install_json_merge: parse the existing destination, deep-merge the template's tables into it, write back. idempotent — re-running produces no change.
- reading is stdlib (
tomllib; we're requires-python >= 3.11). writing needs a decision: a minimal serializer that handles the shapes we ship (tables + string/array values only), or the small tomli-w dependency. either is fine — the hand-rolled serializer keeps the dependency set unchanged and our templates are deliberately simple.
- flip
adapters/codex/install.yaml T1 to toml_merge: true.
review gate & scope
installer-only; no kb read/write surface touched, nothing bypasses proposals.approve(). stays project-local — never touches ~/.codex/config.toml.
acceptance criteria
vouch install-mcp codexships one file:adapters/codex/config.toml-><project>/.codex/config.toml(project-local per the #179 scope decision). the installer's plain-copy path insrc/vouch/install_adapter.pysilently skips any destination that already exists:.codex/config.tomlis codex's primary config file — model, approval policy, and any other mcp servers all live there. so on any project where codex is already configured, the install is a silent no-op and vouch never gets wired. this is exactly the situationjson_mergealready solves for claude-code's.claude/settings.json; toml has no equivalent strategy.proposed change
toml_merge: trueentry flag in install.yaml manifests, next tofenced_appendandjson_merge(_FileEntryinsrc/vouch/install_adapter.py)._install_json_merge: parse the existing destination, deep-merge the template's tables into it, write back. idempotent — re-running produces no change.tomllib; we'rerequires-python >= 3.11). writing needs a decision: a minimal serializer that handles the shapes we ship (tables + string/array values only), or the smalltomli-wdependency. either is fine — the hand-rolled serializer keeps the dependency set unchanged and our templates are deliberately simple.adapters/codex/install.yamlT1 totoml_merge: true.review gate & scope
installer-only; no kb read/write surface touched, nothing bypasses
proposals.approve(). stays project-local — never touches~/.codex/config.toml.acceptance criteria
toml_merge: truesupported in install.yaml entries and applied byinstall_adapter.codex/config.tomlpreserves unrelated tables (e.g.[mcp_servers.other], top-level model settings) and adds[mcp_servers.vouch][mcp_servers.vouch]is handled deterministically (documented which side wins, matching_install_json_merge's convention) and testedtests/test_install_adapter.pycover merge, idempotence, conflict, and fresh-install paths