cursor-guard 4.8.2 → 4.8.4
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.
- package/ROADMAP.md +18 -2
- package/package.json +1 -1
- package/references/dashboard/public/app.js +17 -7
- package/references/dashboard/public/style.css +9 -0
- package/references/lib/core/doctor.js +33 -13
- package/references/vscode-extension/dist/dashboard/public/app.js +17 -7
- package/references/vscode-extension/dist/dashboard/public/style.css +9 -0
- package/references/vscode-extension/dist/guard-version.json +1 -1
- package/references/vscode-extension/dist/lib/auto-setup.js +32 -15
- package/references/vscode-extension/dist/lib/core/doctor.js +33 -13
- package/references/vscode-extension/dist/mcp/server.js +29 -10
- package/references/vscode-extension/dist/package.json +1 -1
- package/references/vscode-extension/dist/skill/ROADMAP.md +18 -2
- package/references/vscode-extension/lib/auto-setup.js +32 -15
- package/references/vscode-extension/package.json +1 -1
- package/references/vscode-extension/dist/cursor-guard-ide-4.8.2.vsix +0 -0
package/ROADMAP.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
> 本文档描述 cursor-guard 从 V2 到 V7 的长期演进方向。
|
|
4
4
|
> 每一代向下兼容,低版本功能永远不废弃。
|
|
5
5
|
>
|
|
6
|
-
> **当前版本**:`V4.8.
|
|
7
|
-
> **文档状态**:`V2` ~ `V4.8.
|
|
6
|
+
> **当前版本**:`V4.8.4`
|
|
7
|
+
> **文档状态**:`V2` ~ `V4.8.4` 已完成交付(含 V5 intent/audit 基础),`V5` 主体规划中
|
|
8
8
|
|
|
9
9
|
## 阅读导航
|
|
10
10
|
|
|
@@ -734,6 +734,22 @@ V4 经过 4 轮系统性代码审查,修复了以下关键问题:
|
|
|
734
734
|
}
|
|
735
735
|
```
|
|
736
736
|
|
|
737
|
+
### V4.8.4:已删除文件恢复命令自动指向父提交 ✅
|
|
738
|
+
|
|
739
|
+
| 修复 | 说明 |
|
|
740
|
+
|------|------|
|
|
741
|
+
| **删除文件恢复命令修正** | 当备份记录中文件 action 为 `D`(删除)时,恢复命令的 `source` 自动改为 `commitHash~1`(父提交,即删除前的版本)。之前指向删除后的 commit,导致恢复报错"文件不存在" |
|
|
742
|
+
| **按钮文案区分** | 删除文件的恢复按钮显示"恢复删除前"(橙色边框),非删除文件显示"复制命令"(默认样式)。同时作用于 Modal 文件详情和 Drawer 文件表格 |
|
|
743
|
+
| **i18n** | 新增 `modal.restorePreDelete` key(EN: "Restore pre-delete" / CN: "恢复删除前") |
|
|
744
|
+
|
|
745
|
+
### V4.8.3:Doctor MCP 检测修复 + Skill 目录 Junction 补全 ✅
|
|
746
|
+
|
|
747
|
+
| 修复 | 说明 |
|
|
748
|
+
|------|------|
|
|
749
|
+
| **Doctor MCP 检查误报修复** | 之前只检查 `server.js` 文件存在性和 SDK `require.resolve`(esbuild bundle 后必然失败)。新增 `.cursor/mcp.json` 和 `.windsurf/mcp.json` 配置扫描(项目级 + 全局级),如果 `cursor-guard` 已注册则直接 PASS,显示 `registered in .cursor/mcp.json (bundled mode)` |
|
|
750
|
+
| **Skill 目录 junction 补全** | v4.8.2 的 `autoInstallSkill` 有 `if (SKILL.md exists) return` 早退逻辑,导致已安装的旧目录永远不会创建 junction。重构为独立检查:即使 SKILL.md 已存在,仍检测 `references/` 是否为 junction、是否缺少 `mcp/` 运行时目录,不满足则删除旧纯文档目录并重建 junction |
|
|
751
|
+
| **package.json 安装增强** | 优先从根 `package.json` 复制,fallback 到 `guard-version.json` |
|
|
752
|
+
|
|
737
753
|
### V4.8.2:Skill 目录运行时完整安装 ✅
|
|
738
754
|
|
|
739
755
|
| 修复 | 说明 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-guard",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.4",
|
|
4
4
|
"description": "Protects code from accidental AI overwrite or deletion in Cursor IDE — mandatory pre-write snapshots, review-before-apply, local Git safety net, and deterministic recovery. | 保护代码免受 Cursor AI 代理意外覆写或删除——强制写前快照、预览再执行、本地 Git 安全网、确定性恢复。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cursor",
|
|
@@ -65,6 +65,7 @@ const I18N = {
|
|
|
65
65
|
'modal.alertFiles': 'Alert File Details',
|
|
66
66
|
'modal.col.restore': 'Restore',
|
|
67
67
|
'modal.copyRestore': 'Copy cmd',
|
|
68
|
+
'modal.restorePreDelete':'Restore pre-delete',
|
|
68
69
|
'modal.copied': 'Copied!',
|
|
69
70
|
|
|
70
71
|
'backups.gitCommits': 'Git Commits',
|
|
@@ -292,6 +293,7 @@ const I18N = {
|
|
|
292
293
|
'modal.alertFiles': '告警文件详情',
|
|
293
294
|
'modal.col.restore': '恢复',
|
|
294
295
|
'modal.copyRestore': '复制命令',
|
|
296
|
+
'modal.restorePreDelete':'恢复删除前',
|
|
295
297
|
'modal.copied': '已复制!',
|
|
296
298
|
|
|
297
299
|
'backups.gitCommits': 'Git 提交数',
|
|
@@ -1306,14 +1308,17 @@ function openFileModal(title, files, projectPath, commitHash) {
|
|
|
1306
1308
|
else sorted.sort((a, b) => (b.added + b.deleted) - (a.added + a.deleted));
|
|
1307
1309
|
|
|
1308
1310
|
const rows = sorted.map(f => {
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
+
const isDeleted = f.action === 'D';
|
|
1312
|
+
const source = isDeleted && commitHash ? `${commitHash}~1` : commitHash;
|
|
1313
|
+
const restoreCmd = source
|
|
1314
|
+
? `restore_file({ path: "${projectPath || ''}", file: "${f.path}", source: "${source}" })`
|
|
1311
1315
|
: '';
|
|
1316
|
+
const btnLabel = isDeleted ? t('modal.restorePreDelete') : t('modal.copyRestore');
|
|
1312
1317
|
return `<tr>
|
|
1313
1318
|
<td class="text-mono modal-file-path" title="${esc(f.path)}">${esc(f.path)}</td>
|
|
1314
1319
|
<td>${formatFileActionBadge(f.action)}</td>
|
|
1315
1320
|
<td class="text-mono modal-file-changes">+${f.added || 0} -${f.deleted || 0}</td>
|
|
1316
|
-
${commitHash ? `<td><button class="modal-restore-btn" data-restore-cmd="${esc(restoreCmd)}">${
|
|
1321
|
+
${commitHash ? `<td><button class="modal-restore-btn${isDeleted ? ' btn-deleted' : ''}" data-restore-cmd="${esc(restoreCmd)}">${btnLabel}</button></td>` : ''}
|
|
1317
1322
|
</tr>`;
|
|
1318
1323
|
}).join('');
|
|
1319
1324
|
|
|
@@ -1339,9 +1344,10 @@ function openFileModal(title, files, projectPath, commitHash) {
|
|
|
1339
1344
|
const btn = e.target.closest('[data-restore-cmd]');
|
|
1340
1345
|
if (btn) {
|
|
1341
1346
|
copyText(btn.dataset.restoreCmd);
|
|
1347
|
+
const origLabel = btn.classList.contains('btn-deleted') ? t('modal.restorePreDelete') : t('modal.copyRestore');
|
|
1342
1348
|
btn.textContent = t('modal.copied');
|
|
1343
1349
|
btn.classList.add('copied');
|
|
1344
|
-
setTimeout(() => { btn.textContent =
|
|
1350
|
+
setTimeout(() => { btn.textContent = origLabel; btn.classList.remove('copied'); }, 1500);
|
|
1345
1351
|
}
|
|
1346
1352
|
});
|
|
1347
1353
|
|
|
@@ -1378,12 +1384,15 @@ function renderDrawerFilesTable(files, sortKey, commitHash, projectPath) {
|
|
|
1378
1384
|
else sorted.sort((a, b) => (b.added + b.deleted) - (a.added + a.deleted));
|
|
1379
1385
|
const hasRestore = !!commitHash;
|
|
1380
1386
|
const rows = sorted.map(f => {
|
|
1381
|
-
const
|
|
1387
|
+
const isDeleted = f.action === 'D';
|
|
1388
|
+
const source = isDeleted && commitHash ? `${commitHash}~1` : commitHash;
|
|
1389
|
+
const cmd = hasRestore ? `restore_file({ path: "${projectPath || ''}", file: "${f.path}", source: "${source}" })` : '';
|
|
1390
|
+
const btnLabel = isDeleted ? t('modal.restorePreDelete') : t('modal.copyRestore');
|
|
1382
1391
|
return `<tr>
|
|
1383
1392
|
<td class="text-mono drawer-file-path">${esc(f.path)}</td>
|
|
1384
1393
|
<td>${formatFileActionBadge(f.action)}</td>
|
|
1385
1394
|
<td class="text-mono drawer-file-changes">+${f.added} -${f.deleted}</td>
|
|
1386
|
-
${hasRestore ? `<td><button class="modal-restore-btn" data-restore-cmd="${esc(cmd)}">${
|
|
1395
|
+
${hasRestore ? `<td><button class="modal-restore-btn${isDeleted ? ' btn-deleted' : ''}" data-restore-cmd="${esc(cmd)}">${btnLabel}</button></td>` : ''}
|
|
1387
1396
|
</tr>`;
|
|
1388
1397
|
}).join('');
|
|
1389
1398
|
return `<table class="drawer-files-table">
|
|
@@ -1490,9 +1499,10 @@ function openRestoreDrawer(backup) {
|
|
|
1490
1499
|
const restoreBtn = e.target.closest('[data-restore-cmd]');
|
|
1491
1500
|
if (restoreBtn) {
|
|
1492
1501
|
copyText(restoreBtn.dataset.restoreCmd);
|
|
1502
|
+
const origLabel = restoreBtn.classList.contains('btn-deleted') ? t('modal.restorePreDelete') : t('modal.copyRestore');
|
|
1493
1503
|
restoreBtn.textContent = t('modal.copied');
|
|
1494
1504
|
restoreBtn.classList.add('copied');
|
|
1495
|
-
setTimeout(() => { restoreBtn.textContent =
|
|
1505
|
+
setTimeout(() => { restoreBtn.textContent = origLabel; restoreBtn.classList.remove('copied'); }, 1500);
|
|
1496
1506
|
}
|
|
1497
1507
|
});
|
|
1498
1508
|
};
|
|
@@ -1443,6 +1443,15 @@ main {
|
|
|
1443
1443
|
color: #fff;
|
|
1444
1444
|
border-color: var(--green);
|
|
1445
1445
|
}
|
|
1446
|
+
.modal-restore-btn.btn-deleted {
|
|
1447
|
+
border-color: var(--orange, #f0ad4e);
|
|
1448
|
+
color: var(--orange, #f0ad4e);
|
|
1449
|
+
}
|
|
1450
|
+
.modal-restore-btn.btn-deleted:hover {
|
|
1451
|
+
background: var(--orange, #f0ad4e);
|
|
1452
|
+
color: #fff;
|
|
1453
|
+
border-color: var(--orange, #f0ad4e);
|
|
1454
|
+
}
|
|
1446
1455
|
|
|
1447
1456
|
/* ── Responsive ───────────────────────────────────────────── */
|
|
1448
1457
|
|
|
@@ -253,49 +253,69 @@ function runDiagnostics(projectDir) {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
// 14. MCP server status
|
|
256
|
+
// Strategy: check multiple sources — mcp.json config, local server.js, SDK availability
|
|
256
257
|
const mcpServerPath = path.resolve(__dirname, '../../mcp/server.js');
|
|
257
258
|
const mcpServerExists = fs.existsSync(mcpServerPath);
|
|
258
259
|
|
|
260
|
+
// Check if cursor-guard is registered in any mcp.json (project or global)
|
|
261
|
+
let mcpConfigured = false;
|
|
262
|
+
let mcpConfigSource = '';
|
|
263
|
+
const home = process.env.USERPROFILE || process.env.HOME || '';
|
|
264
|
+
const mcpJsonCandidates = [
|
|
265
|
+
projectDir ? path.join(projectDir, '.cursor', 'mcp.json') : null,
|
|
266
|
+
projectDir ? path.join(projectDir, '.windsurf', 'mcp.json') : null,
|
|
267
|
+
path.join(home, '.cursor', 'mcp.json'),
|
|
268
|
+
path.join(home, '.windsurf', 'mcp.json'),
|
|
269
|
+
].filter(Boolean);
|
|
270
|
+
for (const mcpJsonPath of mcpJsonCandidates) {
|
|
271
|
+
try {
|
|
272
|
+
if (!fs.existsSync(mcpJsonPath)) continue;
|
|
273
|
+
const mcpJson = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8'));
|
|
274
|
+
if (mcpJson.mcpServers?.['cursor-guard']) {
|
|
275
|
+
mcpConfigured = true;
|
|
276
|
+
mcpConfigSource = path.relative(projectDir || home, mcpJsonPath);
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
} catch { /* ignore */ }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Check SDK availability (for non-bundled installations)
|
|
259
283
|
let mcpSdkAvailable = false;
|
|
260
284
|
let mcpSdkVersion = null;
|
|
261
285
|
const skillRoot = path.resolve(__dirname, '../../..');
|
|
262
|
-
// Search multiple candidate locations for SDK package.json
|
|
263
286
|
const sdkCandidates = [
|
|
264
287
|
path.join(skillRoot, 'node_modules', '@modelcontextprotocol', 'sdk', 'package.json'),
|
|
265
288
|
];
|
|
266
289
|
for (const candidate of sdkCandidates) {
|
|
267
290
|
try {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const mcpPkg = JSON.parse(fs.readFileSync(resolved, 'utf-8'));
|
|
291
|
+
if (fs.existsSync(candidate)) {
|
|
292
|
+
mcpSdkVersion = JSON.parse(fs.readFileSync(candidate, 'utf-8')).version;
|
|
271
293
|
mcpSdkAvailable = true;
|
|
272
|
-
mcpSdkVersion = mcpPkg.version;
|
|
273
294
|
break;
|
|
274
295
|
}
|
|
275
296
|
} catch { /* ignore */ }
|
|
276
297
|
}
|
|
277
298
|
if (!mcpSdkAvailable) {
|
|
278
|
-
// Fallback: try require.resolve from Node's module paths.
|
|
279
|
-
// Some SDK versions restrict subpath access via exports, so try
|
|
280
|
-
// the main entry first and derive the package.json from it.
|
|
281
299
|
try {
|
|
282
300
|
const mainPath = require.resolve('@modelcontextprotocol/sdk');
|
|
283
301
|
const sdkDir = mainPath.replace(/[/\\]dist[/\\].*$/, '').replace(/[/\\]src[/\\].*$/, '');
|
|
284
302
|
const pkgPath = path.join(sdkDir, 'package.json');
|
|
285
303
|
if (fs.existsSync(pkgPath)) {
|
|
286
|
-
|
|
304
|
+
mcpSdkVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version;
|
|
287
305
|
mcpSdkAvailable = true;
|
|
288
|
-
mcpSdkVersion = mcpPkg.version;
|
|
289
306
|
}
|
|
290
307
|
} catch { /* not installed */ }
|
|
291
308
|
}
|
|
292
309
|
|
|
293
|
-
if (
|
|
310
|
+
if (mcpConfigured) {
|
|
311
|
+
const detail = mcpSdkAvailable
|
|
312
|
+
? `registered in ${mcpConfigSource}, SDK ${mcpSdkVersion}`
|
|
313
|
+
: `registered in ${mcpConfigSource} (bundled mode)`;
|
|
314
|
+
check('MCP server', 'PASS', detail);
|
|
315
|
+
} else if (mcpServerExists && mcpSdkAvailable) {
|
|
294
316
|
check('MCP server', 'PASS', `server.js found, SDK ${mcpSdkVersion}`);
|
|
295
317
|
} else if (mcpServerExists && !mcpSdkAvailable) {
|
|
296
318
|
check('MCP server', 'WARN', 'server.js found but @modelcontextprotocol/sdk not installed — run: cd <skill-dir> && npm install');
|
|
297
|
-
} else if (!mcpServerExists && mcpSdkAvailable) {
|
|
298
|
-
check('MCP server', 'WARN', `SDK installed (${mcpSdkVersion}) but server.js not found at expected path`);
|
|
299
319
|
} else {
|
|
300
320
|
check('MCP server', 'WARN', 'MCP not configured (optional — cursor-guard works without it)');
|
|
301
321
|
}
|
|
@@ -65,6 +65,7 @@ const I18N = {
|
|
|
65
65
|
'modal.alertFiles': 'Alert File Details',
|
|
66
66
|
'modal.col.restore': 'Restore',
|
|
67
67
|
'modal.copyRestore': 'Copy cmd',
|
|
68
|
+
'modal.restorePreDelete':'Restore pre-delete',
|
|
68
69
|
'modal.copied': 'Copied!',
|
|
69
70
|
|
|
70
71
|
'backups.gitCommits': 'Git Commits',
|
|
@@ -292,6 +293,7 @@ const I18N = {
|
|
|
292
293
|
'modal.alertFiles': '告警文件详情',
|
|
293
294
|
'modal.col.restore': '恢复',
|
|
294
295
|
'modal.copyRestore': '复制命令',
|
|
296
|
+
'modal.restorePreDelete':'恢复删除前',
|
|
295
297
|
'modal.copied': '已复制!',
|
|
296
298
|
|
|
297
299
|
'backups.gitCommits': 'Git 提交数',
|
|
@@ -1306,14 +1308,17 @@ function openFileModal(title, files, projectPath, commitHash) {
|
|
|
1306
1308
|
else sorted.sort((a, b) => (b.added + b.deleted) - (a.added + a.deleted));
|
|
1307
1309
|
|
|
1308
1310
|
const rows = sorted.map(f => {
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
+
const isDeleted = f.action === 'D';
|
|
1312
|
+
const source = isDeleted && commitHash ? `${commitHash}~1` : commitHash;
|
|
1313
|
+
const restoreCmd = source
|
|
1314
|
+
? `restore_file({ path: "${projectPath || ''}", file: "${f.path}", source: "${source}" })`
|
|
1311
1315
|
: '';
|
|
1316
|
+
const btnLabel = isDeleted ? t('modal.restorePreDelete') : t('modal.copyRestore');
|
|
1312
1317
|
return `<tr>
|
|
1313
1318
|
<td class="text-mono modal-file-path" title="${esc(f.path)}">${esc(f.path)}</td>
|
|
1314
1319
|
<td>${formatFileActionBadge(f.action)}</td>
|
|
1315
1320
|
<td class="text-mono modal-file-changes">+${f.added || 0} -${f.deleted || 0}</td>
|
|
1316
|
-
${commitHash ? `<td><button class="modal-restore-btn" data-restore-cmd="${esc(restoreCmd)}">${
|
|
1321
|
+
${commitHash ? `<td><button class="modal-restore-btn${isDeleted ? ' btn-deleted' : ''}" data-restore-cmd="${esc(restoreCmd)}">${btnLabel}</button></td>` : ''}
|
|
1317
1322
|
</tr>`;
|
|
1318
1323
|
}).join('');
|
|
1319
1324
|
|
|
@@ -1339,9 +1344,10 @@ function openFileModal(title, files, projectPath, commitHash) {
|
|
|
1339
1344
|
const btn = e.target.closest('[data-restore-cmd]');
|
|
1340
1345
|
if (btn) {
|
|
1341
1346
|
copyText(btn.dataset.restoreCmd);
|
|
1347
|
+
const origLabel = btn.classList.contains('btn-deleted') ? t('modal.restorePreDelete') : t('modal.copyRestore');
|
|
1342
1348
|
btn.textContent = t('modal.copied');
|
|
1343
1349
|
btn.classList.add('copied');
|
|
1344
|
-
setTimeout(() => { btn.textContent =
|
|
1350
|
+
setTimeout(() => { btn.textContent = origLabel; btn.classList.remove('copied'); }, 1500);
|
|
1345
1351
|
}
|
|
1346
1352
|
});
|
|
1347
1353
|
|
|
@@ -1378,12 +1384,15 @@ function renderDrawerFilesTable(files, sortKey, commitHash, projectPath) {
|
|
|
1378
1384
|
else sorted.sort((a, b) => (b.added + b.deleted) - (a.added + a.deleted));
|
|
1379
1385
|
const hasRestore = !!commitHash;
|
|
1380
1386
|
const rows = sorted.map(f => {
|
|
1381
|
-
const
|
|
1387
|
+
const isDeleted = f.action === 'D';
|
|
1388
|
+
const source = isDeleted && commitHash ? `${commitHash}~1` : commitHash;
|
|
1389
|
+
const cmd = hasRestore ? `restore_file({ path: "${projectPath || ''}", file: "${f.path}", source: "${source}" })` : '';
|
|
1390
|
+
const btnLabel = isDeleted ? t('modal.restorePreDelete') : t('modal.copyRestore');
|
|
1382
1391
|
return `<tr>
|
|
1383
1392
|
<td class="text-mono drawer-file-path">${esc(f.path)}</td>
|
|
1384
1393
|
<td>${formatFileActionBadge(f.action)}</td>
|
|
1385
1394
|
<td class="text-mono drawer-file-changes">+${f.added} -${f.deleted}</td>
|
|
1386
|
-
${hasRestore ? `<td><button class="modal-restore-btn" data-restore-cmd="${esc(cmd)}">${
|
|
1395
|
+
${hasRestore ? `<td><button class="modal-restore-btn${isDeleted ? ' btn-deleted' : ''}" data-restore-cmd="${esc(cmd)}">${btnLabel}</button></td>` : ''}
|
|
1387
1396
|
</tr>`;
|
|
1388
1397
|
}).join('');
|
|
1389
1398
|
return `<table class="drawer-files-table">
|
|
@@ -1490,9 +1499,10 @@ function openRestoreDrawer(backup) {
|
|
|
1490
1499
|
const restoreBtn = e.target.closest('[data-restore-cmd]');
|
|
1491
1500
|
if (restoreBtn) {
|
|
1492
1501
|
copyText(restoreBtn.dataset.restoreCmd);
|
|
1502
|
+
const origLabel = restoreBtn.classList.contains('btn-deleted') ? t('modal.restorePreDelete') : t('modal.copyRestore');
|
|
1493
1503
|
restoreBtn.textContent = t('modal.copied');
|
|
1494
1504
|
restoreBtn.classList.add('copied');
|
|
1495
|
-
setTimeout(() => { restoreBtn.textContent =
|
|
1505
|
+
setTimeout(() => { restoreBtn.textContent = origLabel; restoreBtn.classList.remove('copied'); }, 1500);
|
|
1496
1506
|
}
|
|
1497
1507
|
});
|
|
1498
1508
|
};
|
|
@@ -1443,6 +1443,15 @@ main {
|
|
|
1443
1443
|
color: #fff;
|
|
1444
1444
|
border-color: var(--green);
|
|
1445
1445
|
}
|
|
1446
|
+
.modal-restore-btn.btn-deleted {
|
|
1447
|
+
border-color: var(--orange, #f0ad4e);
|
|
1448
|
+
color: var(--orange, #f0ad4e);
|
|
1449
|
+
}
|
|
1450
|
+
.modal-restore-btn.btn-deleted:hover {
|
|
1451
|
+
background: var(--orange, #f0ad4e);
|
|
1452
|
+
color: #fff;
|
|
1453
|
+
border-color: var(--orange, #f0ad4e);
|
|
1454
|
+
}
|
|
1446
1455
|
|
|
1447
1456
|
/* ── Responsive ───────────────────────────────────────────── */
|
|
1448
1457
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"4.8.
|
|
1
|
+
{"version":"4.8.4"}
|
|
@@ -67,47 +67,64 @@ function autoInstallSkill(extRoot, homePath, dirName) {
|
|
|
67
67
|
if (!skillSrc) return actions;
|
|
68
68
|
|
|
69
69
|
const skillTarget = path.join(homePath, 'skills', 'cursor-guard');
|
|
70
|
-
const skillMdTarget = path.join(skillTarget, 'SKILL.md');
|
|
71
|
-
|
|
72
|
-
if (fs.existsSync(skillMdTarget)) return actions;
|
|
73
|
-
|
|
74
70
|
fs.mkdirSync(skillTarget, { recursive: true });
|
|
75
71
|
|
|
72
|
+
// ── Install/update SKILL.md and ROADMAP.md ──
|
|
76
73
|
const skillMdSrc = path.join(skillSrc, 'SKILL.md');
|
|
77
|
-
|
|
74
|
+
const skillMdTarget = path.join(skillTarget, 'SKILL.md');
|
|
75
|
+
if (fs.existsSync(skillMdSrc) && !fs.existsSync(skillMdTarget)) {
|
|
78
76
|
fs.copyFileSync(skillMdSrc, skillMdTarget);
|
|
79
77
|
actions.push('SKILL.md installed');
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
const roadmapSrc = path.join(skillSrc, 'ROADMAP.md');
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
const roadmapDst = path.join(skillTarget, 'ROADMAP.md');
|
|
82
|
+
if (fs.existsSync(roadmapSrc) && !fs.existsSync(roadmapDst)) {
|
|
83
|
+
fs.copyFileSync(roadmapSrc, roadmapDst);
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
//
|
|
88
|
-
// (mcp/server.js, lib/core/*, dashboard/, bin/ etc.)
|
|
86
|
+
// ── Ensure references/ junction exists (runs even for existing installations) ──
|
|
89
87
|
const refsTarget = path.join(skillTarget, 'references');
|
|
90
|
-
|
|
88
|
+
const refsIsJunction = _isSymlinkOrJunction(refsTarget);
|
|
89
|
+
const refsIsPlainDir = !refsIsJunction && fs.existsSync(refsTarget);
|
|
90
|
+
const refsMissingRuntime = refsIsPlainDir && !fs.existsSync(path.join(refsTarget, 'mcp'));
|
|
91
|
+
|
|
92
|
+
if (!fs.existsSync(refsTarget) || refsMissingRuntime) {
|
|
93
|
+
// Remove old plain directory if it only has docs (no runtime)
|
|
94
|
+
if (refsMissingRuntime) {
|
|
95
|
+
try { fs.rmSync(refsTarget, { recursive: true, force: true }); } catch { /* ok */ }
|
|
96
|
+
}
|
|
91
97
|
try {
|
|
92
98
|
fs.symlinkSync(extRoot, refsTarget, 'junction');
|
|
93
99
|
actions.push('references/ linked');
|
|
94
100
|
} catch {
|
|
95
|
-
// junction failed (rare) — fall back to copying essential docs only
|
|
96
101
|
fs.mkdirSync(refsTarget, { recursive: true });
|
|
97
102
|
_copyDocFiles(skillSrc, refsTarget);
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
//
|
|
102
|
-
const pkgSrc = path.join(extRoot, '..', '..', 'package.json');
|
|
106
|
+
// ── Ensure package.json exists ──
|
|
103
107
|
const pkgDst = path.join(skillTarget, 'package.json');
|
|
104
|
-
if (
|
|
105
|
-
|
|
108
|
+
if (!fs.existsSync(pkgDst)) {
|
|
109
|
+
const pkgSrc = path.join(extRoot, '..', '..', 'package.json');
|
|
110
|
+
const guardVer = path.join(extRoot, 'guard-version.json');
|
|
111
|
+
if (fs.existsSync(pkgSrc)) {
|
|
112
|
+
fs.copyFileSync(pkgSrc, pkgDst);
|
|
113
|
+
} else if (fs.existsSync(guardVer)) {
|
|
114
|
+
fs.copyFileSync(guardVer, pkgDst);
|
|
115
|
+
}
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
return actions;
|
|
109
119
|
}
|
|
110
120
|
|
|
121
|
+
function _isSymlinkOrJunction(p) {
|
|
122
|
+
try {
|
|
123
|
+
const stat = fs.lstatSync(p);
|
|
124
|
+
return stat.isSymbolicLink();
|
|
125
|
+
} catch { return false; }
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
function _copyDocFiles(skillSrc, refsTarget) {
|
|
112
129
|
const docs = [
|
|
113
130
|
'config-reference.md', 'config-reference.zh-CN.md',
|
|
@@ -253,49 +253,69 @@ function runDiagnostics(projectDir) {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
// 14. MCP server status
|
|
256
|
+
// Strategy: check multiple sources — mcp.json config, local server.js, SDK availability
|
|
256
257
|
const mcpServerPath = path.resolve(__dirname, '../../mcp/server.js');
|
|
257
258
|
const mcpServerExists = fs.existsSync(mcpServerPath);
|
|
258
259
|
|
|
260
|
+
// Check if cursor-guard is registered in any mcp.json (project or global)
|
|
261
|
+
let mcpConfigured = false;
|
|
262
|
+
let mcpConfigSource = '';
|
|
263
|
+
const home = process.env.USERPROFILE || process.env.HOME || '';
|
|
264
|
+
const mcpJsonCandidates = [
|
|
265
|
+
projectDir ? path.join(projectDir, '.cursor', 'mcp.json') : null,
|
|
266
|
+
projectDir ? path.join(projectDir, '.windsurf', 'mcp.json') : null,
|
|
267
|
+
path.join(home, '.cursor', 'mcp.json'),
|
|
268
|
+
path.join(home, '.windsurf', 'mcp.json'),
|
|
269
|
+
].filter(Boolean);
|
|
270
|
+
for (const mcpJsonPath of mcpJsonCandidates) {
|
|
271
|
+
try {
|
|
272
|
+
if (!fs.existsSync(mcpJsonPath)) continue;
|
|
273
|
+
const mcpJson = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8'));
|
|
274
|
+
if (mcpJson.mcpServers?.['cursor-guard']) {
|
|
275
|
+
mcpConfigured = true;
|
|
276
|
+
mcpConfigSource = path.relative(projectDir || home, mcpJsonPath);
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
} catch { /* ignore */ }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Check SDK availability (for non-bundled installations)
|
|
259
283
|
let mcpSdkAvailable = false;
|
|
260
284
|
let mcpSdkVersion = null;
|
|
261
285
|
const skillRoot = path.resolve(__dirname, '../../..');
|
|
262
|
-
// Search multiple candidate locations for SDK package.json
|
|
263
286
|
const sdkCandidates = [
|
|
264
287
|
path.join(skillRoot, 'node_modules', '@modelcontextprotocol', 'sdk', 'package.json'),
|
|
265
288
|
];
|
|
266
289
|
for (const candidate of sdkCandidates) {
|
|
267
290
|
try {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const mcpPkg = JSON.parse(fs.readFileSync(resolved, 'utf-8'));
|
|
291
|
+
if (fs.existsSync(candidate)) {
|
|
292
|
+
mcpSdkVersion = JSON.parse(fs.readFileSync(candidate, 'utf-8')).version;
|
|
271
293
|
mcpSdkAvailable = true;
|
|
272
|
-
mcpSdkVersion = mcpPkg.version;
|
|
273
294
|
break;
|
|
274
295
|
}
|
|
275
296
|
} catch { /* ignore */ }
|
|
276
297
|
}
|
|
277
298
|
if (!mcpSdkAvailable) {
|
|
278
|
-
// Fallback: try require.resolve from Node's module paths.
|
|
279
|
-
// Some SDK versions restrict subpath access via exports, so try
|
|
280
|
-
// the main entry first and derive the package.json from it.
|
|
281
299
|
try {
|
|
282
300
|
const mainPath = require.resolve('@modelcontextprotocol/sdk');
|
|
283
301
|
const sdkDir = mainPath.replace(/[/\\]dist[/\\].*$/, '').replace(/[/\\]src[/\\].*$/, '');
|
|
284
302
|
const pkgPath = path.join(sdkDir, 'package.json');
|
|
285
303
|
if (fs.existsSync(pkgPath)) {
|
|
286
|
-
|
|
304
|
+
mcpSdkVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version;
|
|
287
305
|
mcpSdkAvailable = true;
|
|
288
|
-
mcpSdkVersion = mcpPkg.version;
|
|
289
306
|
}
|
|
290
307
|
} catch { /* not installed */ }
|
|
291
308
|
}
|
|
292
309
|
|
|
293
|
-
if (
|
|
310
|
+
if (mcpConfigured) {
|
|
311
|
+
const detail = mcpSdkAvailable
|
|
312
|
+
? `registered in ${mcpConfigSource}, SDK ${mcpSdkVersion}`
|
|
313
|
+
: `registered in ${mcpConfigSource} (bundled mode)`;
|
|
314
|
+
check('MCP server', 'PASS', detail);
|
|
315
|
+
} else if (mcpServerExists && mcpSdkAvailable) {
|
|
294
316
|
check('MCP server', 'PASS', `server.js found, SDK ${mcpSdkVersion}`);
|
|
295
317
|
} else if (mcpServerExists && !mcpSdkAvailable) {
|
|
296
318
|
check('MCP server', 'WARN', 'server.js found but @modelcontextprotocol/sdk not installed — run: cd <skill-dir> && npm install');
|
|
297
|
-
} else if (!mcpServerExists && mcpSdkAvailable) {
|
|
298
|
-
check('MCP server', 'WARN', `SDK installed (${mcpSdkVersion}) but server.js not found at expected path`);
|
|
299
319
|
} else {
|
|
300
320
|
check('MCP server', 'WARN', 'MCP not configured (optional — cursor-guard works without it)');
|
|
301
321
|
}
|
|
@@ -35568,7 +35568,7 @@ var require_package = __commonJS({
|
|
|
35568
35568
|
"package.json"(exports2, module2) {
|
|
35569
35569
|
module2.exports = {
|
|
35570
35570
|
name: "cursor-guard",
|
|
35571
|
-
version: "4.8.
|
|
35571
|
+
version: "4.8.4",
|
|
35572
35572
|
description: "Protects code from accidental AI overwrite or deletion in Cursor IDE \u2014 mandatory pre-write snapshots, review-before-apply, local Git safety net, and deterministic recovery. | \u4FDD\u62A4\u4EE3\u7801\u514D\u53D7 Cursor AI \u4EE3\u7406\u610F\u5916\u8986\u5199\u6216\u5220\u9664\u2014\u2014\u5F3A\u5236\u5199\u524D\u5FEB\u7167\u3001\u9884\u89C8\u518D\u6267\u884C\u3001\u672C\u5730 Git \u5B89\u5168\u7F51\u3001\u786E\u5B9A\u6027\u6062\u590D\u3002",
|
|
35573
35573
|
keywords: [
|
|
35574
35574
|
"cursor",
|
|
@@ -35875,6 +35875,27 @@ var require_doctor = __commonJS({
|
|
|
35875
35875
|
}
|
|
35876
35876
|
const mcpServerPath = path2.resolve(__dirname, "../../mcp/server.js");
|
|
35877
35877
|
const mcpServerExists = fs.existsSync(mcpServerPath);
|
|
35878
|
+
let mcpConfigured = false;
|
|
35879
|
+
let mcpConfigSource = "";
|
|
35880
|
+
const home = process.env.USERPROFILE || process.env.HOME || "";
|
|
35881
|
+
const mcpJsonCandidates = [
|
|
35882
|
+
projectDir ? path2.join(projectDir, ".cursor", "mcp.json") : null,
|
|
35883
|
+
projectDir ? path2.join(projectDir, ".windsurf", "mcp.json") : null,
|
|
35884
|
+
path2.join(home, ".cursor", "mcp.json"),
|
|
35885
|
+
path2.join(home, ".windsurf", "mcp.json")
|
|
35886
|
+
].filter(Boolean);
|
|
35887
|
+
for (const mcpJsonPath of mcpJsonCandidates) {
|
|
35888
|
+
try {
|
|
35889
|
+
if (!fs.existsSync(mcpJsonPath)) continue;
|
|
35890
|
+
const mcpJson = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
|
|
35891
|
+
if (mcpJson.mcpServers?.["cursor-guard"]) {
|
|
35892
|
+
mcpConfigured = true;
|
|
35893
|
+
mcpConfigSource = path2.relative(projectDir || home, mcpJsonPath);
|
|
35894
|
+
break;
|
|
35895
|
+
}
|
|
35896
|
+
} catch {
|
|
35897
|
+
}
|
|
35898
|
+
}
|
|
35878
35899
|
let mcpSdkAvailable = false;
|
|
35879
35900
|
let mcpSdkVersion = null;
|
|
35880
35901
|
const skillRoot = path2.resolve(__dirname, "../../..");
|
|
@@ -35883,11 +35904,9 @@ var require_doctor = __commonJS({
|
|
|
35883
35904
|
];
|
|
35884
35905
|
for (const candidate of sdkCandidates) {
|
|
35885
35906
|
try {
|
|
35886
|
-
|
|
35887
|
-
|
|
35888
|
-
const mcpPkg = JSON.parse(fs.readFileSync(resolved, "utf-8"));
|
|
35907
|
+
if (fs.existsSync(candidate)) {
|
|
35908
|
+
mcpSdkVersion = JSON.parse(fs.readFileSync(candidate, "utf-8")).version;
|
|
35889
35909
|
mcpSdkAvailable = true;
|
|
35890
|
-
mcpSdkVersion = mcpPkg.version;
|
|
35891
35910
|
break;
|
|
35892
35911
|
}
|
|
35893
35912
|
} catch {
|
|
@@ -35899,19 +35918,19 @@ var require_doctor = __commonJS({
|
|
|
35899
35918
|
const sdkDir = mainPath.replace(/[/\\]dist[/\\].*$/, "").replace(/[/\\]src[/\\].*$/, "");
|
|
35900
35919
|
const pkgPath = path2.join(sdkDir, "package.json");
|
|
35901
35920
|
if (fs.existsSync(pkgPath)) {
|
|
35902
|
-
|
|
35921
|
+
mcpSdkVersion = JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
|
|
35903
35922
|
mcpSdkAvailable = true;
|
|
35904
|
-
mcpSdkVersion = mcpPkg.version;
|
|
35905
35923
|
}
|
|
35906
35924
|
} catch {
|
|
35907
35925
|
}
|
|
35908
35926
|
}
|
|
35909
|
-
if (
|
|
35927
|
+
if (mcpConfigured) {
|
|
35928
|
+
const detail = mcpSdkAvailable ? `registered in ${mcpConfigSource}, SDK ${mcpSdkVersion}` : `registered in ${mcpConfigSource} (bundled mode)`;
|
|
35929
|
+
check("MCP server", "PASS", detail);
|
|
35930
|
+
} else if (mcpServerExists && mcpSdkAvailable) {
|
|
35910
35931
|
check("MCP server", "PASS", `server.js found, SDK ${mcpSdkVersion}`);
|
|
35911
35932
|
} else if (mcpServerExists && !mcpSdkAvailable) {
|
|
35912
35933
|
check("MCP server", "WARN", "server.js found but @modelcontextprotocol/sdk not installed \u2014 run: cd <skill-dir> && npm install");
|
|
35913
|
-
} else if (!mcpServerExists && mcpSdkAvailable) {
|
|
35914
|
-
check("MCP server", "WARN", `SDK installed (${mcpSdkVersion}) but server.js not found at expected path`);
|
|
35915
35934
|
} else {
|
|
35916
35935
|
check("MCP server", "WARN", "MCP not configured (optional \u2014 cursor-guard works without it)");
|
|
35917
35936
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "cursor-guard-ide",
|
|
3
3
|
"displayName": "Cursor Guard",
|
|
4
4
|
"description": "AI code protection dashboard embedded in your IDE — real-time alerts, backup history, one-click snapshots",
|
|
5
|
-
"version": "4.8.
|
|
5
|
+
"version": "4.8.4",
|
|
6
6
|
"publisher": "zhangqiang8vipp",
|
|
7
7
|
"license": "BUSL-1.1",
|
|
8
8
|
"engines": {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
> 本文档描述 cursor-guard 从 V2 到 V7 的长期演进方向。
|
|
4
4
|
> 每一代向下兼容,低版本功能永远不废弃。
|
|
5
5
|
>
|
|
6
|
-
> **当前版本**:`V4.8.
|
|
7
|
-
> **文档状态**:`V2` ~ `V4.8.
|
|
6
|
+
> **当前版本**:`V4.8.4`
|
|
7
|
+
> **文档状态**:`V2` ~ `V4.8.4` 已完成交付(含 V5 intent/audit 基础),`V5` 主体规划中
|
|
8
8
|
|
|
9
9
|
## 阅读导航
|
|
10
10
|
|
|
@@ -734,6 +734,22 @@ V4 经过 4 轮系统性代码审查,修复了以下关键问题:
|
|
|
734
734
|
}
|
|
735
735
|
```
|
|
736
736
|
|
|
737
|
+
### V4.8.4:已删除文件恢复命令自动指向父提交 ✅
|
|
738
|
+
|
|
739
|
+
| 修复 | 说明 |
|
|
740
|
+
|------|------|
|
|
741
|
+
| **删除文件恢复命令修正** | 当备份记录中文件 action 为 `D`(删除)时,恢复命令的 `source` 自动改为 `commitHash~1`(父提交,即删除前的版本)。之前指向删除后的 commit,导致恢复报错"文件不存在" |
|
|
742
|
+
| **按钮文案区分** | 删除文件的恢复按钮显示"恢复删除前"(橙色边框),非删除文件显示"复制命令"(默认样式)。同时作用于 Modal 文件详情和 Drawer 文件表格 |
|
|
743
|
+
| **i18n** | 新增 `modal.restorePreDelete` key(EN: "Restore pre-delete" / CN: "恢复删除前") |
|
|
744
|
+
|
|
745
|
+
### V4.8.3:Doctor MCP 检测修复 + Skill 目录 Junction 补全 ✅
|
|
746
|
+
|
|
747
|
+
| 修复 | 说明 |
|
|
748
|
+
|------|------|
|
|
749
|
+
| **Doctor MCP 检查误报修复** | 之前只检查 `server.js` 文件存在性和 SDK `require.resolve`(esbuild bundle 后必然失败)。新增 `.cursor/mcp.json` 和 `.windsurf/mcp.json` 配置扫描(项目级 + 全局级),如果 `cursor-guard` 已注册则直接 PASS,显示 `registered in .cursor/mcp.json (bundled mode)` |
|
|
750
|
+
| **Skill 目录 junction 补全** | v4.8.2 的 `autoInstallSkill` 有 `if (SKILL.md exists) return` 早退逻辑,导致已安装的旧目录永远不会创建 junction。重构为独立检查:即使 SKILL.md 已存在,仍检测 `references/` 是否为 junction、是否缺少 `mcp/` 运行时目录,不满足则删除旧纯文档目录并重建 junction |
|
|
751
|
+
| **package.json 安装增强** | 优先从根 `package.json` 复制,fallback 到 `guard-version.json` |
|
|
752
|
+
|
|
737
753
|
### V4.8.2:Skill 目录运行时完整安装 ✅
|
|
738
754
|
|
|
739
755
|
| 修复 | 说明 |
|
|
@@ -67,47 +67,64 @@ function autoInstallSkill(extRoot, homePath, dirName) {
|
|
|
67
67
|
if (!skillSrc) return actions;
|
|
68
68
|
|
|
69
69
|
const skillTarget = path.join(homePath, 'skills', 'cursor-guard');
|
|
70
|
-
const skillMdTarget = path.join(skillTarget, 'SKILL.md');
|
|
71
|
-
|
|
72
|
-
if (fs.existsSync(skillMdTarget)) return actions;
|
|
73
|
-
|
|
74
70
|
fs.mkdirSync(skillTarget, { recursive: true });
|
|
75
71
|
|
|
72
|
+
// ── Install/update SKILL.md and ROADMAP.md ──
|
|
76
73
|
const skillMdSrc = path.join(skillSrc, 'SKILL.md');
|
|
77
|
-
|
|
74
|
+
const skillMdTarget = path.join(skillTarget, 'SKILL.md');
|
|
75
|
+
if (fs.existsSync(skillMdSrc) && !fs.existsSync(skillMdTarget)) {
|
|
78
76
|
fs.copyFileSync(skillMdSrc, skillMdTarget);
|
|
79
77
|
actions.push('SKILL.md installed');
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
const roadmapSrc = path.join(skillSrc, 'ROADMAP.md');
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
const roadmapDst = path.join(skillTarget, 'ROADMAP.md');
|
|
82
|
+
if (fs.existsSync(roadmapSrc) && !fs.existsSync(roadmapDst)) {
|
|
83
|
+
fs.copyFileSync(roadmapSrc, roadmapDst);
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
//
|
|
88
|
-
// (mcp/server.js, lib/core/*, dashboard/, bin/ etc.)
|
|
86
|
+
// ── Ensure references/ junction exists (runs even for existing installations) ──
|
|
89
87
|
const refsTarget = path.join(skillTarget, 'references');
|
|
90
|
-
|
|
88
|
+
const refsIsJunction = _isSymlinkOrJunction(refsTarget);
|
|
89
|
+
const refsIsPlainDir = !refsIsJunction && fs.existsSync(refsTarget);
|
|
90
|
+
const refsMissingRuntime = refsIsPlainDir && !fs.existsSync(path.join(refsTarget, 'mcp'));
|
|
91
|
+
|
|
92
|
+
if (!fs.existsSync(refsTarget) || refsMissingRuntime) {
|
|
93
|
+
// Remove old plain directory if it only has docs (no runtime)
|
|
94
|
+
if (refsMissingRuntime) {
|
|
95
|
+
try { fs.rmSync(refsTarget, { recursive: true, force: true }); } catch { /* ok */ }
|
|
96
|
+
}
|
|
91
97
|
try {
|
|
92
98
|
fs.symlinkSync(extRoot, refsTarget, 'junction');
|
|
93
99
|
actions.push('references/ linked');
|
|
94
100
|
} catch {
|
|
95
|
-
// junction failed (rare) — fall back to copying essential docs only
|
|
96
101
|
fs.mkdirSync(refsTarget, { recursive: true });
|
|
97
102
|
_copyDocFiles(skillSrc, refsTarget);
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
//
|
|
102
|
-
const pkgSrc = path.join(extRoot, '..', '..', 'package.json');
|
|
106
|
+
// ── Ensure package.json exists ──
|
|
103
107
|
const pkgDst = path.join(skillTarget, 'package.json');
|
|
104
|
-
if (
|
|
105
|
-
|
|
108
|
+
if (!fs.existsSync(pkgDst)) {
|
|
109
|
+
const pkgSrc = path.join(extRoot, '..', '..', 'package.json');
|
|
110
|
+
const guardVer = path.join(extRoot, 'guard-version.json');
|
|
111
|
+
if (fs.existsSync(pkgSrc)) {
|
|
112
|
+
fs.copyFileSync(pkgSrc, pkgDst);
|
|
113
|
+
} else if (fs.existsSync(guardVer)) {
|
|
114
|
+
fs.copyFileSync(guardVer, pkgDst);
|
|
115
|
+
}
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
return actions;
|
|
109
119
|
}
|
|
110
120
|
|
|
121
|
+
function _isSymlinkOrJunction(p) {
|
|
122
|
+
try {
|
|
123
|
+
const stat = fs.lstatSync(p);
|
|
124
|
+
return stat.isSymbolicLink();
|
|
125
|
+
} catch { return false; }
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
function _copyDocFiles(skillSrc, refsTarget) {
|
|
112
129
|
const docs = [
|
|
113
130
|
'config-reference.md', 'config-reference.zh-CN.md',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "cursor-guard-ide",
|
|
3
3
|
"displayName": "Cursor Guard",
|
|
4
4
|
"description": "AI code protection dashboard embedded in your IDE — real-time alerts, backup history, one-click snapshots",
|
|
5
|
-
"version": "4.8.
|
|
5
|
+
"version": "4.8.4",
|
|
6
6
|
"publisher": "zhangqiang8vipp",
|
|
7
7
|
"license": "BUSL-1.1",
|
|
8
8
|
"engines": {
|
|
Binary file
|