wangchuan 5.5.0 → 5.6.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 (46) hide show
  1. package/dist/bin/wangchuan.js +1 -1
  2. package/dist/src/commands/init.d.ts.map +1 -1
  3. package/dist/src/commands/init.js +67 -22
  4. package/dist/src/commands/init.js.map +1 -1
  5. package/dist/src/core/config.js +1 -1
  6. package/dist/src/core/config.js.map +1 -1
  7. package/dist/src/core/crypto.d.ts.map +1 -1
  8. package/dist/src/core/crypto.js +3 -2
  9. package/dist/src/core/crypto.js.map +1 -1
  10. package/dist/src/core/migrate.d.ts.map +1 -1
  11. package/dist/src/core/migrate.js +1 -0
  12. package/dist/src/core/migrate.js.map +1 -1
  13. package/dist/src/core/sync-restore.d.ts +19 -0
  14. package/dist/src/core/sync-restore.d.ts.map +1 -0
  15. package/dist/src/core/sync-restore.js +339 -0
  16. package/dist/src/core/sync-restore.js.map +1 -0
  17. package/dist/src/core/sync-shared.d.ts +30 -0
  18. package/dist/src/core/sync-shared.d.ts.map +1 -0
  19. package/dist/src/core/sync-shared.js +397 -0
  20. package/dist/src/core/sync-shared.js.map +1 -0
  21. package/dist/src/core/sync-stage.d.ts +57 -0
  22. package/dist/src/core/sync-stage.d.ts.map +1 -0
  23. package/dist/src/core/sync-stage.js +429 -0
  24. package/dist/src/core/sync-stage.js.map +1 -0
  25. package/dist/src/core/sync.d.ts +22 -46
  26. package/dist/src/core/sync.d.ts.map +1 -1
  27. package/dist/src/core/sync.js +64 -1267
  28. package/dist/src/core/sync.js.map +1 -1
  29. package/dist/src/i18n.d.ts.map +1 -1
  30. package/dist/src/i18n.js +27 -2
  31. package/dist/src/i18n.js.map +1 -1
  32. package/dist/test/crypto.test.js +2 -2
  33. package/dist/test/crypto.test.js.map +1 -1
  34. package/dist/test/git.test.d.ts +5 -0
  35. package/dist/test/git.test.d.ts.map +1 -0
  36. package/dist/test/git.test.js +90 -0
  37. package/dist/test/git.test.js.map +1 -0
  38. package/dist/test/migrate.test.d.ts +9 -0
  39. package/dist/test/migrate.test.d.ts.map +1 -0
  40. package/dist/test/migrate.test.js +133 -0
  41. package/dist/test/migrate.test.js.map +1 -0
  42. package/dist/test/sync-lock.test.d.ts +5 -0
  43. package/dist/test/sync-lock.test.d.ts.map +1 -0
  44. package/dist/test/sync-lock.test.js +94 -0
  45. package/dist/test/sync-lock.test.js.map +1 -0
  46. package/package.json +2 -2
@@ -0,0 +1,133 @@
1
+ /**
2
+ * migrate.test.ts — ensureMigrated unit tests
3
+ *
4
+ * Tests profile reconciliation behavior: verifies that user customizations
5
+ * (enabled state, workspacePath) are preserved while sync definitions are
6
+ * updated from latest agent definitions.
7
+ */
8
+ import { describe, it, after, before } from 'node:test';
9
+ import assert from 'node:assert/strict';
10
+ import fs from 'fs';
11
+ import os from 'os';
12
+ import path from 'path';
13
+ import { ensureMigrated } from '../src/core/migrate.js';
14
+ import { config, CONFIG_VERSION } from '../src/core/config.js';
15
+ import { buildDefaultProfiles, buildDefaultShared } from '../src/agents/index.js';
16
+ import { AGENT_NAMES } from '../src/types.js';
17
+ const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'wc-test-migrate-'));
18
+ // Create a synthetic ~/.wangchuan structure for config.save() calls inside ensureMigrated
19
+ const FAKE_HOME = path.join(TMP, 'home');
20
+ const WANGCHUAN_DIR = path.join(FAKE_HOME, '.wangchuan');
21
+ const REPO_DIR = path.join(WANGCHUAN_DIR, 'repo');
22
+ // Backup real paths and override
23
+ const REAL_SAVE = config.save.bind(config);
24
+ let savedCfg = null;
25
+ before(() => {
26
+ fs.mkdirSync(REPO_DIR, { recursive: true });
27
+ fs.mkdirSync(path.join(REPO_DIR, 'agents'), { recursive: true });
28
+ // Monkey-patch config.save to capture output without writing to real ~/.wangchuan
29
+ config.save = (cfg) => {
30
+ savedCfg = cfg;
31
+ };
32
+ });
33
+ after(() => {
34
+ // Restore real save
35
+ config.save = REAL_SAVE;
36
+ fs.rmSync(TMP, { recursive: true, force: true });
37
+ });
38
+ /** Build a minimal v2 config for testing */
39
+ function buildTestConfig(profileOverrides) {
40
+ const defaults = buildDefaultProfiles();
41
+ const profiles = {};
42
+ for (const name of AGENT_NAMES) {
43
+ const def = defaults[name];
44
+ const override = profileOverrides?.[name];
45
+ profiles[name] = { ...def, ...override };
46
+ }
47
+ return {
48
+ repo: 'git@github.com:test/test.git',
49
+ branch: 'main',
50
+ localRepoPath: REPO_DIR,
51
+ keyPath: path.join(WANGCHUAN_DIR, 'master.key'),
52
+ hostname: 'test-host',
53
+ version: CONFIG_VERSION,
54
+ profiles: { default: profiles },
55
+ shared: buildDefaultShared(),
56
+ };
57
+ }
58
+ describe('ensureMigrated — v2 config reconciliation', () => {
59
+ it('already-current v2 config passes through without error', () => {
60
+ const cfg = buildTestConfig();
61
+ const result = ensureMigrated(cfg);
62
+ assert.equal(result.version, CONFIG_VERSION);
63
+ });
64
+ it('preserves user enabled=true when reconciling', () => {
65
+ const cfg = buildTestConfig({ claude: { enabled: true } });
66
+ const result = ensureMigrated(cfg);
67
+ const claude = result.profiles.default.claude;
68
+ assert.equal(claude.enabled, true);
69
+ });
70
+ it('preserves user enabled=false when reconciling', () => {
71
+ const cfg = buildTestConfig({ openclaw: { enabled: false } });
72
+ const result = ensureMigrated(cfg);
73
+ const oc = result.profiles.default.openclaw;
74
+ assert.equal(oc.enabled, false);
75
+ });
76
+ it('preserves user workspacePath customization', () => {
77
+ const customPath = '/custom/workspace/path';
78
+ const cfg = buildTestConfig({ gemini: { workspacePath: customPath } });
79
+ const result = ensureMigrated(cfg);
80
+ assert.equal(result.profiles.default.gemini.workspacePath, customPath);
81
+ });
82
+ it('updates syncFiles from latest definitions', () => {
83
+ const defaults = buildDefaultProfiles();
84
+ const latestClaudeFiles = defaults.claude.syncFiles;
85
+ // Start with an outdated syncFiles (empty array)
86
+ const cfg = buildTestConfig({ claude: { syncFiles: [] } });
87
+ const result = ensureMigrated(cfg);
88
+ assert.deepEqual(result.profiles.default.claude.syncFiles, latestClaudeFiles);
89
+ });
90
+ it('result contains all agent names', () => {
91
+ const cfg = buildTestConfig();
92
+ const result = ensureMigrated(cfg);
93
+ for (const name of AGENT_NAMES) {
94
+ assert.ok(result.profiles.default[name] !== undefined, `agent ${name} should be present in reconciled profiles`);
95
+ }
96
+ });
97
+ it('result has shared config', () => {
98
+ const cfg = buildTestConfig();
99
+ const result = ensureMigrated(cfg);
100
+ assert.ok(result.shared !== undefined);
101
+ assert.ok(result.shared.skills !== undefined);
102
+ assert.ok(result.shared.mcp !== undefined);
103
+ });
104
+ });
105
+ describe('ensureMigrated — new agent detection', () => {
106
+ it('adds new agents from definitions with enabled=false', () => {
107
+ // Build a config missing the last agent to simulate an upgrade
108
+ const defaults = buildDefaultProfiles();
109
+ const profiles = {};
110
+ const allNames = [...AGENT_NAMES];
111
+ const removedAgent = allNames[allNames.length - 1];
112
+ for (const name of allNames) {
113
+ if (name === removedAgent)
114
+ continue;
115
+ profiles[name] = defaults[name];
116
+ }
117
+ const cfg = {
118
+ repo: 'git@github.com:test/test.git',
119
+ branch: 'main',
120
+ localRepoPath: REPO_DIR,
121
+ keyPath: path.join(WANGCHUAN_DIR, 'master.key'),
122
+ hostname: 'test-host',
123
+ version: CONFIG_VERSION,
124
+ profiles: { default: profiles },
125
+ shared: buildDefaultShared(),
126
+ };
127
+ const result = ensureMigrated(cfg);
128
+ const added = result.profiles.default[removedAgent];
129
+ assert.ok(added !== undefined, `${removedAgent} should be added`);
130
+ assert.equal(added.enabled, false, 'newly added agent should be disabled');
131
+ });
132
+ });
133
+ //# sourceMappingURL=migrate.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.test.js","sourceRoot":"","sources":["../../test/migrate.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * sync-lock.test.ts — syncLock unit tests
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=sync-lock.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-lock.test.d.ts","sourceRoot":"","sources":["../../test/sync-lock.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * sync-lock.test.ts — syncLock unit tests
3
+ */
4
+ import { describe, it, after } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import fs from 'fs';
7
+ import os from 'os';
8
+ import path from 'path';
9
+ import { execSync } from 'child_process';
10
+ import { syncLock } from '../src/core/sync-lock.js';
11
+ const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'wc-test-lock-'));
12
+ // Create a bare repo + work clone so acquire() has a valid repoPath for cleanDirtyState
13
+ const BARE = path.join(TMP, 'bare.git');
14
+ const REPO = path.join(TMP, 'repo');
15
+ execSync(`git init --bare "${BARE}"`);
16
+ // Use a seed clone to create initial commit, then re-clone for REPO
17
+ const SEED = path.join(TMP, 'seed');
18
+ execSync(`git clone "${BARE}" "${SEED}"`);
19
+ execSync('git config user.name "Test"', { cwd: SEED });
20
+ execSync('git config user.email "test@test.com"', { cwd: SEED });
21
+ fs.writeFileSync(path.join(SEED, 'init.txt'), 'seed');
22
+ execSync('git add . && git commit -m "seed"', { cwd: SEED });
23
+ // Push to whatever default branch was created
24
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: SEED, encoding: 'utf-8' }).trim();
25
+ execSync(`git push origin ${branch}`, { cwd: SEED });
26
+ // Clone fresh for test use
27
+ execSync(`git clone "${BARE}" "${REPO}"`);
28
+ after(() => {
29
+ // Always clean up lock to avoid polluting the real ~/.wangchuan/
30
+ syncLock.release();
31
+ fs.rmSync(TMP, { recursive: true, force: true });
32
+ });
33
+ describe('syncLock.acquire + release', () => {
34
+ it('basic cycle: lock file created then deleted', async () => {
35
+ await syncLock.acquire(REPO);
36
+ assert.ok(fs.existsSync(syncLock.lockPath), 'lock file should exist after acquire');
37
+ syncLock.release();
38
+ assert.ok(!fs.existsSync(syncLock.lockPath), 'lock file should be gone after release');
39
+ });
40
+ });
41
+ describe('syncLock.exists', () => {
42
+ it('returns true when lock present', async () => {
43
+ await syncLock.acquire(REPO);
44
+ assert.equal(syncLock.exists(), true);
45
+ syncLock.release();
46
+ });
47
+ it('returns false when absent', () => {
48
+ assert.equal(syncLock.exists(), false);
49
+ });
50
+ });
51
+ describe('syncLock.read', () => {
52
+ it('returns parsed lock with pid and startedAt', async () => {
53
+ await syncLock.acquire(REPO);
54
+ const lock = syncLock.read();
55
+ assert.ok(lock !== null);
56
+ assert.equal(lock.pid, process.pid);
57
+ assert.ok(typeof lock.startedAt === 'string');
58
+ syncLock.release();
59
+ });
60
+ it('returns null for corrupt lock file', () => {
61
+ // Write garbage to the lock path
62
+ fs.mkdirSync(path.dirname(syncLock.lockPath), { recursive: true });
63
+ fs.writeFileSync(syncLock.lockPath, '{{{not json', 'utf-8');
64
+ const lock = syncLock.read();
65
+ assert.equal(lock, null);
66
+ fs.unlinkSync(syncLock.lockPath);
67
+ });
68
+ });
69
+ describe('syncLock.acquire stale detection', () => {
70
+ it('cleans stale lock from dead PID', async () => {
71
+ // Write a lock with a PID that almost certainly does not exist
72
+ fs.mkdirSync(path.dirname(syncLock.lockPath), { recursive: true });
73
+ fs.writeFileSync(syncLock.lockPath, JSON.stringify({
74
+ startedAt: new Date().toISOString(),
75
+ pid: 999999,
76
+ }), 'utf-8');
77
+ // acquire should detect dead PID and succeed
78
+ await syncLock.acquire(REPO);
79
+ assert.ok(syncLock.exists());
80
+ const lock = syncLock.read();
81
+ assert.equal(lock.pid, process.pid);
82
+ syncLock.release();
83
+ });
84
+ it('throws when live PID holds lock', async () => {
85
+ await syncLock.acquire(REPO);
86
+ // Attempting a second acquire with live PID should throw
87
+ await assert.rejects(() => syncLock.acquire(REPO), (err) => {
88
+ assert.ok(err.message.includes(String(process.pid)));
89
+ return true;
90
+ });
91
+ syncLock.release();
92
+ });
93
+ });
94
+ //# sourceMappingURL=sync-lock.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-lock.test.js","sourceRoot":"","sources":["../../test/sync-lock.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wangchuan",
3
- "version": "5.5.0",
3
+ "version": "5.6.0",
4
4
  "description": "忘川 · AI 记忆同步系统 — 智能体记忆永不遗失",
5
5
  "bin": {
6
6
  "wangchuan": "./dist/bin/wangchuan.js"
@@ -17,7 +17,7 @@
17
17
  "postbuild": "chmod +x dist/bin/wangchuan.js",
18
18
  "prepublishOnly": "npm run build",
19
19
  "dev": "tsx bin/wangchuan.ts",
20
- "test": "node --import tsx/esm --test test/crypto.test.ts test/json-field.test.ts test/sync.test.ts test/sync-history.test.ts test/merge.test.ts",
20
+ "test": "node --import tsx/esm --test test/crypto.test.ts test/json-field.test.ts test/sync.test.ts test/sync-history.test.ts test/merge.test.ts test/git.test.ts test/sync-lock.test.ts test/migrate.test.ts",
21
21
  "typecheck": "tsc --noEmit"
22
22
  },
23
23
  "keywords": [