phase 04: deep merge opencode json
This commit is contained in:
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user