wangchuan 3.0.0 → 4.0.0

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.
Files changed (72) hide show
  1. package/README.md +146 -174
  2. package/README.zh-CN.md +254 -0
  3. package/dist/bin/wangchuan.d.ts +10 -2
  4. package/dist/bin/wangchuan.d.ts.map +1 -1
  5. package/dist/bin/wangchuan.js +30 -233
  6. package/dist/bin/wangchuan.js.map +1 -1
  7. package/dist/src/agents/codex.d.ts +9 -0
  8. package/dist/src/agents/codex.d.ts.map +1 -0
  9. package/dist/src/agents/codex.js +20 -0
  10. package/dist/src/agents/codex.js.map +1 -0
  11. package/dist/src/agents/index.d.ts.map +1 -1
  12. package/dist/src/agents/index.js +2 -0
  13. package/dist/src/agents/index.js.map +1 -1
  14. package/dist/src/commands/completions.js +2 -2
  15. package/dist/src/commands/completions.js.map +1 -1
  16. package/dist/src/commands/doctor.d.ts +17 -6
  17. package/dist/src/commands/doctor.d.ts.map +1 -1
  18. package/dist/src/commands/doctor.js +233 -65
  19. package/dist/src/commands/doctor.js.map +1 -1
  20. package/dist/src/commands/list.d.ts.map +1 -1
  21. package/dist/src/commands/list.js +1 -0
  22. package/dist/src/commands/list.js.map +1 -1
  23. package/dist/src/commands/status.d.ts +20 -3
  24. package/dist/src/commands/status.d.ts.map +1 -1
  25. package/dist/src/commands/status.js +321 -51
  26. package/dist/src/commands/status.js.map +1 -1
  27. package/dist/src/commands/sync.d.ts.map +1 -1
  28. package/dist/src/commands/sync.js +51 -0
  29. package/dist/src/commands/sync.js.map +1 -1
  30. package/dist/src/commands/template.js +1 -1
  31. package/dist/src/commands/template.js.map +1 -1
  32. package/dist/src/core/config.d.ts +9 -9
  33. package/dist/src/core/config.d.ts.map +1 -1
  34. package/dist/src/core/config.js +11 -11
  35. package/dist/src/core/config.js.map +1 -1
  36. package/dist/src/core/crypto.d.ts +10 -10
  37. package/dist/src/core/crypto.d.ts.map +1 -1
  38. package/dist/src/core/crypto.js +19 -19
  39. package/dist/src/core/crypto.js.map +1 -1
  40. package/dist/src/core/git.d.ts +2 -2
  41. package/dist/src/core/git.js +9 -9
  42. package/dist/src/core/git.js.map +1 -1
  43. package/dist/src/core/json-field.d.ts +5 -5
  44. package/dist/src/core/json-field.d.ts.map +1 -1
  45. package/dist/src/core/json-field.js +5 -5
  46. package/dist/src/core/json-field.js.map +1 -1
  47. package/dist/src/core/sync.d.ts +9 -9
  48. package/dist/src/core/sync.js +47 -47
  49. package/dist/src/core/sync.js.map +1 -1
  50. package/dist/src/i18n.d.ts.map +1 -1
  51. package/dist/src/i18n.js +24 -10
  52. package/dist/src/i18n.js.map +1 -1
  53. package/dist/src/types.d.ts +24 -23
  54. package/dist/src/types.d.ts.map +1 -1
  55. package/dist/src/types.js +3 -3
  56. package/dist/src/types.js.map +1 -1
  57. package/dist/src/utils/linediff.d.ts +6 -5
  58. package/dist/src/utils/linediff.d.ts.map +1 -1
  59. package/dist/src/utils/linediff.js +10 -9
  60. package/dist/src/utils/linediff.js.map +1 -1
  61. package/dist/test/crypto.test.d.ts +1 -1
  62. package/dist/test/crypto.test.js +16 -16
  63. package/dist/test/crypto.test.js.map +1 -1
  64. package/dist/test/json-field.test.d.ts +1 -1
  65. package/dist/test/json-field.test.js +13 -13
  66. package/dist/test/json-field.test.js.map +1 -1
  67. package/dist/test/sync.test.d.ts +8 -8
  68. package/dist/test/sync.test.js +122 -113
  69. package/dist/test/sync.test.js.map +1 -1
  70. package/package.json +1 -1
  71. package/skill/SKILL.md +40 -53
  72. package/skill/claude/SKILL.md +10 -41
@@ -1 +1 @@
1
- {"version":3,"file":"json-field.test.js","sourceRoot":"","sources":["../../test/json-field.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAEtD,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;QACnB,MAAM,GAAG,GAAG;YACV,UAAU,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;YAC7C,WAAW,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE;YAC9B,WAAW,EAAE,EAAE;SAChB,CAAC;QACF,MAAM,MAAM,GAAG,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QAChB,MAAM,GAAG,GAAG,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;QAC7F,MAAM,MAAM,GAAG,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;QACnE,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE;QAClB,MAAM,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC;QAClE,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE;QACpB,MAAM,MAAM,GAAG,SAAS,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3D,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,SAAS,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACtD,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,SAAS,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACtD,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,MAAM,GAAG,SAAS,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3E,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,QAAQ,GAAG;YACf,UAAU,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE;YAC7B,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;YACxB,WAAW,EAAE,EAAE;YACf,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;SACpC,CAAC;QACF,yBAAyB;QACzB,MAAM,SAAS,GAAG,SAAS,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;QACpE,SAAS;QACR,SAAqC,CAAC,UAAU,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;QACtE,sBAAsB;QACtB,MAAM,MAAM,GAAG,SAAS,CAAC,WAAW,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC1D,wBAAwB;QACxB,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE;YAC7B,UAAU,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE;YAC7B,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;YACxB,WAAW,EAAE,EAAE;YACf,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;SACpC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"json-field.test.js","sourceRoot":"","sources":["../../test/json-field.test.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,13 @@
1
1
  /**
2
- * sync.test.ts — 同步引擎单元测试
2
+ * sync.test.ts — Sync engine unit tests
3
3
  *
4
- * 覆盖:
5
- * - buildFileEntries 条目生成(agents + shared + jsonFields + 去重)
6
- * - distributeSharedskills 分发、MCP 分发)
7
- * - stageToRepo + restoreFromRepo 往返一致性
8
- * - jsonFields 字段级提取/合并不丢数据
9
- * - 新增 skill/MCP 的跨 agent 共享
10
- * - 删除 skill 后的行为(当前:只增不删)
4
+ * Coverage:
5
+ * - buildFileEntries entry generation (agents + shared + jsonFields + dedup)
6
+ * - distributeShared (skills distribution, MCP distribution)
7
+ * - stageToRepo + restoreFromRepo round-trip consistency
8
+ * - jsonFields field-level extraction/merge without data loss
9
+ * - New skill/MCP cross-agent sharing
10
+ * - Behavior after skill deletion (add-only model)
11
11
  */
12
12
  export {};
13
13
  //# sourceMappingURL=sync.test.d.ts.map
@@ -1,13 +1,13 @@
1
1
  /**
2
- * sync.test.ts — 同步引擎单元测试
2
+ * sync.test.ts — Sync engine unit tests
3
3
  *
4
- * 覆盖:
5
- * - buildFileEntries 条目生成(agents + shared + jsonFields + 去重)
6
- * - distributeSharedskills 分发、MCP 分发)
7
- * - stageToRepo + restoreFromRepo 往返一致性
8
- * - jsonFields 字段级提取/合并不丢数据
9
- * - 新增 skill/MCP 的跨 agent 共享
10
- * - 删除 skill 后的行为(当前:只增不删)
4
+ * Coverage:
5
+ * - buildFileEntries entry generation (agents + shared + jsonFields + dedup)
6
+ * - distributeShared (skills distribution, MCP distribution)
7
+ * - stageToRepo + restoreFromRepo round-trip consistency
8
+ * - jsonFields field-level extraction/merge without data loss
9
+ * - New skill/MCP cross-agent sharing
10
+ * - Behavior after skill deletion (add-only model)
11
11
  */
12
12
  import { describe, it, before, after } from 'node:test';
13
13
  import assert from 'node:assert/strict';
@@ -16,7 +16,7 @@ import os from 'os';
16
16
  import path from 'path';
17
17
  import { syncEngine, buildFileEntries } from '../src/core/sync.js';
18
18
  import { cryptoEngine } from '../src/core/crypto.js';
19
- // ── 测试工具 ────────────────────────────────────────────────────────
19
+ // ── Test utilities ──────────────────────────────────────────────────
20
20
  const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'wc-sync-'));
21
21
  const KEY = path.join(TMP, 'master.key');
22
22
  const REPO = path.join(TMP, 'repo');
@@ -26,6 +26,7 @@ const WS_GE = path.join(TMP, 'gemini');
26
26
  const WS_CB = path.join(TMP, 'codebuddy');
27
27
  const WS_WB = path.join(TMP, 'workbuddy');
28
28
  const WS_CU = path.join(TMP, 'cursor');
29
+ const WS_CX = path.join(TMP, 'codex');
29
30
  function mkCfg(overrides) {
30
31
  return {
31
32
  repo: 'git@example.com:test.git',
@@ -97,6 +98,14 @@ function mkCfg(overrides) {
97
98
  { src: 'cli-config.json', fields: ['permissions', 'model', 'enabledPlugins'], repoName: 'cli-config-sync.json', encrypt: true },
98
99
  ],
99
100
  },
101
+ codex: {
102
+ enabled: true,
103
+ workspacePath: WS_CX,
104
+ syncFiles: [
105
+ { src: 'MEMORY.md', encrypt: true },
106
+ { src: 'instructions.md', encrypt: false },
107
+ ],
108
+ },
100
109
  },
101
110
  },
102
111
  shared: {
@@ -121,7 +130,7 @@ function mkCfg(overrides) {
121
130
  ...overrides,
122
131
  };
123
132
  }
124
- /** 写文件,自动建目录 */
133
+ /** Write file, auto-creating parent dirs */
125
134
  function writeFile(filePath, content) {
126
135
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
127
136
  fs.writeFileSync(filePath, content, 'utf-8');
@@ -139,7 +148,7 @@ function prepareEmptyMcpFiles() {
139
148
  // ── Setup / Teardown ────────────────────────────────────────────────
140
149
  before(() => {
141
150
  cryptoEngine.generateKey(KEY);
142
- for (const d of [REPO, WS_OC, WS_CL, WS_GE, WS_CB, WS_WB, WS_CU]) {
151
+ for (const d of [REPO, WS_OC, WS_CL, WS_GE, WS_CB, WS_WB, WS_CU, WS_CX]) {
143
152
  fs.mkdirSync(d, { recursive: true });
144
153
  }
145
154
  // Pre-create empty MCP configs for all shared MCP source agents
@@ -148,9 +157,9 @@ before(() => {
148
157
  after(() => {
149
158
  fs.rmSync(TMP, { recursive: true, force: true });
150
159
  });
151
- // ── buildFileEntries 测试 ───────────────────────────────────────────
160
+ // ── buildFileEntries tests ──────────────────────────────────────────
152
161
  describe('buildFileEntries', () => {
153
- it('为每个 agent 生成 agents/<name>/ 前缀的条目', () => {
162
+ it('generates agents/<name>/ prefixed entries for each agent', () => {
154
163
  const cfg = mkCfg();
155
164
  const entries = buildFileEntries(cfg);
156
165
  const ocEntries = entries.filter(e => e.agentName === 'openclaw');
@@ -160,7 +169,7 @@ describe('buildFileEntries', () => {
160
169
  assert.ok(clEntries.length >= 1);
161
170
  assert.ok(clEntries.every(e => e.repoRel.startsWith('agents/claude/') || e.repoRel.startsWith('shared/')));
162
171
  });
163
- it('jsonFields 条目带 jsonExtract 元数据', () => {
172
+ it('jsonFields entries carry jsonExtract metadata', () => {
164
173
  const cfg = mkCfg();
165
174
  const entries = buildFileEntries(cfg);
166
175
  const jfEntries = entries.filter(e => e.jsonExtract);
@@ -170,14 +179,14 @@ describe('buildFileEntries', () => {
170
179
  assert.deepStrictEqual(claudeJf.jsonExtract.fields, ['mcpServers']);
171
180
  assert.ok(claudeJf.encrypt);
172
181
  });
173
- it('--agent 过滤只返回指定 agent 条目', () => {
182
+ it('--agent filter returns only specified agent entries', () => {
174
183
  const cfg = mkCfg();
175
184
  const entries = buildFileEntries(cfg, undefined, 'gemini');
176
185
  assert.ok(entries.length > 0);
177
186
  assert.ok(entries.every(e => e.agentName === 'gemini'));
178
187
  });
179
- it('shared skills 条目带 shared 标识', () => {
180
- // 先创建 skill 文件让 walkDir 能扫到
188
+ it('shared skills entries carry shared identifier', () => {
189
+ // Create skill file so walkDir can find it
181
190
  writeFile(path.join(WS_CL, 'skills', 'test.md'), '# test skill');
182
191
  const cfg = mkCfg();
183
192
  const entries = buildFileEntries(cfg);
@@ -185,41 +194,41 @@ describe('buildFileEntries', () => {
185
194
  assert.ok(sharedSkills.length >= 1);
186
195
  fs.rmSync(path.join(WS_CL, 'skills'), { recursive: true, force: true });
187
196
  });
188
- it('repoRel 去重:多 agent 同名 skill 只保留先出现者', () => {
197
+ it('repoRel dedup: same-name skill across agents keeps only first', () => {
189
198
  writeFile(path.join(WS_CL, 'skills', 'dup.md'), 'claude version');
190
199
  writeFile(path.join(WS_OC, 'skills', 'dup.md'), 'openclaw version');
191
200
  const cfg = mkCfg();
192
201
  const entries = buildFileEntries(cfg);
193
202
  const dupEntries = entries.filter(e => e.repoRel === path.join('shared', 'skills', 'dup.md'));
194
- assert.equal(dupEntries.length, 1, '同名 skill 应去重为 1 ');
195
- // 清理
203
+ assert.equal(dupEntries.length, 1, 'same-name skill should be deduped to 1 entry');
204
+ // Cleanup
196
205
  fs.rmSync(path.join(WS_CL, 'skills'), { recursive: true, force: true });
197
206
  fs.rmSync(path.join(WS_OC, 'skills'), { recursive: true, force: true });
198
207
  });
199
208
  });
200
- // ── stageToRepo + restoreFromRepo 往返测试 ──────────────────────────
201
- describe('stageToRepo → restoreFromRepo 往返', () => {
202
- it('整文件同步:push pull 内容一致', async () => {
209
+ // ── stageToRepo + restoreFromRepo round-trip tests ──────────────────
210
+ describe('stageToRepo → restoreFromRepo round-trip', () => {
211
+ it('whole-file sync: push then pull content matches', async () => {
203
212
  writeFile(path.join(WS_OC, 'MEMORY.md'), '# 记忆内容 v1');
204
213
  writeFile(path.join(WS_OC, 'SOUL.md'), '# 灵魂');
205
214
  writeFile(path.join(WS_CL, 'CLAUDE.md'), '# Claude 指令');
206
215
  const cfg = mkCfg({ shared: { skills: { sources: [] }, mcp: { sources: [] }, syncFiles: [] } });
207
216
  const pushResult = await syncEngine.stageToRepo(cfg);
208
217
  assert.ok(pushResult.synced.length >= 3);
209
- // repo 中文件应存在
218
+ // Repo files should exist
210
219
  assert.ok(fs.existsSync(path.join(REPO, 'agents/openclaw/MEMORY.md.enc')));
211
220
  assert.ok(fs.existsSync(path.join(REPO, 'agents/openclaw/SOUL.md')));
212
221
  assert.ok(fs.existsSync(path.join(REPO, 'agents/claude/CLAUDE.md')));
213
- // 模拟新环境:删除本地文件
222
+ // Simulate new environment: delete local files
214
223
  fs.unlinkSync(path.join(WS_OC, 'MEMORY.md'));
215
224
  fs.unlinkSync(path.join(WS_CL, 'CLAUDE.md'));
216
225
  const pullResult = await syncEngine.restoreFromRepo(cfg);
217
226
  assert.ok(pullResult.synced.length >= 3);
218
- // 还原后内容一致
227
+ // Restored content should match
219
228
  assert.equal(fs.readFileSync(path.join(WS_OC, 'MEMORY.md'), 'utf-8'), '# 记忆内容 v1');
220
229
  assert.equal(fs.readFileSync(path.join(WS_CL, 'CLAUDE.md'), 'utf-8'), '# Claude 指令');
221
230
  });
222
- it('jsonFieldspush 只提取指定字段,pull merge 回不丢其他字段', async () => {
231
+ it('jsonFields: push extracts specified fields only, pull merge-back preserves others', async () => {
223
232
  const claudeJson = {
224
233
  mcpServers: { playwright: { type: 'stdio', cmd: 'npx' } },
225
234
  tipsHistory: { tip1: 5 },
@@ -237,41 +246,41 @@ describe('stageToRepo → restoreFromRepo 往返', () => {
237
246
  writeFile(path.join(WS_GE, 'settings.json'), JSON.stringify(geminiJson, null, 2));
238
247
  const cfg = mkCfg({ shared: { skills: { sources: [] }, mcp: { sources: [] }, syncFiles: [] } });
239
248
  await syncEngine.stageToRepo(cfg);
240
- // repo claude 的 mcpServers.json.enc 应只含 mcpServers
249
+ // Repo: Claude mcpServers.json.enc should only contain mcpServers
241
250
  const claudeRepoFile = path.join(REPO, 'agents/claude/mcpServers.json.enc');
242
251
  assert.ok(fs.existsSync(claudeRepoFile));
243
252
  const decrypted = JSON.parse(cryptoEngine.decryptString(fs.readFileSync(claudeRepoFile, 'utf-8').trim(), KEY));
244
253
  assert.deepStrictEqual(Object.keys(decrypted), ['mcpServers']);
245
- assert.ok(!('tipsHistory' in decrypted), 'tipsHistory 不应被提取');
246
- // repo gemini 的 settings-sync.json 应只含 security + model
254
+ assert.ok(!('tipsHistory' in decrypted), 'tipsHistory should not be extracted');
255
+ // Repo: Gemini settings-sync.json should only contain security + model
247
256
  const geminiRepoFile = path.join(REPO, 'agents/gemini/settings-sync.json');
248
257
  assert.ok(fs.existsSync(geminiRepoFile));
249
258
  const geminiPartial = JSON.parse(fs.readFileSync(geminiRepoFile, 'utf-8'));
250
259
  assert.deepStrictEqual(Object.keys(geminiPartial).sort(), ['model', 'security']);
251
- // 模拟远端修改 mcpServers(添加新 server
260
+ // Simulate remote modification of mcpServers (add new server)
252
261
  const modified = { mcpServers: { playwright: { type: 'stdio', cmd: 'npx' }, gongfeng: { type: 'sse' } } };
253
262
  const enc = cryptoEngine.encryptString(JSON.stringify(modified, null, 2), KEY);
254
263
  fs.writeFileSync(claudeRepoFile, enc, 'utf-8');
255
- // pull 回来
264
+ // Pull back
256
265
  await syncEngine.restoreFromRepo(cfg);
257
- // .claude.json 应保留 tipsHistory/numStartups/projectsmcpServers 被更新
266
+ // .claude.json should preserve tipsHistory/numStartups/projects, mcpServers updated
258
267
  const restored = JSON.parse(fs.readFileSync(path.join(WS_CL, '.claude.json'), 'utf-8'));
259
268
  assert.deepStrictEqual(restored.mcpServers, modified.mcpServers);
260
- assert.equal(restored.tipsHistory.tip1, 5, 'tipsHistory 不应被破坏');
261
- assert.equal(restored.numStartups, 42, 'numStartups 不应被破坏');
269
+ assert.equal(restored.tipsHistory.tip1, 5, 'tipsHistory should not be destroyed');
270
+ assert.equal(restored.numStartups, 42, 'numStartups should not be destroyed');
262
271
  assert.deepStrictEqual(restored.projects, { '/tmp/proj': { cost: 100 } });
263
272
  });
264
273
  });
265
- // ── distributeShared 测试(通过 stageToRepo 间接验证) ───────────────
266
- describe('skill 跨 agent 共享', () => {
267
- it('Claude 新增 skill → push OpenClaw 也获得该 skill', async () => {
274
+ // ── distributeShared tests (verified indirectly via stageToRepo) ────
275
+ describe('cross-agent skill sharing', () => {
276
+ it('Claude adds skill → push distributes to OpenClaw', async () => {
268
277
  writeFile(path.join(WS_CL, 'skills', 'new-skill.md'), '# New Skill');
269
- // OpenClaw 没有这个 skill
278
+ // OpenClaw does not have this skill
270
279
  const ocSkillPath = path.join(WS_OC, 'skills', 'new-skill.md');
271
280
  if (fs.existsSync(ocSkillPath))
272
281
  fs.unlinkSync(ocSkillPath);
273
282
  const cfg = mkCfg();
274
- // 准备必需的工作区文件
283
+ // Prepare required workspace files
275
284
  writeFile(path.join(WS_OC, 'MEMORY.md'), '# M');
276
285
  writeFile(path.join(WS_OC, 'SOUL.md'), '# S');
277
286
  writeFile(path.join(WS_CL, 'CLAUDE.md'), '# C');
@@ -279,26 +288,26 @@ describe('skill 跨 agent 共享', () => {
279
288
  writeFile(path.join(WS_GE, 'settings.json'), '{"security":{},"model":{}}');
280
289
  writeFile(path.join(WS_OC, 'config', 'mcporter.json'), '{"mcpServers":{}}');
281
290
  await syncEngine.stageToRepo(cfg);
282
- // distributeShared 应将 skill 复制到 OpenClaw
283
- assert.ok(fs.existsSync(ocSkillPath), 'OpenClaw 应获得 Claude new-skill.md');
291
+ // distributeShared should copy skill to OpenClaw
292
+ assert.ok(fs.existsSync(ocSkillPath), 'OpenClaw should receive Claude new-skill.md');
284
293
  assert.equal(fs.readFileSync(ocSkillPath, 'utf-8'), '# New Skill');
285
- // repo shared/skills/ 也应有
294
+ // Repo shared/skills/ should also have it
286
295
  assert.ok(fs.existsSync(path.join(REPO, 'shared', 'skills', 'new-skill.md')));
287
296
  });
288
- it('删除 skill push 另一个 agent 有则复制回来(只增不删)', async () => {
289
- // 两个 agent 都有 old-skill
297
+ it('deleted skill is copied back from another agent (add-only)', async () => {
298
+ // Both agents have old-skill
290
299
  writeFile(path.join(WS_CL, 'skills', 'old-skill.md'), '# Old');
291
300
  writeFile(path.join(WS_OC, 'skills', 'old-skill.md'), '# Old');
292
- // OpenClaw 删除
301
+ // Delete from OpenClaw
293
302
  fs.unlinkSync(path.join(WS_OC, 'skills', 'old-skill.md'));
294
303
  const cfg = mkCfg();
295
304
  await syncEngine.stageToRepo(cfg);
296
- // distributeShared 会从 Claude 复制回 OpenClaw
297
- assert.ok(fs.existsSync(path.join(WS_OC, 'skills', 'old-skill.md')), '删除的 skill 被从另一个 agent 复制回来');
305
+ // distributeShared copies back from Claude to OpenClaw
306
+ assert.ok(fs.existsSync(path.join(WS_OC, 'skills', 'old-skill.md')), 'deleted skill is copied back from another agent');
298
307
  });
299
308
  });
300
- describe('MCP 跨 agent 共享', () => {
301
- it('Claude 新增 MCP server → push OpenClaw 也获得', async () => {
309
+ describe('cross-agent MCP sharing', () => {
310
+ it('Claude adds MCP server → push distributes to OpenClaw', async () => {
302
311
  writeFile(path.join(WS_CL, '.claude.json'), JSON.stringify({
303
312
  mcpServers: { playwright: { type: 'stdio' }, newServer: { type: 'sse' } },
304
313
  tipsHistory: {},
@@ -312,12 +321,12 @@ describe('MCP 跨 agent 共享', () => {
312
321
  writeFile(path.join(WS_CL, 'CLAUDE.md'), '# C');
313
322
  const cfg = mkCfg();
314
323
  await syncEngine.stageToRepo(cfg);
315
- // OpenClaw mcporter.json 应包含 newServer
324
+ // OpenClaw mcporter.json should contain newServer
316
325
  const ocMcp = JSON.parse(fs.readFileSync(path.join(WS_OC, 'config', 'mcporter.json'), 'utf-8'));
317
- assert.ok('newServer' in ocMcp.mcpServers, 'OpenClaw 应获得 Claude newServer');
326
+ assert.ok('newServer' in ocMcp.mcpServers, 'OpenClaw should receive Claude newServer');
318
327
  assert.deepStrictEqual(ocMcp.mcpServers.newServer, { type: 'sse' });
319
328
  });
320
- it('分发 MCP 不覆盖已有配置', async () => {
329
+ it('MCP distribution does not overwrite existing config', async () => {
321
330
  writeFile(path.join(WS_CL, '.claude.json'), JSON.stringify({
322
331
  mcpServers: { playwright: { type: 'stdio', version: 'claude' } },
323
332
  }, null, 2));
@@ -330,20 +339,20 @@ describe('MCP 跨 agent 共享', () => {
330
339
  writeFile(path.join(WS_CL, 'CLAUDE.md'), '# C');
331
340
  const cfg = mkCfg();
332
341
  await syncEngine.stageToRepo(cfg);
333
- // 两个 agent playwright 配置应保持各自的 version,不被覆盖
342
+ // Both agents' playwright config should retain their own version
334
343
  const clMcp = JSON.parse(fs.readFileSync(path.join(WS_CL, '.claude.json'), 'utf-8'));
335
344
  const ocMcp = JSON.parse(fs.readFileSync(path.join(WS_OC, 'config', 'mcporter.json'), 'utf-8'));
336
345
  assert.equal(clMcp.mcpServers.playwright.version, 'claude');
337
346
  assert.equal(ocMcp.mcpServers.playwright.version, 'openclaw');
338
347
  });
339
348
  });
340
- // ── 一键还原(新服务器场景) ────────────────────────────────────────
341
- describe('新环境一键还原', () => {
342
- it('工作区为空时 pull 能完整还原所有 agent 配置', async () => {
349
+ // ── One-click restore (new server scenario) ─────────────────────────
350
+ describe('one-click restore on new environment', () => {
351
+ it('pull fully restores all agent configs when workspace is empty', async () => {
343
352
  // Clean repo to avoid leftover data from previous tests
344
353
  fs.rmSync(REPO, { recursive: true, force: true });
345
354
  fs.mkdirSync(REPO, { recursive: true });
346
- // push 一份完整数据到 repo
355
+ // Push a complete dataset to repo
347
356
  writeFile(path.join(WS_OC, 'MEMORY.md'), '# 永久记忆');
348
357
  writeFile(path.join(WS_OC, 'SOUL.md'), '# 灵魂身份');
349
358
  writeFile(path.join(WS_CL, 'CLAUDE.md'), '# Claude 全局指令');
@@ -364,37 +373,37 @@ describe('新环境一键还原', () => {
364
373
  writeFile(path.join(WS_CU, 'mcp.json'), '{"mcpServers":{}}');
365
374
  const cfg = mkCfg();
366
375
  await syncEngine.stageToRepo(cfg);
367
- // 模拟新服务器:清空所有工作区
368
- for (const d of [WS_OC, WS_CL, WS_GE, WS_CB, WS_WB, WS_CU]) {
376
+ // Simulate new server: clear all workspaces
377
+ for (const d of [WS_OC, WS_CL, WS_GE, WS_CB, WS_WB, WS_CU, WS_CX]) {
369
378
  fs.rmSync(d, { recursive: true, force: true });
370
379
  fs.mkdirSync(d, { recursive: true });
371
380
  }
372
- // pull 还原
381
+ // Pull restore
373
382
  const pullResult = await syncEngine.restoreFromRepo(cfg);
374
383
  assert.ok(pullResult.synced.length >= 4);
375
- // OpenClaw 还原
384
+ // OpenClaw restored
376
385
  assert.equal(fs.readFileSync(path.join(WS_OC, 'MEMORY.md'), 'utf-8'), '# 永久记忆');
377
386
  assert.equal(fs.readFileSync(path.join(WS_OC, 'SOUL.md'), 'utf-8'), '# 灵魂身份');
378
- // Claude 还原
387
+ // Claude restored
379
388
  assert.equal(fs.readFileSync(path.join(WS_CL, 'CLAUDE.md'), 'utf-8'), '# Claude 全局指令');
380
- // Claude jsonFields merge-back(新环境本地无 .claude.json → 从空 {} merge)
389
+ // Claude jsonFields merge-back (new env has no .claude.json → merge from empty {})
381
390
  const clJson = JSON.parse(fs.readFileSync(path.join(WS_CL, '.claude.json'), 'utf-8'));
382
391
  assert.deepStrictEqual(clJson.mcpServers, { server1: { cmd: 'test' } });
383
- assert.ok(!('tipsHistory' in clJson), '新环境不应有 tipsHistory');
392
+ assert.ok(!('tipsHistory' in clJson), 'new env should not have tipsHistory');
384
393
  // Gemini jsonFields merge-back
385
394
  const geJson = JSON.parse(fs.readFileSync(path.join(WS_GE, 'settings.json'), 'utf-8'));
386
395
  assert.deepStrictEqual(geJson.security, { auth: 'test' });
387
396
  assert.deepStrictEqual(geJson.model, { name: 'gemini-2' });
388
- assert.ok(!('cache' in geJson), '新环境不应有 cache');
389
- // shared skills 分发到所有 agent
390
- assert.ok(fs.existsSync(path.join(WS_CL, 'skills', 'review.md')), 'Claude 应还原 skill');
391
- assert.ok(fs.existsSync(path.join(WS_OC, 'skills', 'review.md')), 'OpenClaw 也应获得共享 skill');
397
+ assert.ok(!('cache' in geJson), 'new env should not have cache');
398
+ // Shared skills distributed to all agents
399
+ assert.ok(fs.existsSync(path.join(WS_CL, 'skills', 'review.md')), 'Claude should restore skill');
400
+ assert.ok(fs.existsSync(path.join(WS_OC, 'skills', 'review.md')), 'OpenClaw should also receive shared skill');
392
401
  });
393
402
  });
394
- // ── 删除传播测试 ────────────────────────────────────────────────────
395
- describe('删除传播', () => {
396
- it('所有 agent 都删除的 skillpush 后从 repo 清理', async () => {
397
- // push 一个 skill repo
403
+ // ── Delete propagation tests ────────────────────────────────────────
404
+ describe('delete propagation', () => {
405
+ it('skill deleted from all agents pruned from repo on push', async () => {
406
+ // Push a skill to repo first
398
407
  writeFile(path.join(WS_CL, 'skills', 'obsolete.md'), '# 过时的 skill');
399
408
  writeFile(path.join(WS_OC, 'MEMORY.md'), '# M');
400
409
  writeFile(path.join(WS_OC, 'SOUL.md'), '# S');
@@ -405,7 +414,7 @@ describe('删除传播', () => {
405
414
  const cfg = mkCfg();
406
415
  await syncEngine.stageToRepo(cfg);
407
416
  assert.ok(fs.existsSync(path.join(REPO, 'shared', 'skills', 'obsolete.md')));
408
- // 从所有 agent 删除(Claude 有,OpenClaw CodeBuddy 也被分发了)
417
+ // Delete from all agents (Claude has it, OpenClaw and CodeBuddy received it via distribution)
409
418
  const clSkill = path.join(WS_CL, 'skills', 'obsolete.md');
410
419
  const ocSkill = path.join(WS_OC, 'skills', 'obsolete.md');
411
420
  const cbSkill = path.join(WS_CB, 'skills', 'obsolete.md');
@@ -415,14 +424,14 @@ describe('删除传播', () => {
415
424
  fs.unlinkSync(ocSkill);
416
425
  if (fs.existsSync(cbSkill))
417
426
  fs.unlinkSync(cbSkill);
418
- // 再次 push
427
+ // Push again
419
428
  const result = await syncEngine.stageToRepo(cfg);
420
- // repo 中应被清理
421
- assert.ok(!fs.existsSync(path.join(REPO, 'shared', 'skills', 'obsolete.md')), '所有 agent 都删除后,repo 中也应被清理');
429
+ // Repo should be cleaned
430
+ assert.ok(!fs.existsSync(path.join(REPO, 'shared', 'skills', 'obsolete.md')), 'skill deleted from all agents should be pruned from repo');
422
431
  assert.ok(result.deleted.includes(path.join('shared', 'skills', 'obsolete.md')));
423
432
  });
424
- it('repo 有但本地所有 agent 都无的整文件push 后从 repo 清理', async () => {
425
- // 手动在 repo 中创建一个「幽灵文件」(模拟旧版残留)
433
+ it('whole file in repo but absent from all local agents pruned on push', async () => {
434
+ // Manually create a ghost file in repo (simulating old version leftovers)
426
435
  writeFile(path.join(REPO, 'agents', 'openclaw', 'GHOST.md'), '幽灵');
427
436
  writeFile(path.join(WS_OC, 'MEMORY.md'), '# M');
428
437
  writeFile(path.join(WS_OC, 'SOUL.md'), '# S');
@@ -432,11 +441,11 @@ describe('删除传播', () => {
432
441
  writeFile(path.join(WS_OC, 'config', 'mcporter.json'), '{"mcpServers":{}}');
433
442
  const cfg = mkCfg();
434
443
  const result = await syncEngine.stageToRepo(cfg);
435
- assert.ok(!fs.existsSync(path.join(REPO, 'agents', 'openclaw', 'GHOST.md')), 'repo 中的幽灵文件应被清理');
444
+ assert.ok(!fs.existsSync(path.join(REPO, 'agents', 'openclaw', 'GHOST.md')), 'ghost file in repo should be pruned');
436
445
  assert.ok(result.deleted.includes(path.join('agents', 'openclaw', 'GHOST.md')));
437
446
  });
438
- it('pull repo 中删除的 skill 不再分发到任何 agent', async () => {
439
- // 先创建 skill push
447
+ it('deleted skill from repo is not redistributed on pull', async () => {
448
+ // Create skill and push
440
449
  writeFile(path.join(WS_CL, 'skills', 'temp.md'), '# temp');
441
450
  writeFile(path.join(WS_OC, 'MEMORY.md'), '# M');
442
451
  writeFile(path.join(WS_OC, 'SOUL.md'), '# S');
@@ -446,7 +455,7 @@ describe('删除传播', () => {
446
455
  writeFile(path.join(WS_OC, 'config', 'mcporter.json'), '{"mcpServers":{}}');
447
456
  const cfg = mkCfg();
448
457
  await syncEngine.stageToRepo(cfg);
449
- // 从所有 agent 删除并 push(触发 repo 清理)
458
+ // Delete from all agents and push (triggers repo cleanup)
450
459
  for (const d of [WS_CL, WS_OC, WS_CB]) {
451
460
  const f = path.join(d, 'skills', 'temp.md');
452
461
  if (fs.existsSync(f))
@@ -454,19 +463,19 @@ describe('删除传播', () => {
454
463
  }
455
464
  await syncEngine.stageToRepo(cfg);
456
465
  assert.ok(!fs.existsSync(path.join(REPO, 'shared', 'skills', 'temp.md')));
457
- // pull — temp.md 不应出现在任何 agent
466
+ // Pull — temp.md should not appear in any agent
458
467
  await syncEngine.restoreFromRepo(cfg);
459
- assert.ok(!fs.existsSync(path.join(WS_CL, 'skills', 'temp.md')), 'Claude 不应恢复已删除的 skill');
460
- assert.ok(!fs.existsSync(path.join(WS_OC, 'skills', 'temp.md')), 'OpenClaw 不应恢复已删除的 skill');
468
+ assert.ok(!fs.existsSync(path.join(WS_CL, 'skills', 'temp.md')), 'Claude should not restore deleted skill');
469
+ assert.ok(!fs.existsSync(path.join(WS_OC, 'skills', 'temp.md')), 'OpenClaw should not restore deleted skill');
461
470
  });
462
471
  });
463
- // ── localOnly 检测测试 ──────────────────────────────────────────────
464
- describe('pull 检测本地独有文件', () => {
465
- it('本地有但 repo 无的文件标记为 localOnly', async () => {
466
- // 清空 repo
472
+ // ── localOnly detection tests ───────────────────────────────────────
473
+ describe('pull detects local-only files', () => {
474
+ it('files present locally but absent from repo are marked localOnly', async () => {
475
+ // Clear repo
467
476
  fs.rmSync(REPO, { recursive: true, force: true });
468
477
  fs.mkdirSync(REPO, { recursive: true });
469
- // 本地有文件
478
+ // Local files exist
470
479
  writeFile(path.join(WS_OC, 'MEMORY.md'), '# 本地记忆');
471
480
  writeFile(path.join(WS_OC, 'SOUL.md'), '# 灵魂');
472
481
  writeFile(path.join(WS_CL, 'CLAUDE.md'), '# 指令');
@@ -474,14 +483,14 @@ describe('pull 检测本地独有文件', () => {
474
483
  writeFile(path.join(WS_GE, 'settings.json'), '{"security":{},"model":{}}');
475
484
  const cfg = mkCfg({ shared: { skills: { sources: [] }, mcp: { sources: [] }, syncFiles: [] } });
476
485
  const result = await syncEngine.restoreFromRepo(cfg);
477
- // 应检测到本地独有文件
478
- assert.ok(result.localOnly.length >= 2, `应有 localOnly 文件,实际: ${result.localOnly.length}`);
479
- assert.ok(result.localOnly.some(f => f.includes('MEMORY.md')), 'MEMORY.md 应标记为 localOnly');
486
+ // Should detect local-only files
487
+ assert.ok(result.localOnly.length >= 2, `should have localOnly files, actual: ${result.localOnly.length}`);
488
+ assert.ok(result.localOnly.some(f => f.includes('MEMORY.md')), 'MEMORY.md should be marked as localOnly');
480
489
  });
481
- it('jsonFields localOnly 不误报提取字段为空则不标记', async () => {
490
+ it('jsonFields localOnly no false positive empty extracted fields not marked', async () => {
482
491
  fs.rmSync(REPO, { recursive: true, force: true });
483
492
  fs.mkdirSync(REPO, { recursive: true });
484
- // .claude.json 存在但 mcpServers 字段为空
493
+ // .claude.json exists but mcpServers field is empty
485
494
  writeFile(path.join(WS_CL, '.claude.json'), JSON.stringify({
486
495
  tipsHistory: { x: 1 },
487
496
  numStartups: 5,
@@ -496,17 +505,17 @@ describe('pull 检测本地独有文件', () => {
496
505
  writeFile(path.join(WS_CU, 'mcp.json'), '{}');
497
506
  const cfg = mkCfg({ shared: { skills: { sources: [] }, mcp: { sources: [] }, syncFiles: [] } });
498
507
  const result = await syncEngine.restoreFromRepo(cfg);
499
- // .claude.json 没有 mcpServers 字段,不应标记为 localOnly
500
- assert.ok(!result.localOnly.some(f => f.includes('mcpServers')), 'mcpServers 字段为空时不应标记为 localOnly');
508
+ // .claude.json has no mcpServers field, should not be marked as localOnly
509
+ assert.ok(!result.localOnly.some(f => f.includes('mcpServers')), 'empty mcpServers field should not be marked as localOnly');
501
510
  });
502
511
  });
503
- // ── shared MCP agent 分发(pull 时) ─────────────────────────────
504
- describe('pull 时 shared MCP agent 分发', () => {
505
- it(' repo pull MCP 配置应分发到所有 agent', async () => {
506
- // 清理 repo
512
+ // ── shared MCP cross-agent distribution (on pull) ───────────────────
513
+ describe('shared MCP cross-agent distribution on pull', () => {
514
+ it('MCP config pulled from repo is distributed to all agents', async () => {
515
+ // Clean repo
507
516
  fs.rmSync(REPO, { recursive: true, force: true });
508
517
  fs.mkdirSync(REPO, { recursive: true });
509
- // 准备 Claude MCP configOpenClaw 没有
518
+ // Claude has MCP config, OpenClaw does not
510
519
  writeFile(path.join(WS_CL, '.claude.json'), JSON.stringify({
511
520
  mcpServers: { playwright: { type: 'stdio' } },
512
521
  }, null, 2));
@@ -516,20 +525,20 @@ describe('pull 时 shared MCP 跨 agent 分发', () => {
516
525
  writeFile(path.join(WS_OC, 'config', 'mcporter.json'), '{"mcpServers":{}}');
517
526
  writeFile(path.join(WS_GE, 'settings.json'), '{"security":{},"model":{}}');
518
527
  const cfg = mkCfg();
519
- // push 先(创建 repo 内容)
528
+ // Push first (create repo content)
520
529
  await syncEngine.stageToRepo(cfg);
521
- // 模拟新环境:清空 OpenClaw mcporter.json
530
+ // Simulate new environment: reset OpenClaw mcporter.json
522
531
  writeFile(path.join(WS_OC, 'config', 'mcporter.json'), '{"mcpServers":{}}');
523
- // pull
532
+ // Pull
524
533
  await syncEngine.restoreFromRepo(cfg);
525
- // OpenClaw 应从 shared MCP 获得 playwright
534
+ // OpenClaw should receive playwright from shared MCP
526
535
  const ocMcp = JSON.parse(fs.readFileSync(path.join(WS_OC, 'config', 'mcporter.json'), 'utf-8'));
527
- assert.ok('playwright' in (ocMcp.mcpServers ?? {}), 'OpenClaw 应通过 pull 获得 Claude MCP 配置');
536
+ assert.ok('playwright' in (ocMcp.mcpServers ?? {}), 'OpenClaw should receive Claude MCP config via pull');
528
537
  });
529
538
  });
530
- // ── JSON 解析失败容错 ───────────────────────────────────────────────
531
- describe('JSON 解析失败容错', () => {
532
- it('损坏的 JSON 源文件不导致 push 崩溃', async () => {
539
+ // ── JSON parse failure tolerance ────────────────────────────────────
540
+ describe('JSON parse failure tolerance', () => {
541
+ it('corrupted JSON source file does not crash push', async () => {
533
542
  writeFile(path.join(WS_CL, '.claude.json'), '{ invalid json !!!');
534
543
  writeFile(path.join(WS_CL, 'CLAUDE.md'), '# C');
535
544
  writeFile(path.join(WS_OC, 'MEMORY.md'), '# M');
@@ -537,9 +546,9 @@ describe('JSON 解析失败容错', () => {
537
546
  writeFile(path.join(WS_GE, 'settings.json'), '{"security":{},"model":{}}');
538
547
  writeFile(path.join(WS_OC, 'config', 'mcporter.json'), '{"mcpServers":{}}');
539
548
  const cfg = mkCfg({ shared: { skills: { sources: [] }, mcp: { sources: [] }, syncFiles: [] } });
540
- // 不应抛异常
549
+ // Should not throw
541
550
  const result = await syncEngine.stageToRepo(cfg);
542
- assert.ok(result.skipped.some(f => f.includes('mcpServers')), '损坏 JSON 对应的条目应被 skip');
551
+ assert.ok(result.skipped.some(f => f.includes('mcpServers')), 'corrupted JSON entry should be skipped');
543
552
  });
544
553
  });
545
554
  //# sourceMappingURL=sync.test.js.map