diff --git a/CHANGELOG.md b/CHANGELOG.md index a15c9ce..0b093a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to the XMemo CLI client will be documented in this file. -## [Unreleased] +## [0.4.193] - 2026-10-28 ### Added - **Muse Code (Meta) MCP Client Support**: @@ -11,9 +11,13 @@ All notable changes to the XMemo CLI client will be documented in this file. - Automatically initializes `schema_version: 1` when creating a new settings file while preserving existing settings and schema versions. - Seamlessly migrates legacy `mcp_servers` to `mcpServers` while failing closed if both coexist to prevent Muse loader member drop. - Detects pre-existing lowercase `xmemo` or legacy `memory-os` server entries to prevent duplicate definitions. - - Uses atomic write via temporary file and rename to prevent file corruption. + - Scopes atomic write via temporary file and rename to `muse-code` while preserving symlinks via `fs.realpath` across all JSON clients. - Config path defaults to `$XDG_CONFIG_HOME/muse/settings.json` or `~/.config/muse/settings.json`. + - Next steps guidance printed on `xmemo mcp add muse-code --write` and `xmemo setup muse`. +### Changed +- **Skill Version Pin Synchronization**: + - Updated pinned XMemo Skill version (`PINNED_SKILL_VERSION` and `PINNED_OPENCLAW_SKILL_VERSION`) in `src/core/pins.js` from `1.1.45` to `1.1.48` with dual-verified SHA-512 SRI integrity (`sha512-GlAxEoo1eOq0GBcVem+hbqEKrBegpRuIhv9asblgFvrAGz79cZM7FcOXLjz2R9DaK9lexL89yEIYTqnuaeKiig==`). ## [0.4.192] - 2026-10-19 diff --git a/README.md b/README.md index 99ba41c..a6f6c48 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ The CLI installs the verified XMemo Skill locally into agent skill folders or a - **Codex**: `~/.codex/skills/xmemo-memory` - **OpenClaw**: `~/.openclaw/skills/xmemo-memory` - All other 21 clients remain `null` until officially documented. -- Defaults to the pinned `@xmemo/skill@1.1.45` release from npm and verifies tarball integrity (sha512 SRI) before extraction. +- Defaults to the pinned `@xmemo/skill@1.1.48` release from npm and verifies tarball integrity (sha512 SRI) before extraction. - Override version with `--version ` or explicitly opt into the latest release via `--version latest`. - For air-gapped or offline installations, install from a local directory or packed tarball with `--from ` (optional `--integrity `). - Manage client skills with `skill status`, `skill update`, and `skill remove`. @@ -359,7 +359,7 @@ curl -fsSL https://xmemo.dev/skill/install | XMEMO_SKILL_AGENT=claude-code sh curl -fsSL https://xmemo.dev/skill/install | XMEMO_SKILL_AGENT=codex sh # OpenClaw: install via OpenClaw CLI -openclaw skills install @xmemo/xmemo --version 1.1.45 +openclaw skills install @xmemo/xmemo --version 1.1.48 # Windows (PowerShell): # $env:XMEMO_SKILL_AGENT="claude-code"; irm https://xmemo.dev/skill/install.ps1 | iex diff --git a/README_CN.md b/README_CN.md index 9831637..1b19d45 100644 --- a/README_CN.md +++ b/README_CN.md @@ -277,7 +277,7 @@ CLI 支持将经过官方完整性校验的 XMemo Skill 安装至各智能体专 - **Codex**: `~/.codex/skills/xmemo-memory` - **OpenClaw**: `~/.openclaw/skills/xmemo-memory` - 其余 21 款客户端在官方文档明确前均设为 `null` -- 默认安装 npm 上锁定的 `@xmemo/skill@1.1.45` 版本,并在解压前严格校验 tarball 完整性哈希(sha512 SRI)。 +- 默认安装 npm 上锁定的 `@xmemo/skill@1.1.48` 版本,并在解压前严格校验 tarball 完整性哈希(sha512 SRI)。 - 可通过 `--version ` 指定特定版本,或显式传入 `--version latest` 获取最新版本。 - 在离线或内网隔离环境中,可通过 `--from ` 从本地目录或打好的 tarball 进行离线安装(支持 `--integrity `)。 - 支持通过 `skill status` 查看安装状态,通过 `skill update` 快捷更新,通过 `skill remove` 干净移除。 @@ -344,7 +344,7 @@ curl -fsSL https://xmemo.dev/skill/install | XMEMO_SKILL_AGENT=claude-code sh curl -fsSL https://xmemo.dev/skill/install | XMEMO_SKILL_AGENT=codex sh # OpenClaw: 建议通过 OpenClaw 官方 CLI 安装 -openclaw skills install @xmemo/xmemo --version 1.1.45 +openclaw skills install @xmemo/xmemo --version 1.1.48 # Windows (PowerShell): # $env:XMEMO_SKILL_AGENT="claude-code"; irm https://xmemo.dev/skill/install.ps1 | iex diff --git a/package-lock.json b/package-lock.json index 963e2cf..7797a48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@xmemo/client", - "version": "0.4.192", + "version": "0.4.193", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@xmemo/client", - "version": "0.4.192", + "version": "0.4.193", "license": "MIT", "bin": { "client": "bin/memory-os.js", diff --git a/package.json b/package.json index 7907e4b..7b2adc0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xmemo/client", - "version": "0.4.192", + "version": "0.4.193", "description": "Official XMemo client: CLI, MCP setup and stdio server for XMemo, the user-owned Memory OS for AI agents.", "mcpName": "io.github.yonro/xmemo", "type": "module", diff --git a/server.json b/server.json index c3eea36..e5d3ad4 100644 --- a/server.json +++ b/server.json @@ -51,7 +51,7 @@ { "registryType": "npm", "identifier": "@xmemo/client", - "version": "0.4.192", + "version": "0.4.193", "runtimeHint": "npx", "transport": { "type": "stdio" diff --git a/src/core/pins.js b/src/core/pins.js index be074b8..03bf050 100644 --- a/src/core/pins.js +++ b/src/core/pins.js @@ -42,7 +42,7 @@ export const PINNED_OPENCLAW_PLUGIN_VERSION = '1.0.18'; export const PINNED_OPENCLAW_PLUGIN_SPEC = `clawhub:@xmemo/openclaw-memory@${PINNED_OPENCLAW_PLUGIN_VERSION}`; export const PINNED_OPENCLAW_PLUGIN_NAME = '@xmemo/openclaw-memory'; -export const PINNED_OPENCLAW_SKILL_VERSION = '1.1.45'; +export const PINNED_OPENCLAW_SKILL_VERSION = '1.1.48'; export const PINNED_OPENCLAW_SKILL_NAME = '@xmemo/xmemo'; export const PINNED_OPENCLAW_SKILL_SPEC = `${PINNED_OPENCLAW_SKILL_NAME}@${PINNED_OPENCLAW_SKILL_VERSION}`; @@ -50,6 +50,6 @@ export const PINNED_HERMES_PLUGIN_VERSION = '1.1.3'; export const PINNED_HERMES_CATALOG_ID = 'xmemo'; export const HERMES_CATALOG_ID = PINNED_HERMES_CATALOG_ID; -export const PINNED_SKILL_VERSION = '1.1.45'; -export const PINNED_SKILL_INTEGRITY = 'sha512-UGUKUlorT+YQs6/hb6sHHTrQqkm9n1aKtxgOUpKrhRONeDFHTceijpRHRvptKdpODOlA0fvwqDCoo3vw8l1Zng=='; +export const PINNED_SKILL_VERSION = '1.1.48'; +export const PINNED_SKILL_INTEGRITY = 'sha512-GlAxEoo1eOq0GBcVem+hbqEKrBegpRuIhv9asblgFvrAGz79cZM7FcOXLjz2R9DaK9lexL89yEIYTqnuaeKiig=='; diff --git a/test/cli-skill-install.test.js b/test/cli-skill-install.test.js index 684065e..8e29152 100644 --- a/test/cli-skill-install.test.js +++ b/test/cli-skill-install.test.js @@ -56,13 +56,13 @@ function spawnStub(calls, { code = 0, stdout = '', stderr = '', error = null, ta let callCode = code; if (code === 0) { if (args.includes('view')) { - callStdout = JSON.stringify('sha512-UGUKUlorT+YQs6/hb6sHHTrQqkm9n1aKtxgOUpKrhRONeDFHTceijpRHRvptKdpODOlA0fvwqDCoo3vw8l1Zng=='); + callStdout = JSON.stringify('sha512-GlAxEoo1eOq0GBcVem+hbqEKrBegpRuIhv9asblgFvrAGz79cZM7FcOXLjz2R9DaK9lexL89yEIYTqnuaeKiig=='); } else if (args.includes('pack')) { const destIdx = args.indexOf('--pack-destination'); if (destIdx !== -1 && args[destIdx + 1]) { const destDir = args[destIdx + 1]; - const fixturePath = path.resolve(__dirname, 'fixtures', 'xmemo-skill-1.1.45.fixture'); - const tgzPath = path.join(destDir, 'xmemo-skill-1.1.45.tgz'); + const fixturePath = path.resolve(__dirname, 'fixtures', 'xmemo-skill-1.1.48.fixture'); + const tgzPath = path.join(destDir, 'xmemo-skill-1.1.48.tgz'); try { if (tarballContent !== null) { fsSync.writeFileSync(tgzPath, tarballContent); @@ -71,7 +71,7 @@ function spawnStub(calls, { code = 0, stdout = '', stderr = '', error = null, ta } } catch {} } - callStdout = JSON.stringify([{ filename: 'xmemo-skill-1.1.45.tgz' }]); + callStdout = JSON.stringify([{ filename: 'xmemo-skill-1.1.48.tgz' }]); } } if (callStdout) { @@ -113,7 +113,7 @@ test('CLI skill install: default delegates to @xmemo/skill@pinned without shell' const packDest1 = calls[0].args[calls[0].args.indexOf('--pack-destination') + 1]; const expectedPackArgs1 = [ 'pack', - '@xmemo/skill@1.1.45', + '@xmemo/skill@1.1.48', '--pack-destination', packDest1, '--json' @@ -158,7 +158,7 @@ test('CLI skill install: default delegates to @xmemo/skill@pinned without shell' const report = JSON.parse(result.stdout); assert.equal(report.source, 'npm'); - assert.equal(report.spec, '1.1.45'); + assert.equal(report.spec, '1.1.48'); assert.equal(report.networkUsed, true); assert.equal(report.tokenSent, false); assert.equal(report.package, '@xmemo/skill'); @@ -313,7 +313,7 @@ test('CLI skill install: non-JSON human readable output', async () => { assert.equal(result.code, 0); assert.match(result.stdout, /Installed XMemo Skill 1\.1\.25 to/); - assert.match(result.stdout, /Source: npm \(@xmemo\/skill@1\.1\.45\) \(no credential used\)/); + assert.match(result.stdout, /Source: npm \(@xmemo\/skill@1\.1\.48\) \(no credential used\)/); }); test('CLI skill install: end-to-end with --from installs byte-identical skill', async () => { diff --git a/test/cli-status-client.test.js b/test/cli-status-client.test.js index 7e74433..f7ea52e 100644 --- a/test/cli-status-client.test.js +++ b/test/cli-status-client.test.js @@ -265,7 +265,7 @@ test('status codex with skill installed reports skill installed and version', as await fs.mkdir(codexSkillDir, { recursive: true }); await fs.writeFile( path.join(codexSkillDir, 'package.json'), - JSON.stringify({ name: '@xmemo/skill', version: '1.1.45' }, null, 2), + JSON.stringify({ name: '@xmemo/skill', version: '1.1.48' }, null, 2), 'utf8' ); @@ -278,7 +278,7 @@ test('status codex with skill installed reports skill installed and version', as const report = JSON.parse(jsonResult.stdout); assert.equal(report.clients[0].id, 'codex'); assert.equal(report.clients[0].resources.skill.installed, true); - assert.equal(report.clients[0].resources.skill.version, '1.1.45'); + assert.equal(report.clients[0].resources.skill.version, '1.1.48'); assert.ok(report.clients[0].resources.skill.path.includes('xmemo-memory')); // Status codex human-readable @@ -288,7 +288,7 @@ test('status codex with skill installed reports skill installed and version', as }); assert.equal(humanResult.code, 0); assert.match(humanResult.stdout, /Codex \(codex\):/); - assert.match(humanResult.stdout, /Skill: installed 1\.1\.45/); + assert.match(humanResult.stdout, /Skill: installed 1\.1\.48/); }); test('status with no client arguments and nothing detected reports service probes only', async (t) => { diff --git a/test/cli.test.js b/test/cli.test.js index da7b937..6f35f37 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -57,7 +57,7 @@ test('skill install delegates to @xmemo/skill npm package', async () => { const packDest = calls[0].args[calls[0].args.indexOf('--pack-destination') + 1]; const expectedPackArgs = [ 'pack', - '@xmemo/skill@1.1.45', + '@xmemo/skill@1.1.48', '--pack-destination', packDest, '--json' @@ -97,7 +97,7 @@ test('skill install delegates to @xmemo/skill npm package', async () => { assert.equal(report.installed, false); assert.equal(report.networkUsed, true); assert.equal(report.source, 'npm'); - assert.equal(report.spec, '1.1.45'); + assert.equal(report.spec, '1.1.48'); assert.equal(report.tokenSent, false); } finally { await fs.rm(tempDir, { recursive: true, force: true }); @@ -1900,7 +1900,7 @@ test('setup openclaw installs native plugin and skill without hosted MCP by defa assert.equal(plan.selectedClient.status.connected, true); assert.deepEqual(calls.map((call) => call.args), [ ['plugins', 'install', 'clawhub:@xmemo/openclaw-memory@1.0.18'], - ['skills', 'install', '@xmemo/xmemo', '--version', '1.1.45'], + ['skills', 'install', '@xmemo/xmemo', '--version', '1.1.48'], ['xmemo', 'status', '--json'] ]); }); @@ -2758,14 +2758,14 @@ function spawnStub(calls, { code = 0, stdout = '', stderr = '' } = {}) { const destIdx = args.indexOf('--pack-destination'); if (destIdx !== -1 && args[destIdx + 1]) { const destDir = args[destIdx + 1]; - const fixturePath = path.resolve(__dirname, 'fixtures', 'xmemo-skill-1.1.45.fixture'); - const tgzPath = path.join(destDir, 'xmemo-skill-1.1.45.tgz'); + const fixturePath = path.resolve(__dirname, 'fixtures', 'xmemo-skill-1.1.48.fixture'); + const tgzPath = path.join(destDir, 'xmemo-skill-1.1.48.tgz'); try { const bytes = readFileSync(fixturePath); writeFileSync(tgzPath, bytes); } catch {} } - callStdout = JSON.stringify([{ filename: 'xmemo-skill-1.1.45.tgz' }]); + callStdout = JSON.stringify([{ filename: 'xmemo-skill-1.1.48.tgz' }]); } if (callStdout) { child.stdout.emit('data', callStdout); diff --git a/test/fixtures/xmemo-skill-1.1.45.fixture b/test/fixtures/xmemo-skill-1.1.45.fixture deleted file mode 100644 index 44ee66f..0000000 Binary files a/test/fixtures/xmemo-skill-1.1.45.fixture and /dev/null differ diff --git a/test/fixtures/xmemo-skill-1.1.48.fixture b/test/fixtures/xmemo-skill-1.1.48.fixture new file mode 100644 index 0000000..5c382c0 Binary files /dev/null and b/test/fixtures/xmemo-skill-1.1.48.fixture differ diff --git a/test/pinned-installs.test.js b/test/pinned-installs.test.js index 400c4a7..3de72aa 100644 --- a/test/pinned-installs.test.js +++ b/test/pinned-installs.test.js @@ -98,13 +98,13 @@ function spawnStub(calls, { code = 0, stdout = '', stderr = '', error = null, ta let callCode = code; if (code === 0) { if (args.includes('view')) { - callStdout = JSON.stringify('sha512-UGUKUlorT+YQs6/hb6sHHTrQqkm9n1aKtxgOUpKrhRONeDFHTceijpRHRvptKdpODOlA0fvwqDCoo3vw8l1Zng=='); + callStdout = JSON.stringify('sha512-GlAxEoo1eOq0GBcVem+hbqEKrBegpRuIhv9asblgFvrAGz79cZM7FcOXLjz2R9DaK9lexL89yEIYTqnuaeKiig=='); } else if (args.includes('pack')) { const destIdx = args.indexOf('--pack-destination'); if (destIdx !== -1 && args[destIdx + 1]) { const destDir = args[destIdx + 1]; - const fixturePath = path.resolve(__dirname, 'fixtures', 'xmemo-skill-1.1.45.fixture'); - const tgzPath = path.join(destDir, 'xmemo-skill-1.1.45.tgz'); + const fixturePath = path.resolve(__dirname, 'fixtures', 'xmemo-skill-1.1.48.fixture'); + const tgzPath = path.join(destDir, 'xmemo-skill-1.1.48.tgz'); try { if (tarballContent !== null) { fsSync.writeFileSync(tgzPath, tarballContent); @@ -113,7 +113,7 @@ function spawnStub(calls, { code = 0, stdout = '', stderr = '', error = null, ta } } catch {} } - callStdout = JSON.stringify([{ filename: 'xmemo-skill-1.1.45.tgz' }]); + callStdout = JSON.stringify([{ filename: 'xmemo-skill-1.1.48.tgz' }]); } } if (callStdout) { diff --git a/test/release-check.test.js b/test/release-check.test.js index 683b6ce..3bfee37 100644 --- a/test/release-check.test.js +++ b/test/release-check.test.js @@ -19,10 +19,10 @@ const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json const CLI_VERSION = packageJson.version; test('check-release-version: parseSemver parses valid semver and rejects invalid strings', () => { - const v1 = parseSemver('1.1.45'); + const v1 = parseSemver('1.1.48'); assert.equal(v1.major, 1); assert.equal(v1.minor, 1); - assert.equal(v1.patch, 45); + assert.equal(v1.patch, 48); assert.equal(v1.prerelease, null); const v2 = parseSemver('v2.0.1-beta.2'); @@ -37,10 +37,10 @@ test('check-release-version: parseSemver parses valid semver and rejects invalid }); test('check-release-version: compareSemver correctly orders semver numerically', () => { - assert.ok(compareSemver('1.1.45', '1.1.35') > 0); - assert.ok(compareSemver('1.1.35', '1.1.45') < 0); - assert.equal(compareSemver('1.1.45', '1.1.45'), 0); - assert.ok(compareSemver('1.1.45', '1.1.9') > 0, '1.1.45 must be greater than 1.1.9'); + assert.ok(compareSemver('1.1.48', '1.1.35') > 0); + assert.ok(compareSemver('1.1.35', '1.1.48') < 0); + assert.equal(compareSemver('1.1.48', '1.1.48'), 0); + assert.ok(compareSemver('1.1.48', '1.1.9') > 0, '1.1.48 must be greater than 1.1.9'); assert.ok(compareSemver('2.0.0', '1.9.99') > 0); }); @@ -48,34 +48,34 @@ test('check-release-version: getLatestSkillTag finds the highest semver skill ta const sampleTags = [ 'skill-v1.1.9', 'skill-v1.1.35', - 'skill-v1.1.45', + 'skill-v1.1.48', 'skill-v1.1.10', 'cli-v0.4.191', 'random-tag' ]; const latest = getLatestSkillTag(sampleTags); assert.ok(latest); - assert.equal(latest.tag, 'skill-v1.1.45'); - assert.equal(latest.version, '1.1.45'); + assert.equal(latest.tag, 'skill-v1.1.48'); + assert.equal(latest.version, '1.1.48'); }); test('check-release-version: verifySkillPinGate passes when pinned version is current or newer', () => { - const tags = ['skill-v1.1.35', 'skill-v1.1.45']; + const tags = ['skill-v1.1.35', 'skill-v1.1.48']; - const passEqual = verifySkillPinGate({ pinnedVersion: '1.1.45', tagList: tags }); + const passEqual = verifySkillPinGate({ pinnedVersion: '1.1.48', tagList: tags }); assert.equal(passEqual.ok, true); - assert.equal(passEqual.latestVersion, '1.1.45'); + assert.equal(passEqual.latestVersion, '1.1.48'); - const passNewer = verifySkillPinGate({ pinnedVersion: '1.1.46', tagList: tags }); + const passNewer = verifySkillPinGate({ pinnedVersion: '1.1.49', tagList: tags }); assert.equal(passNewer.ok, true); }); test('check-release-version: verifySkillPinGate fails closed when pinned version is older than latest skill tag', () => { - const tags = ['skill-v1.1.35', 'skill-v1.1.45']; + const tags = ['skill-v1.1.35', 'skill-v1.1.48']; const failOlder = verifySkillPinGate({ pinnedVersion: '1.1.35', tagList: tags }); assert.equal(failOlder.ok, false); - assert.equal(failOlder.latestVersion, '1.1.45'); + assert.equal(failOlder.latestVersion, '1.1.48'); assert.match(failOlder.error, /older than the latest skill tag/); }); @@ -105,26 +105,26 @@ test('check-release-version CLI gate: fails closed with exit code 1 when pinned }); test('check-release-version: compareSemver throws when either version is invalid', () => { - assert.throws(() => compareSemver('invalid', '1.1.45'), /Cannot compare invalid semver/); - assert.throws(() => compareSemver('1.1.45', 'bad'), /Cannot compare invalid semver/); - assert.throws(() => compareSemver('', '1.1.45'), /Cannot compare invalid semver/); - assert.throws(() => compareSemver(null, '1.1.45'), /Cannot compare invalid semver/); + assert.throws(() => compareSemver('invalid', '1.1.48'), /Cannot compare invalid semver/); + assert.throws(() => compareSemver('1.1.48', 'bad'), /Cannot compare invalid semver/); + assert.throws(() => compareSemver('', '1.1.48'), /Cannot compare invalid semver/); + assert.throws(() => compareSemver(null, '1.1.48'), /Cannot compare invalid semver/); }); test('check-release-version: verifySkillPinGate fails when pinnedVersion is invalid semver', () => { - const res = verifySkillPinGate({ pinnedVersion: 'invalid', tagList: ['skill-v1.1.45'] }); + const res = verifySkillPinGate({ pinnedVersion: 'invalid', tagList: ['skill-v1.1.48'] }); assert.equal(res.ok, false); assert.match(res.error, /not a valid semver/); }); test('check-release-version: verifySkillPinGate fails closed when requireTags is true and no tags found', () => { - const res = verifySkillPinGate({ pinnedVersion: '1.1.45', tagList: [], requireTags: true }); + const res = verifySkillPinGate({ pinnedVersion: '1.1.48', tagList: [], requireTags: true }); assert.equal(res.ok, false); assert.match(res.error, /No skill-v tags found/); }); test('check-release-version: verifySkillPinGate skips with explicit warning when requireTags is false and no tags found', () => { - const res = verifySkillPinGate({ pinnedVersion: '1.1.45', tagList: [], requireTags: false }); + const res = verifySkillPinGate({ pinnedVersion: '1.1.48', tagList: [], requireTags: false }); assert.equal(res.ok, true); assert.equal(res.skipped, true); assert.match(res.warning, /No skill-v tags found in git repository; skill pin gate check was skipped/); diff --git a/test/skill-client-install.test.js b/test/skill-client-install.test.js index c87e38c..233d908 100644 --- a/test/skill-client-install.test.js +++ b/test/skill-client-install.test.js @@ -56,18 +56,18 @@ function spawnStub(calls, { code = 0, stdout = '', stderr = '', error = null } = let callCode = code; if (code === 0) { if (args.includes('view')) { - callStdout = JSON.stringify('sha512-UGUKUlorT+YQs6/hb6sHHTrQqkm9n1aKtxgOUpKrhRONeDFHTceijpRHRvptKdpODOlA0fvwqDCoo3vw8l1Zng=='); + callStdout = JSON.stringify('sha512-GlAxEoo1eOq0GBcVem+hbqEKrBegpRuIhv9asblgFvrAGz79cZM7FcOXLjz2R9DaK9lexL89yEIYTqnuaeKiig=='); } else if (args.includes('pack')) { const destIdx = args.indexOf('--pack-destination'); if (destIdx !== -1 && args[destIdx + 1]) { const destDir = args[destIdx + 1]; - const fixturePath = path.resolve(__dirname, 'fixtures', 'xmemo-skill-1.1.45.fixture'); - const tgzPath = path.join(destDir, 'xmemo-skill-1.1.45.tgz'); + const fixturePath = path.resolve(__dirname, 'fixtures', 'xmemo-skill-1.1.48.fixture'); + const tgzPath = path.join(destDir, 'xmemo-skill-1.1.48.tgz'); try { fsSync.copyFileSync(fixturePath, tgzPath); } catch {} } - callStdout = JSON.stringify([{ filename: 'xmemo-skill-1.1.45.tgz' }]); + callStdout = JSON.stringify([{ filename: 'xmemo-skill-1.1.48.tgz' }]); } else if (command === 'openclaw') { callStdout = 'Installed @xmemo/xmemo successfully.\n'; } else if (args.includes('install')) { @@ -80,20 +80,20 @@ function spawnStub(calls, { code = 0, stdout = '', stderr = '', error = null } = fsSync.mkdirSync(path.join(targetPath, 'scripts'), { recursive: true }); fsSync.writeFileSync( path.join(targetPath, 'SKILL.md'), - '# XMemo Skill\nVersion: 1.1.45\nXMemo memory assistant.\n' + '# XMemo Skill\nVersion: 1.1.48\nXMemo memory assistant.\n' ); fsSync.writeFileSync( path.join(targetPath, 'scripts', 'xmemo-skill.mjs'), - "const SKILL_VERSION = '1.1.45';\n" + "const SKILL_VERSION = '1.1.48';\n" ); fsSync.writeFileSync( path.join(targetPath, 'package.json'), - JSON.stringify({ name: '@xmemo/skill', version: '1.1.45' }) + JSON.stringify({ name: '@xmemo/skill', version: '1.1.48' }) ); } catch {} callStdout = JSON.stringify({ package: '@xmemo/skill', - skillVersion: '1.1.45', + skillVersion: '1.1.48', target: targetPath, installed: true, replaced: args.includes('--force') @@ -260,13 +260,13 @@ test('skill install --client: installs to correct agent skill folders for codex, assert.equal(report4.client, 'openclaw'); assert.equal(report4.installed, true); assert.equal(report4.skill, '@xmemo/xmemo'); - assert.equal(report4.version, '1.1.45'); + assert.equal(report4.version, '1.1.48'); assert.deepEqual(calls4[0].args, [ 'skills', 'install', '@xmemo/xmemo', '--version', - '1.1.45' + '1.1.48' ]); // Assert openclaw skill install never runs plugin commands assert.ok(!calls4.some((c) => c.args.includes('plugins'))); @@ -286,7 +286,7 @@ test('skill install --client: installs to correct agent skill folders for codex, 'install', '@xmemo/xmemo', '--version', - '1.1.45', + '1.1.48', '--global', '--force' ]); @@ -371,15 +371,15 @@ test('skill status: checks installed path and version for clients', async () => // Install codex skill const targetDir = path.join(tmpHome, '.codex', 'skills', 'xmemo-memory'); await fs.mkdir(path.join(targetDir, 'scripts'), { recursive: true }); - await fs.writeFile(path.join(targetDir, 'SKILL.md'), '# XMemo Skill v1.1.45\nxmemo'); - await fs.writeFile(path.join(targetDir, 'scripts', 'xmemo-skill.mjs'), "const SKILL_VERSION = '1.1.45';"); + await fs.writeFile(path.join(targetDir, 'SKILL.md'), '# XMemo Skill v1.1.48\nxmemo'); + await fs.writeFile(path.join(targetDir, 'scripts', 'xmemo-skill.mjs'), "const SKILL_VERSION = '1.1.48';"); // Status after install const res2 = await invoke(['skill', 'status', '--client', 'codex', '--json'], { env }); assert.equal(res2.code, 0); const parsed2 = JSON.parse(res2.stdout); assert.equal(parsed2.targets[0].installed, true); - assert.equal(parsed2.targets[0].version, '1.1.45'); + assert.equal(parsed2.targets[0].version, '1.1.48'); // Status --all const resAll = await invoke(['skill', 'status', '--all', '--json'], { env, cwd: tmpCwd }); @@ -394,7 +394,7 @@ test('skill status: checks installed path and version for clients', async () => assert.equal(resHuman.code, 0); assert.match(resHuman.stdout, /Client: Codex \(codex\) \[user\]/); assert.match(resHuman.stdout, /Status: installed/); - assert.match(resHuman.stdout, /Version: 1\.1\.45/); + assert.match(resHuman.stdout, /Version: 1\.1\.48/); } finally { await fs.rm(tmpHome, { recursive: true, force: true }); await fs.rm(tmpCwd, { recursive: true, force: true }); @@ -425,7 +425,7 @@ test('skill remove: removes installed skill, handles consent, and refuses foreig // 3. Make it a legitimate XMemo skill folder and create a pre-existing backup outside the skills dir await fs.mkdir(path.join(targetDir, 'scripts'), { recursive: true }); await fs.writeFile(path.join(targetDir, 'SKILL.md'), '# XMemo Skill\nxmemo'); - await fs.writeFile(path.join(targetDir, 'scripts', 'xmemo-skill.mjs'), "const SKILL_VERSION = '1.1.45';"); + await fs.writeFile(path.join(targetDir, 'scripts', 'xmemo-skill.mjs'), "const SKILL_VERSION = '1.1.48';"); await fs.mkdir(expectedBackupDir, { recursive: true }); await fs.writeFile(path.join(expectedBackupDir, 'backup-proof.txt'), 'preserved'); @@ -457,7 +457,7 @@ test('skill remove: removes installed skill, handles consent, and refuses foreig // Recreate skill and remove human mode await fs.mkdir(path.join(targetDir, 'scripts'), { recursive: true }); await fs.writeFile(path.join(targetDir, 'SKILL.md'), '# XMemo Skill\nxmemo'); - await fs.writeFile(path.join(targetDir, 'scripts', 'xmemo-skill.mjs'), "const SKILL_VERSION = '1.1.45';"); + await fs.writeFile(path.join(targetDir, 'scripts', 'xmemo-skill.mjs'), "const SKILL_VERSION = '1.1.48';"); const resHumanRemove = await invoke(['skill', 'remove', '--client', 'codex', '--yes'], { env }); assert.equal(resHumanRemove.code, 0); @@ -543,7 +543,7 @@ test('skill remove --client openclaw: handles absent skill, absent clawhub, cons child.stderr = new EventEmitter(); queueMicrotask(() => { if (command === 'openclaw' && args[0] === 'skills' && args[1] === 'list') { - child.stdout.emit('data', '@xmemo/xmemo@1.1.45\n'); + child.stdout.emit('data', '@xmemo/xmemo@1.1.48\n'); child.emit('close', 0); } else if (command === 'clawhub') { const err = new Error('spawn clawhub ENOENT'); @@ -583,7 +583,7 @@ test('skill remove --client openclaw: handles absent skill, absent clawhub, cons child.stderr = new EventEmitter(); queueMicrotask(() => { if (command === 'openclaw' && args[0] === 'skills' && args[1] === 'list') { - child.stdout.emit('data', '@xmemo/xmemo@1.1.45\n'); + child.stdout.emit('data', '@xmemo/xmemo@1.1.48\n'); child.emit('close', 0); } else if (command === 'clawhub' && args[0] === '--version') { child.stdout.emit('data', 'clawhub/0.4.0\n'); diff --git a/test/xmemo-skill-update-check.test.js b/test/xmemo-skill-update-check.test.js index 0a01c7a..fdb80ba 100644 --- a/test/xmemo-skill-update-check.test.js +++ b/test/xmemo-skill-update-check.test.js @@ -27,7 +27,7 @@ const skillScript = path.join(repoRoot, 'skills', 'xmemo', 'scripts', 'xmemo-ski const entrypointContent = await readFile(skillScript, 'utf8'); const versionMatch = entrypointContent.match(/const\s+SKILL_VERSION\s*=\s*['"]([^'"]+)['"]/); -const CURRENT_SKILL_VERSION = versionMatch ? versionMatch[1] : '1.1.45'; +const CURRENT_SKILL_VERSION = versionMatch ? versionMatch[1] : '1.1.48'; const currentSemver = parseSemver(CURRENT_SKILL_VERSION); const NEWER_SKILL_VERSION = `${currentSemver.major}.${currentSemver.minor}.${currentSemver.patch + 1}`; const NEWER_TAG = `skill-v${NEWER_SKILL_VERSION}`; @@ -211,8 +211,8 @@ test('readUpdateCheckCache and writeUpdateCheckCache handle file I/O safely', as const testData = { lastCheck: '2026-10-19T10:00:00.000Z', - latestVersion: '1.1.45', - notifiedVersion: '1.1.45', + latestVersion: '1.1.48', + notifiedVersion: '1.1.48', notifiedAt: '2026-10-19T10:00:00.000Z', }; await writeUpdateCheckCache(cachePath, testData); diff --git a/test/xmemo-skill-upgrade-command.test.js b/test/xmemo-skill-upgrade-command.test.js index ac7933c..3f6bef1 100644 --- a/test/xmemo-skill-upgrade-command.test.js +++ b/test/xmemo-skill-upgrade-command.test.js @@ -18,7 +18,7 @@ const skillScript = path.join(repoRoot, 'skills', 'xmemo', 'scripts', 'xmemo-ski const entrypointContent = await fs.readFile(skillScript, 'utf8'); const versionMatch = entrypointContent.match(/const\s+SKILL_VERSION\s*=\s*['"]([^'"]+)['"]/); -const CURRENT_SKILL_VERSION = versionMatch ? versionMatch[1] : '1.1.45'; +const CURRENT_SKILL_VERSION = versionMatch ? versionMatch[1] : '1.1.48'; const semverParts = CURRENT_SKILL_VERSION.split('.').map(Number); const NEWER_SKILL_VERSION = `${semverParts[0]}.${semverParts[1]}.${semverParts[2] + 1}`; const NEWER_TAG = `skill-v${NEWER_SKILL_VERSION}`;