phase 04: deep merge opencode json

This commit is contained in:
Adrian
2026-02-20 13:30:17 -05:00
parent 5abddbcbd9
commit 3914dfdebe
5 changed files with 818 additions and 9 deletions

View File

@@ -0,0 +1,45 @@
# Phase 4 Handoff: Deep-Merge opencode.json
**Date:** 2026-02-20
**Status:** Complete
## Summary
Implemented `mergeOpenCodeConfig()` function that performs deep-merge of plugin config into existing opencode.json with user-wins-on-conflict strategy.
## Changes Made
### 1. Updated `src/targets/opencode.ts`
- Added imports for `pathExists`, `readJson`, and `OpenCodeConfig` type
- Added `mergeOpenCodeConfig()` function before `writeOpenCodeBundle()`
- Replaced direct `writeJson()` call with merge logic
### 2. Updated `tests/opencode-writer.test.ts`
- Renamed existing backup test to `"merges plugin config into existing opencode.json without destroying user keys"`
- Added two new tests:
- `"merges mcp servers without overwriting user entry"`
- `"preserves unrelated user keys when merging opencode.json"`
## Verification
All 8 tests pass:
```
bun test tests/opencode-writer.test.ts
8 pass, 0 fail
```
## Key Behaviors
1. **User keys preserved**: All existing config keys remain intact
2. **MCP merge**: Plugin MCP servers added, user servers kept on conflict
3. **Permission merge**: Plugin permissions added, user permissions kept on conflict
4. **Tools merge**: Plugin tools added, user tools kept on conflict
5. **Fallback**: If existing config is malformed JSON, writes plugin-only config (safety first)
6. **Backup**: Original config is still backed up before writing merged result
## Next Steps
- Proceed to next phase (if any)
- Consider adding decision log entry for ADR-002 (user-wins-on-conflict strategy)

View File

@@ -126,4 +126,60 @@ for (const commandFile of bundle.commandFiles) {
## Alternatives Considered
1. Use intermediate variable for commandDir - Rejected: caused intermittent undefined errors
2. Use direct property reference `openCodePaths.commandDir` - Chosen: more reliable
2. Use direct property reference `openCodePaths.commandDir` - Chosen: more reliable
---
## Decision: ADR-002 - User-Wins-On-Conflict for Config Merge
**Date:** 2026-02-20
**Status:** Adopted
## Context
When merging plugin config into existing opencode.json, conflicts may occur (e.g., same MCP server name with different configuration). The merge strategy must decide which value wins.
## Decision
**User config wins on conflict.** When plugin and user both define the same key (MCP server name, permission, tool), the user's value takes precedence.
### Rationale
- Safety first: Do not overwrite user data with plugin defaults
- Users have explicit intent in their local config
- Plugins should add new entries without modifying user's existing setup
- Aligns with AGENTS.md principle: "Do not delete or overwrite user data"
### Merge Algorithm
```typescript
const mergedMcp = {
...(incoming.mcp ?? {}),
...(existing.mcp ?? {}), // existing takes precedence
}
```
Same pattern applied to `permission` and `tools`.
### Fallback Behavior
If existing `opencode.json` is malformed JSON, warn and write plugin-only config rather than crashing:
```typescript
} catch {
console.warn(`Warning: existing ${configPath} is not valid JSON. Writing plugin config without merging.`)
return incoming
}
```
## Consequences
- Positive: User config never accidentally overwritten
- Positive: Plugin can add new entries without conflict
- Negative: Plugin cannot modify user's existing server configuration (must use unique names)
- Negative: Silent merge may mask configuration issues if user expects plugin override
## Alternatives Considered
1. Plugin wins on conflict - Rejected: would overwrite user data
2. Merge and combine arrays - Rejected: MCP servers are keyed objects, not array
3. Fail on conflict - Rejected: breaks installation workflow