metame-cli 1.5.9 → 1.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ function createOpsCommandHandler(deps) {
8
8
  path,
9
9
  spawn,
10
10
  execSync,
11
+ execFileSync,
11
12
  log,
12
13
  loadConfig,
13
14
  loadState,
@@ -86,19 +87,30 @@ function createOpsCommandHandler(deps) {
86
87
  }
87
88
  try {
88
89
  let diffFiles = '';
90
+ let diffFailed = false;
89
91
  const _wh = process.platform === 'win32' ? { windowsHide: true } : {};
90
- try { diffFiles = execSync(`git diff --name-only HEAD ${match.hash}`, { cwd, encoding: 'utf8', timeout: 5000, ..._wh }).trim(); } catch { }
91
- // Reset HEAD to checkpoint's parent (removes any commits Claude made)
92
- if (match.parentHash) {
93
- execSync(`git reset --hard ${match.parentHash}`, { cwd, stdio: 'ignore', timeout: 10000, ..._wh });
92
+ try { diffFiles = execSync(`git diff --name-only HEAD ${match.hash}`, { cwd, encoding: 'utf8', timeout: 5000, ..._wh }).trim(); } catch { diffFailed = true; }
93
+ const changedFiles = diffFiles ? diffFiles.split('\n').filter(Boolean) : [];
94
+ if (changedFiles.length > 0 || diffFailed) {
95
+ // Save current state before rollback (safety net)
96
+ gitCheckpoint(cwd, '[metame-safety] before rollback');
97
+ // Reset HEAD to checkpoint's parent (removes any commits Claude made)
98
+ if (match.parentHash) {
99
+ execSync(`git reset --hard ${match.parentHash}`, { cwd, stdio: 'ignore', timeout: 10000, ..._wh });
100
+ }
101
+ // Restore only changed files (not entire worktree) to preserve user's manual edits
102
+ if (changedFiles.length > 0) {
103
+ execFileSync('git', ['checkout', match.hash, '--', ...changedFiles], { cwd, stdio: 'ignore', timeout: 10000 });
104
+ } else {
105
+ // diff failed but we still reset — fallback to full restore
106
+ execFileSync('git', ['checkout', match.hash, '--', '.'], { cwd, stdio: 'ignore', timeout: 10000 });
107
+ }
94
108
  }
95
- // Restore working tree to exact checkpoint state (recovers pre-Claude uncommitted changes)
96
- execSync(`git checkout ${match.hash} -- .`, { cwd, stdio: 'ignore', timeout: 10000, ..._wh });
97
109
  // Truncate context to checkpoint time (covers multi-turn rollback)
98
110
  truncateSessionToCheckpoint(session.id, match.message);
99
- const fileList = diffFiles ? diffFiles.split('\n').map(f => path.basename(f)).join(', ') : '';
100
- const fileCount = diffFiles ? diffFiles.split('\n').length : 0;
111
+ const fileCount = changedFiles.length;
101
112
  let msg = `⏪ 已回退到 ${cpDisplayLabel(match.message)}`;
113
+ const fileList = changedFiles.map(f => path.basename(f)).join(', ');
102
114
  if (fileCount > 0) msg += `\n📁 ${fileCount} 个文件恢复: ${fileList}`;
103
115
  log('INFO', `/undo <hash> executed for ${chatId}: reset to ${match.hash.slice(0, 8)}, files=${fileCount}`);
104
116
  await bot.sendMessage(chatId, msg);
@@ -232,14 +244,16 @@ function createOpsCommandHandler(deps) {
232
244
  let diffFiles2 = '';
233
245
  const _wh2 = process.platform === 'win32' ? { windowsHide: true } : {};
234
246
  try { diffFiles2 = execSync(`git diff --name-only HEAD ${cpMatch.hash}`, { cwd: cwd2, encoding: 'utf8', timeout: 5000, ..._wh2 }).trim(); } catch { }
235
- if (diffFiles2) {
247
+ const changedFiles2 = diffFiles2 ? diffFiles2.split('\n').filter(Boolean) : [];
248
+ if (changedFiles2.length > 0) {
236
249
  // Save current state before rollback (excluded from normal /undo list)
237
250
  gitCheckpoint(cwd2, '[metame-safety] before rollback');
238
251
  if (cpMatch.parentHash) {
239
252
  execSync(`git reset --hard ${cpMatch.parentHash}`, { cwd: cwd2, stdio: 'ignore', timeout: 10000, ..._wh2 });
240
253
  }
241
- execSync(`git checkout ${cpMatch.hash} -- .`, { cwd: cwd2, stdio: 'ignore', timeout: 10000, ..._wh2 });
242
- gitMsg2 = `\n📁 ${diffFiles2.split('\n').length} 个文件已恢复`;
254
+ // Restore only changed files (not entire worktree) to preserve user's manual edits
255
+ execFileSync('git', ['checkout', cpMatch.hash, '--', ...changedFiles2], { cwd: cwd2, stdio: 'ignore', timeout: 10000 });
256
+ gitMsg2 = `\n📁 ${changedFiles2.length} 个文件已恢复`;
243
257
  cleanupCheckpoints(cwd2);
244
258
  }
245
259
  }