Skip to content

Commit b1c15bc

Browse files
committed
🐛 fix(core): prevent overwriting user customizations on re-install and update
- **`copyRecursive`**: only copies files if destination does not exist, preserving user modifications - **`global update`**: uses `quiet: true` (merge strategy) instead of `force: true` (overwrite strategy), preventing agent replacement with template defaults - **`init` command**: adds `--yes` flag for safe auto-confirm merge (does not overwrite), separate from destructive `--force` - Bump version to 1.3.9 and update migration manifest, CHANGELOG, and docs
1 parent b203053 commit b1c15bc

8 files changed

Lines changed: 45 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9191
- `.opencode/skills/global-install/SKILL.md`: documented all 4 path rewrite locations with table
9292
- `package.json`: version bumped to 1.3.8
9393

94+
### Fixed
95+
96+
- **`copyRecursive` no longer overwrites existing files**`copyFileSync` is now gated by `!existsSync(destPath)`. Previously every file in directories (skills/, prompts/, commands/, etc.) was overwritten on re-install, destroying any user modifications.
97+
- **`global update` no longer uses `--force`** — changed from `force: true` to `quiet: true`. Previously `global update` passed `force: true` which triggered `source-wins` strategy, overwriting all user-customized agents with template defaults. Now it uses merge strategy (preserves user changes).
98+
- **New `--yes` flag** — added `--yes` to `init` command for auto-confirm merge without overwriting. Semantics: `--yes` skips the confirmation prompt but still uses merge strategy. `--force` skips prompt AND overwrites with template (destructive).
99+
100+
### Changed
101+
102+
- `bin/commands/init.mjs`: `installGlobal()` prompt now checks `options.quiet` (auto-confirm) separately from `options.force` (overwrite). `copyRecursive()` only copies non-existing files. `init()` maps `--yes``options.quiet`.
103+
|- `bin/commands/global-cmd.mjs`: `cmdUpdate()` passes `quiet: true` instead of `force: true` to `init()`.
104+
|- `bin/init.mjs`: added `--yes` option to `init` command.
105+
106+
## [1.3.9] - 2026-06-27
107+
108+
### Fixed
109+
110+
- **User config preservation on re-install** — three issues that caused user modifications to `opencode.jsonc` and global files to be lost:
111+
- `copyRecursive()` no longer overwrites existing files (only copies new ones)
112+
- `global update` uses `quiet: true` (merge) instead of `force: true` (overwrite)
113+
- New `--yes` flag for safe auto-confirm merge (separate from destructive `--force`)
114+
115+
### Changed
116+
117+
- `bin/commands/init.mjs`: `copyRecursive()` gated by `!existsSync(destPath)`; `installGlobal()` uses `options.quiet` for prompt skip; `init()` maps `--yes``quiet`
118+
- `bin/commands/global-cmd.mjs`: `cmdUpdate()` passes `quiet: true` instead of `force: true`
119+
- `bin/init.mjs`: added `--yes` option to `init` command
120+
- `package.json`: version bumped to 1.3.9
121+
94122
## [1.3.4] - 2026-06-25
95123

96124
### Added

bin/commands/global-cmd.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,10 @@ async function cmdUpdate() {
153153

154154
console.log(` Current: v${currentVersion} → Latest: v${latestVersion}`);
155155

156-
// Re-run the global install (re-copy template)
156+
// Re-run the global install (re-copy template, merge not overwrite)
157157
info('Updating global install...');
158158
const { init } = await import('./init.mjs');
159-
await init({ global: true, force: true, skipInstall: false, dir: process.cwd() });
159+
await init({ global: true, quiet: true, skipInstall: false, dir: process.cwd() });
160160

161161
success(`Global install updated to v${latestVersion}`);
162162
}

bin/commands/init.mjs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ function copyRecursive(src, dest) {
5656
const destPath = join(dest, entry.name);
5757
if (entry.isDirectory()) {
5858
copyRecursive(srcPath, destPath);
59-
} else {
59+
} else if (!existsSync(destPath)) {
60+
// Only copy if destination does NOT exist — preserves user modifications
6061
copyFileSync(srcPath, destPath);
6162
}
6263
}
@@ -246,7 +247,7 @@ async function installGlobal(options) {
246247
// 2. Check if already installed
247248
const jsoncPath = join(opencodeHome, 'opencode.jsonc');
248249
const jsonPath = join(opencodeHome, 'opencode.json');
249-
if ((existsSync(jsoncPath) || existsSync(jsonPath)) && !options.force) {
250+
if ((existsSync(jsoncPath) || existsSync(jsonPath)) && !options.quiet && !options.force) {
250251
warn(`OpenCode config already exists at ${opencodeHome}`);
251252
const rl = await import('readline/promises');
252253
const readline = rl.createInterface({ input: process.stdin, output: process.stdout });
@@ -643,6 +644,9 @@ async function initLocal(options) {
643644
* default → check global, then choose initLocal() (per-project)
644645
*/
645646
export async function init(options) {
647+
// Map --yes to quiet (auto-confirm merge without overwriting)
648+
if (options.yes) options.quiet = true;
649+
646650
if (options.global) return installGlobal(options);
647651
if (options.local) return initLocal(options);
648652

bin/init.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ program
3434
.option('--skip-install', 'Skip npm/bun install step in .opencode/')
3535
.option('--global', 'Install globally instead of per-project')
3636
.option('--local', 'Force local copy even when global install exists')
37+
.option('--yes', 'Auto-confirm merge without overwriting')
3738
.action(init);
3839

3940
program

bin/migrations/001_initial_setup.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Migration 001: Initial Setup
3-
* Version: 1.3.8
3+
* Version: 1.3.9
44
*
55
* Establishes the migration system foundation:
66
* - Ensures .kit-version file exists in target .opencode/
@@ -20,7 +20,7 @@
2020
* @param {object} opts.path - Node path module
2121
*/
2222
export default {
23-
version: '1.3.8',
23+
version: '1.3.9',
2424
description:
2525
'Initialize migration system — establish .kit-version tracking and validate config structure',
2626

@@ -36,7 +36,7 @@ export default {
3636
// Ensure .kit-version file exists
3737
const versionFile = path.join(opencodeDir, '.kit-version');
3838
if (!fs.existsSync(versionFile)) {
39-
fs.writeFileSync(versionFile, '1.3.8\n', 'utf-8');
39+
fs.writeFileSync(versionFile, '1.3.9\n', 'utf-8');
4040
}
4141

4242
// Validate config has agent and permission fields

bin/migrations/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"001_initial_setup.mjs": {
3-
"version": "1.3.8",
3+
"version": "1.3.9",
44
"description": "Initialize migration system — establish .kit-version tracking and validate config structure"
55
}
66
}

index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2954,7 +2954,7 @@ <h2>One command, <span class="text-primary">any way you
29542954
ready</span></div>
29552955
<div class="code-line code-output code-highlight"><span
29562956
style="color: var(--success)"></span> <span
2957-
style="color: var(--text-primary)">opencode-agent-kit v1.3.8
2957+
style="color: var(--text-primary)">opencode-agent-kit v1.3.9
29582958
installed globally!</span></div>
29592959
</div>
29602960
</div>
@@ -3360,7 +3360,7 @@ <h5 class="footer-col-title">Resources</h5>
33603360
delay: 3200,
33613361
},
33623362
{
3363-
text: '<span style="color: var(--success)"> ✅</span> <span style="color: var(--text-primary)">opencode-agent-kit v1.3.8 installed globally</span>',
3363+
text: '<span style="color: var(--success)"> ✅</span> <span style="color: var(--text-primary)">opencode-agent-kit v1.3.9 installed globally</span>',
33643364
delay: 4000,
33653365
},
33663366
];
@@ -3401,7 +3401,7 @@ <h5 class="footer-col-title">Resources</h5>
34013401
} else {
34023402
// Show final state for reduced motion
34033403
termBody.innerHTML =
3404-
'<span class="terminal-preview-prompt">$</span> npx opencode-agent-kit init --global<br><span style="color: var(--success)"> ✅</span> <span style="color: var(--text-primary)">opencode-agent-kit v1.3.8 installed globally</span>';
3404+
'<span class="terminal-preview-prompt">$</span> npx opencode-agent-kit init --global<br><span style="color: var(--success)"> ✅</span> <span style="color: var(--text-primary)">opencode-agent-kit v1.3.9 installed globally</span>';
34053405
}
34063406
}
34073407

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencode-agent-kit",
3-
"version": "1.3.8",
3+
"version": "1.3.9",
44
"description": "Multi-stack OpenCode agent toolkit — 33+ specialized AI agents, 200+ skills, 46 commands, 8 MCP servers (Nuxt, React, Node.js, Laravel, CI3, Android, Flutter, DevOps, SEO, SonarQube, and more)",
55
"type": "module",
66
"bin": {

0 commit comments

Comments
 (0)