cursor-guard 4.9.0 → 4.9.6
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/README.md +94 -28
- package/README.zh-CN.md +91 -25
- package/ROADMAP.md +51 -9
- package/SKILL.md +32 -22
- package/package.json +1 -1
- package/references/config-reference.md +68 -7
- package/references/config-reference.zh-CN.md +68 -7
- package/references/cursor-guard.example.json +11 -7
- package/references/cursor-guard.schema.json +30 -7
- package/references/dashboard/public/app.js +73 -27
- package/references/dashboard/public/index.html +8 -7
- package/references/lib/auto-backup.js +40 -2
- package/references/lib/core/backups.js +46 -16
- package/references/lib/core/core.test.js +101 -22
- package/references/lib/core/dashboard.js +37 -23
- package/references/lib/core/doctor.js +19 -13
- package/references/lib/core/pre-warning.js +296 -0
- package/references/lib/core/snapshot.js +24 -2
- package/references/lib/core/status.js +15 -7
- package/references/lib/utils.js +46 -20
- package/references/mcp/mcp.test.js +60 -12
- package/references/mcp/server.js +72 -60
- package/references/quickstart.zh-CN.md +46 -21
- package/references/vscode-extension/build-vsix.js +4 -3
- package/references/vscode-extension/dist/LICENSE +65 -0
- package/references/vscode-extension/dist/{cursor-guard-ide-4.9.0.vsix → cursor-guard-ide-4.9.6.vsix} +0 -0
- package/references/vscode-extension/dist/dashboard/public/app.js +73 -27
- package/references/vscode-extension/dist/dashboard/public/index.html +8 -7
- package/references/vscode-extension/dist/extension.js +498 -296
- package/references/vscode-extension/dist/guard-version.json +1 -1
- package/references/vscode-extension/dist/lib/auto-backup.js +40 -2
- package/references/vscode-extension/dist/lib/core/backups.js +46 -16
- package/references/vscode-extension/dist/lib/core/dashboard.js +37 -23
- package/references/vscode-extension/dist/lib/core/doctor.js +19 -13
- package/references/vscode-extension/dist/lib/core/pre-warning.js +296 -0
- package/references/vscode-extension/dist/lib/core/snapshot.js +24 -2
- package/references/vscode-extension/dist/lib/core/status.js +15 -7
- package/references/vscode-extension/dist/lib/sidebar-webview.js +502 -433
- package/references/vscode-extension/dist/lib/status-bar.js +95 -68
- package/references/vscode-extension/dist/lib/tree-view.js +174 -114
- package/references/vscode-extension/dist/lib/utils.js +46 -20
- package/references/vscode-extension/dist/mcp/server.js +393 -30
- package/references/vscode-extension/dist/package.json +1 -1
- package/references/vscode-extension/dist/skill/ROADMAP.md +51 -9
- package/references/vscode-extension/dist/skill/SKILL.md +32 -22
- package/references/vscode-extension/dist/skill/config-reference.md +68 -7
- package/references/vscode-extension/dist/skill/config-reference.zh-CN.md +68 -7
- package/references/vscode-extension/dist/skill/cursor-guard.example.json +11 -7
- package/references/vscode-extension/dist/skill/cursor-guard.schema.json +30 -7
- package/references/vscode-extension/extension.js +498 -296
- package/references/vscode-extension/lib/sidebar-webview.js +502 -433
- package/references/vscode-extension/lib/status-bar.js +95 -68
- package/references/vscode-extension/lib/tree-view.js +174 -114
- package/references/vscode-extension/package.json +1 -1
package/references/lib/utils.js
CHANGED
|
@@ -133,19 +133,23 @@ const VALID_GIT_RETENTION_MODES = ['days', 'count'];
|
|
|
133
133
|
|
|
134
134
|
const DEFAULT_IGNORE = ['.cursor/skills/**'];
|
|
135
135
|
|
|
136
|
-
const DEFAULT_CONFIG = {
|
|
137
|
-
protect: [],
|
|
138
|
-
ignore: [...DEFAULT_IGNORE],
|
|
139
|
-
secrets_patterns: DEFAULT_SECRETS,
|
|
140
|
-
backup_strategy: 'git',
|
|
136
|
+
const DEFAULT_CONFIG = {
|
|
137
|
+
protect: [],
|
|
138
|
+
ignore: [...DEFAULT_IGNORE],
|
|
139
|
+
secrets_patterns: DEFAULT_SECRETS,
|
|
140
|
+
backup_strategy: 'git',
|
|
141
141
|
auto_backup_interval_seconds: 60,
|
|
142
142
|
pre_restore_backup: 'always',
|
|
143
143
|
retention: { mode: 'days', days: 30, max_count: 100, max_size_mb: 500 },
|
|
144
|
-
git_retention: { enabled: false, mode: 'count', days: 30, max_count: 200 },
|
|
145
|
-
proactive_alert: true,
|
|
146
|
-
alert_thresholds: { files_per_window: 20, window_seconds: 10, cooldown_seconds: 60 },
|
|
147
|
-
always_watch: false,
|
|
148
|
-
|
|
144
|
+
git_retention: { enabled: false, mode: 'count', days: 30, max_count: 200 },
|
|
145
|
+
proactive_alert: true,
|
|
146
|
+
alert_thresholds: { files_per_window: 20, window_seconds: 10, cooldown_seconds: 60 },
|
|
147
|
+
always_watch: false,
|
|
148
|
+
enable_pre_warning: false,
|
|
149
|
+
pre_warning_threshold: 30,
|
|
150
|
+
pre_warning_mode: 'popup',
|
|
151
|
+
pre_warning_exclude_patterns: [],
|
|
152
|
+
};
|
|
149
153
|
|
|
150
154
|
function loadConfig(projectDir) {
|
|
151
155
|
const cfgPath = path.join(projectDir, '.cursor-guard.json');
|
|
@@ -234,16 +238,38 @@ function loadConfig(projectDir) {
|
|
|
234
238
|
if (typeof raw.alert_thresholds.cooldown_seconds === 'number' && raw.alert_thresholds.cooldown_seconds > 0)
|
|
235
239
|
cfg.alert_thresholds.cooldown_seconds = raw.alert_thresholds.cooldown_seconds;
|
|
236
240
|
}
|
|
237
|
-
if (raw.always_watch === true) {
|
|
238
|
-
cfg.always_watch = true;
|
|
239
|
-
} else if (raw.always_watch !== undefined && raw.always_watch !== false) {
|
|
240
|
-
warnings.push(`always_watch should be a boolean, got ${JSON.stringify(raw.always_watch)} — using default (false)`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
241
|
+
if (raw.always_watch === true) {
|
|
242
|
+
cfg.always_watch = true;
|
|
243
|
+
} else if (raw.always_watch !== undefined && raw.always_watch !== false) {
|
|
244
|
+
warnings.push(`always_watch should be a boolean, got ${JSON.stringify(raw.always_watch)} — using default (false)`);
|
|
245
|
+
}
|
|
246
|
+
if (raw.enable_pre_warning === true) {
|
|
247
|
+
cfg.enable_pre_warning = true;
|
|
248
|
+
} else if (raw.enable_pre_warning !== undefined && raw.enable_pre_warning !== false) {
|
|
249
|
+
warnings.push(`enable_pre_warning should be a boolean, got ${JSON.stringify(raw.enable_pre_warning)} — using default (false)`);
|
|
250
|
+
}
|
|
251
|
+
if (typeof raw.pre_warning_threshold === 'number') {
|
|
252
|
+
if (raw.pre_warning_threshold >= 1 && raw.pre_warning_threshold <= 100) {
|
|
253
|
+
cfg.pre_warning_threshold = raw.pre_warning_threshold;
|
|
254
|
+
} else {
|
|
255
|
+
warnings.push(`pre_warning_threshold should be 1-100, got ${JSON.stringify(raw.pre_warning_threshold)} — using default (${cfg.pre_warning_threshold})`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (typeof raw.pre_warning_mode === 'string') {
|
|
259
|
+
if (['popup', 'dashboard', 'silent'].includes(raw.pre_warning_mode)) {
|
|
260
|
+
cfg.pre_warning_mode = raw.pre_warning_mode;
|
|
261
|
+
} else {
|
|
262
|
+
warnings.push(`Unknown pre_warning_mode "${raw.pre_warning_mode}", using default "${cfg.pre_warning_mode}"`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (Array.isArray(raw.pre_warning_exclude_patterns)) {
|
|
266
|
+
cfg.pre_warning_exclude_patterns = sanitizeStringArray(raw.pre_warning_exclude_patterns, 'pre_warning_exclude_patterns');
|
|
267
|
+
}
|
|
268
|
+
return { cfg, loaded: true, error: null, warnings };
|
|
269
|
+
} catch (e) {
|
|
270
|
+
return { cfg, loaded: false, error: e.message };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
247
273
|
|
|
248
274
|
// ── Git helpers ─────────────────────────────────────────────────
|
|
249
275
|
|
|
@@ -350,18 +350,66 @@ async function run() {
|
|
|
350
350
|
name: 'restore_project',
|
|
351
351
|
arguments: { path: tmpDir, source: alertHead, preview: true },
|
|
352
352
|
});
|
|
353
|
-
await test('restore_project response contains _activeAlert', () => {
|
|
354
|
-
const content = rpResp.result.content[0].text;
|
|
355
|
-
const data = JSON.parse(content);
|
|
356
|
-
if (!data._activeAlert) throw new Error('_activeAlert missing from restore_project');
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
// Clean up alert file
|
|
360
|
-
try { fs.unlinkSync(alertFile); } catch { /* ignore */ }
|
|
361
|
-
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
353
|
+
await test('restore_project response contains _activeAlert', () => {
|
|
354
|
+
const content = rpResp.result.content[0].text;
|
|
355
|
+
const data = JSON.parse(content);
|
|
356
|
+
if (!data._activeAlert) throw new Error('_activeAlert missing from restore_project');
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Clean up alert file
|
|
360
|
+
try { fs.unlinkSync(alertFile); } catch { /* ignore */ }
|
|
361
|
+
|
|
362
|
+
// Verify pre-warning injection
|
|
363
|
+
const preWarningFile = path.join(gitDir, 'cursor-guard-pre-warning.json');
|
|
364
|
+
const fakePreWarning = {
|
|
365
|
+
type: 'destructive_edit_risk',
|
|
366
|
+
detectedAt: Date.now(),
|
|
367
|
+
timestamp: new Date().toISOString(),
|
|
368
|
+
expiresAt: new Date(Date.now() + 300000).toISOString(),
|
|
369
|
+
file: 'src/app.js',
|
|
370
|
+
summary: '1 method removed, 5 lines deleted (risk 68%)',
|
|
371
|
+
riskPercent: 68,
|
|
372
|
+
removedMethodCount: 1,
|
|
373
|
+
removedMethods: [{ name: 'login', lineNumber: 12 }],
|
|
374
|
+
deletedLines: 5,
|
|
375
|
+
};
|
|
376
|
+
fs.writeFileSync(preWarningFile, JSON.stringify({
|
|
377
|
+
updatedAt: new Date().toISOString(),
|
|
378
|
+
warnings: [fakePreWarning],
|
|
379
|
+
}, null, 2));
|
|
380
|
+
|
|
381
|
+
const toolsWithPreWarning = ['doctor', 'backup_status', 'dashboard'];
|
|
382
|
+
for (const toolName of toolsWithPreWarning) {
|
|
383
|
+
const resp = await rpc('tools/call', {
|
|
384
|
+
name: toolName,
|
|
385
|
+
arguments: { path: tmpDir },
|
|
386
|
+
});
|
|
387
|
+
await test(`${toolName} response contains _activePreWarning when warning exists`, () => {
|
|
388
|
+
const content = resp.result.content[0].text;
|
|
389
|
+
const data = JSON.parse(content);
|
|
390
|
+
if (!data._activePreWarning) throw new Error(`_activePreWarning missing from ${toolName}`);
|
|
391
|
+
if (data._activePreWarning.count !== 1) throw new Error(`unexpected warning count: ${data._activePreWarning.count}`);
|
|
392
|
+
if (data._activePreWarning.latest?.file !== 'src/app.js') {
|
|
393
|
+
throw new Error(`unexpected warning file: ${data._activePreWarning.latest?.file}`);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const snapshotPreWarningResp = await rpc('tools/call', {
|
|
399
|
+
name: 'snapshot_now',
|
|
400
|
+
arguments: { path: tmpDir, strategy: 'git' },
|
|
401
|
+
});
|
|
402
|
+
await test('snapshot_now response contains _activePreWarning', () => {
|
|
403
|
+
const content = snapshotPreWarningResp.result.content[0].text;
|
|
404
|
+
const data = JSON.parse(content);
|
|
405
|
+
if (!data._activePreWarning) throw new Error('_activePreWarning missing from snapshot_now');
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
try { fs.unlinkSync(preWarningFile); } catch { /* ignore */ }
|
|
409
|
+
|
|
410
|
+
// Cleanup
|
|
411
|
+
serverProcess.kill();
|
|
412
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
365
413
|
|
|
366
414
|
console.log(`\n${passed + failed} tests: \x1b[32m${passed} passed\x1b[0m` + (failed ? `, \x1b[31m${failed} failed\x1b[0m` : ''));
|
|
367
415
|
process.exit(failed > 0 ? 1 : 0);
|
package/references/mcp/server.js
CHANGED
|
@@ -10,10 +10,11 @@ const { runDiagnostics } = require('../lib/core/doctor');
|
|
|
10
10
|
const { createGitSnapshot, createShadowCopy } = require('../lib/core/snapshot');
|
|
11
11
|
const { listBackups } = require('../lib/core/backups');
|
|
12
12
|
const { restoreFile, previewProjectRestore, executeProjectRestore } = require('../lib/core/restore');
|
|
13
|
-
const { runFixes } = require('../lib/core/doctor-fix');
|
|
14
|
-
const { getBackupStatus } = require('../lib/core/status');
|
|
15
|
-
const { getDashboard } = require('../lib/core/dashboard');
|
|
16
|
-
const { loadActiveAlert } = require('../lib/core/anomaly');
|
|
13
|
+
const { runFixes } = require('../lib/core/doctor-fix');
|
|
14
|
+
const { getBackupStatus } = require('../lib/core/status');
|
|
15
|
+
const { getDashboard } = require('../lib/core/dashboard');
|
|
16
|
+
const { loadActiveAlert } = require('../lib/core/anomaly');
|
|
17
|
+
const { loadActivePreWarnings } = require('../lib/core/pre-warning');
|
|
17
18
|
|
|
18
19
|
const { loadConfig, gitDir: getGitDir } = require('../lib/utils');
|
|
19
20
|
|
|
@@ -46,18 +47,29 @@ function ensureWatcher(projectPath) {
|
|
|
46
47
|
|
|
47
48
|
// ── Alert injection helper ──────────────────────────────────────
|
|
48
49
|
|
|
49
|
-
function injectAlert(projectPath, result) {
|
|
50
|
-
const alert = loadActiveAlert(projectPath);
|
|
51
|
-
if (alert) {
|
|
52
|
-
result._activeAlert = {
|
|
53
|
-
type: alert.type,
|
|
50
|
+
function injectAlert(projectPath, result) {
|
|
51
|
+
const alert = loadActiveAlert(projectPath);
|
|
52
|
+
if (alert) {
|
|
53
|
+
result._activeAlert = {
|
|
54
|
+
type: alert.type,
|
|
54
55
|
message: alert.recommendation || `${alert.fileCount} files changed in ${alert.windowSeconds}s`,
|
|
55
56
|
timestamp: alert.timestamp,
|
|
56
57
|
expiresAt: alert.expiresAt,
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function injectPreWarning(projectPath, result) {
|
|
64
|
+
const warnings = loadActivePreWarnings(projectPath);
|
|
65
|
+
if (warnings.length > 0) {
|
|
66
|
+
result._activePreWarning = {
|
|
67
|
+
count: warnings.length,
|
|
68
|
+
latest: warnings[0],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
61
73
|
|
|
62
74
|
// ── Watcher status check ────────────────────────────────────────
|
|
63
75
|
|
|
@@ -101,12 +113,12 @@ server.tool(
|
|
|
101
113
|
{
|
|
102
114
|
path: z.string().describe('Absolute path to the project directory'),
|
|
103
115
|
},
|
|
104
|
-
async ({ path: projectPath }) => {
|
|
105
|
-
const resolved = path.resolve(projectPath);
|
|
106
|
-
ensureWatcher(resolved);
|
|
107
|
-
const result = injectAlert(resolved, runDiagnostics(resolved));
|
|
108
|
-
injectWatcherWarning(resolved, result);
|
|
109
|
-
return {
|
|
116
|
+
async ({ path: projectPath }) => {
|
|
117
|
+
const resolved = path.resolve(projectPath);
|
|
118
|
+
ensureWatcher(resolved);
|
|
119
|
+
const result = injectPreWarning(resolved, injectAlert(resolved, runDiagnostics(resolved)));
|
|
120
|
+
injectWatcherWarning(resolved, result);
|
|
121
|
+
return {
|
|
110
122
|
content: [{
|
|
111
123
|
type: 'text',
|
|
112
124
|
text: JSON.stringify(result, null, 2),
|
|
@@ -126,11 +138,11 @@ server.tool(
|
|
|
126
138
|
before: z.string().optional().describe('Only show backups before this time (e.g. "10 minutes ago", "2026-03-21T14:00:00")'),
|
|
127
139
|
limit: z.number().optional().describe('Max results per source (default 20)'),
|
|
128
140
|
},
|
|
129
|
-
async ({ path: projectPath, file, before, limit }) => {
|
|
130
|
-
const resolved = path.resolve(projectPath);
|
|
131
|
-
ensureWatcher(resolved);
|
|
132
|
-
const result = injectAlert(resolved, listBackups(resolved, { file, before, limit }));
|
|
133
|
-
return {
|
|
141
|
+
async ({ path: projectPath, file, before, limit }) => {
|
|
142
|
+
const resolved = path.resolve(projectPath);
|
|
143
|
+
ensureWatcher(resolved);
|
|
144
|
+
const result = injectPreWarning(resolved, injectAlert(resolved, listBackups(resolved, { file, before, limit })));
|
|
145
|
+
return {
|
|
134
146
|
content: [{
|
|
135
147
|
type: 'text',
|
|
136
148
|
text: JSON.stringify(result, null, 2),
|
|
@@ -180,12 +192,12 @@ server.tool(
|
|
|
180
192
|
});
|
|
181
193
|
}
|
|
182
194
|
|
|
183
|
-
if (effectiveStrategy === 'shadow' || effectiveStrategy === 'both') {
|
|
184
|
-
results.shadow = createShadowCopy(resolved, cfg);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
injectAlert(resolved, results);
|
|
188
|
-
injectWatcherWarning(resolved, results);
|
|
195
|
+
if (effectiveStrategy === 'shadow' || effectiveStrategy === 'both') {
|
|
196
|
+
results.shadow = createShadowCopy(resolved, cfg);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
injectPreWarning(resolved, injectAlert(resolved, results));
|
|
200
|
+
injectWatcherWarning(resolved, results);
|
|
189
201
|
|
|
190
202
|
return {
|
|
191
203
|
content: [{
|
|
@@ -207,13 +219,13 @@ server.tool(
|
|
|
207
219
|
source: z.string().describe('Backup source: git commit hash, ref name, or shadow copy timestamp (e.g. "20260321_143205")'),
|
|
208
220
|
preserve_current: z.boolean().optional().describe('Create pre-restore snapshot before restoring (default true)'),
|
|
209
221
|
},
|
|
210
|
-
async ({ path: projectPath, file, source, preserve_current }) => {
|
|
211
|
-
const resolved = path.resolve(projectPath);
|
|
212
|
-
ensureWatcher(resolved);
|
|
213
|
-
const result = injectAlert(resolved, restoreFile(resolved, file, source, {
|
|
214
|
-
preserveCurrent: preserve_current,
|
|
215
|
-
}));
|
|
216
|
-
injectWatcherWarning(resolved, result);
|
|
222
|
+
async ({ path: projectPath, file, source, preserve_current }) => {
|
|
223
|
+
const resolved = path.resolve(projectPath);
|
|
224
|
+
ensureWatcher(resolved);
|
|
225
|
+
const result = injectPreWarning(resolved, injectAlert(resolved, restoreFile(resolved, file, source, {
|
|
226
|
+
preserveCurrent: preserve_current,
|
|
227
|
+
})));
|
|
228
|
+
injectWatcherWarning(resolved, result);
|
|
217
229
|
return {
|
|
218
230
|
content: [{
|
|
219
231
|
type: 'text',
|
|
@@ -239,16 +251,16 @@ server.tool(
|
|
|
239
251
|
const resolved = path.resolve(projectPath);
|
|
240
252
|
ensureWatcher(resolved);
|
|
241
253
|
|
|
242
|
-
if (preview !== false) {
|
|
243
|
-
const result = injectAlert(resolved, previewProjectRestore(resolved, source));
|
|
244
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const result = injectAlert(resolved, executeProjectRestore(resolved, source, {
|
|
248
|
-
preserveCurrent: preserve_current,
|
|
249
|
-
cleanUntracked: clean_untracked,
|
|
250
|
-
}));
|
|
251
|
-
injectWatcherWarning(resolved, result);
|
|
254
|
+
if (preview !== false) {
|
|
255
|
+
const result = injectPreWarning(resolved, injectAlert(resolved, previewProjectRestore(resolved, source)));
|
|
256
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const result = injectPreWarning(resolved, injectAlert(resolved, executeProjectRestore(resolved, source, {
|
|
260
|
+
preserveCurrent: preserve_current,
|
|
261
|
+
cleanUntracked: clean_untracked,
|
|
262
|
+
})));
|
|
263
|
+
injectWatcherWarning(resolved, result);
|
|
252
264
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
253
265
|
}
|
|
254
266
|
);
|
|
@@ -262,11 +274,11 @@ server.tool(
|
|
|
262
274
|
path: z.string().describe('Absolute path to the project directory'),
|
|
263
275
|
dry_run: z.boolean().optional().describe('If true, report what would be fixed without modifying anything (default false)'),
|
|
264
276
|
},
|
|
265
|
-
async ({ path: projectPath, dry_run }) => {
|
|
266
|
-
const resolved = path.resolve(projectPath);
|
|
267
|
-
ensureWatcher(resolved);
|
|
268
|
-
const result = injectAlert(resolved, runFixes(resolved, { dryRun: !!dry_run }));
|
|
269
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
277
|
+
async ({ path: projectPath, dry_run }) => {
|
|
278
|
+
const resolved = path.resolve(projectPath);
|
|
279
|
+
ensureWatcher(resolved);
|
|
280
|
+
const result = injectPreWarning(resolved, injectAlert(resolved, runFixes(resolved, { dryRun: !!dry_run })));
|
|
281
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
270
282
|
}
|
|
271
283
|
);
|
|
272
284
|
|
|
@@ -278,11 +290,11 @@ server.tool(
|
|
|
278
290
|
{
|
|
279
291
|
path: z.string().describe('Absolute path to the project directory'),
|
|
280
292
|
},
|
|
281
|
-
async ({ path: projectPath }) => {
|
|
282
|
-
const resolved = path.resolve(projectPath);
|
|
283
|
-
ensureWatcher(resolved);
|
|
284
|
-
const result = injectAlert(resolved, getBackupStatus(resolved));
|
|
285
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
293
|
+
async ({ path: projectPath }) => {
|
|
294
|
+
const resolved = path.resolve(projectPath);
|
|
295
|
+
ensureWatcher(resolved);
|
|
296
|
+
const result = injectPreWarning(resolved, injectAlert(resolved, getBackupStatus(resolved)));
|
|
297
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
286
298
|
}
|
|
287
299
|
);
|
|
288
300
|
|
|
@@ -294,11 +306,11 @@ server.tool(
|
|
|
294
306
|
{
|
|
295
307
|
path: z.string().describe('Absolute path to the project directory'),
|
|
296
308
|
},
|
|
297
|
-
async ({ path: projectPath }) => {
|
|
298
|
-
const resolved = path.resolve(projectPath);
|
|
299
|
-
ensureWatcher(resolved);
|
|
300
|
-
const result = injectAlert(resolved, getDashboard(resolved));
|
|
301
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
309
|
+
async ({ path: projectPath }) => {
|
|
310
|
+
const resolved = path.resolve(projectPath);
|
|
311
|
+
ensureWatcher(resolved);
|
|
312
|
+
const result = injectPreWarning(resolved, injectAlert(resolved, getDashboard(resolved)));
|
|
313
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
302
314
|
}
|
|
303
315
|
);
|
|
304
316
|
|
|
@@ -202,13 +202,17 @@ npx cursor-guard-backup --path .
|
|
|
202
202
|
{
|
|
203
203
|
"protect": ["src/**", "package.json", "README.md"],
|
|
204
204
|
"ignore": ["dist/**", "node_modules/**", "*.log"],
|
|
205
|
-
"backup_strategy": "both",
|
|
206
|
-
"auto_backup_interval_seconds": 60,
|
|
207
|
-
"pre_restore_backup": "always",
|
|
208
|
-
"secrets_patterns_extra": ["*.secret", "tokens.*"],
|
|
209
|
-
"
|
|
210
|
-
|
|
211
|
-
|
|
205
|
+
"backup_strategy": "both",
|
|
206
|
+
"auto_backup_interval_seconds": 60,
|
|
207
|
+
"pre_restore_backup": "always",
|
|
208
|
+
"secrets_patterns_extra": ["*.secret", "tokens.*"],
|
|
209
|
+
"enable_pre_warning": true,
|
|
210
|
+
"pre_warning_threshold": 30,
|
|
211
|
+
"pre_warning_mode": "popup",
|
|
212
|
+
"pre_warning_exclude_patterns": ["generated/**"],
|
|
213
|
+
"retention": {
|
|
214
|
+
"mode": "days",
|
|
215
|
+
"days": 30,
|
|
212
216
|
"max_count": 100,
|
|
213
217
|
"max_size_mb": 500
|
|
214
218
|
},
|
|
@@ -230,12 +234,13 @@ npx cursor-guard-backup --path .
|
|
|
230
234
|
|
|
231
235
|
- 只重点保护 `src/**`、`package.json`、`README.md`
|
|
232
236
|
- 忽略产物目录和日志
|
|
233
|
-
- 同时启用 Git 备份和影子拷贝
|
|
234
|
-
- 每 60 秒检查一次变化
|
|
235
|
-
- 恢复前默认先保留当前版本
|
|
236
|
-
-
|
|
237
|
-
-
|
|
238
|
-
-
|
|
237
|
+
- 同时启用 Git 备份和影子拷贝
|
|
238
|
+
- 每 60 秒检查一次变化
|
|
239
|
+
- 恢复前默认先保留当前版本
|
|
240
|
+
- 开启局部删除的事先预警,删方法/大段删行时会先提醒
|
|
241
|
+
- 额外排除自定义敏感文件
|
|
242
|
+
- 自动清理过旧备份
|
|
243
|
+
- 开启主动变更频率检测(10 秒内 20+ 文件变更时告警)
|
|
239
244
|
|
|
240
245
|
---
|
|
241
246
|
|
|
@@ -274,16 +279,36 @@ npx cursor-guard-backup --path .
|
|
|
274
279
|
|
|
275
280
|
控制 `refs/guard/auto-backup` 里的 Git 备份保留多久。
|
|
276
281
|
|
|
277
|
-
### `proactive_alert`
|
|
278
|
-
|
|
279
|
-
是否开启 V4 主动变更频率检测(默认 `true`)。开启后 watcher 会自动监控文件变更速率,异常时触发告警。
|
|
280
|
-
|
|
281
|
-
### `alert_thresholds`
|
|
282
|
+
### `proactive_alert`
|
|
283
|
+
|
|
284
|
+
是否开启 V4 主动变更频率检测(默认 `true`)。开启后 watcher 会自动监控文件变更速率,异常时触发告警。
|
|
285
|
+
|
|
286
|
+
### `alert_thresholds`
|
|
282
287
|
|
|
283
288
|
告警阈值配置:
|
|
284
|
-
- `files_per_window`:窗口内触发告警的文件数(默认 20)
|
|
285
|
-
- `window_seconds`:滑动窗口秒数(默认 10)
|
|
286
|
-
- `cooldown_seconds`:连续告警最小间隔(默认 60)
|
|
289
|
+
- `files_per_window`:窗口内触发告警的文件数(默认 20)
|
|
290
|
+
- `window_seconds`:滑动窗口秒数(默认 10)
|
|
291
|
+
- `cooldown_seconds`:连续告警最小间隔(默认 60)
|
|
292
|
+
|
|
293
|
+
### `enable_pre_warning`
|
|
294
|
+
|
|
295
|
+
是否开启“事先预警”功能(默认 `false`)。开启后,IDE 扩展会对局部删代码、删方法这类高风险编辑先做提醒。
|
|
296
|
+
|
|
297
|
+
### `pre_warning_threshold`
|
|
298
|
+
|
|
299
|
+
删除风险阈值(默认 `30`)。删行比例达到这个值时触发预警。
|
|
300
|
+
|
|
301
|
+
### `pre_warning_mode`
|
|
302
|
+
|
|
303
|
+
预警展示模式:
|
|
304
|
+
|
|
305
|
+
- `popup`:弹窗提醒,可快速撤销或看 diff
|
|
306
|
+
- `dashboard`:只在仪表盘/状态栏/侧边栏高亮
|
|
307
|
+
- `silent`:只记录,不主动打断
|
|
308
|
+
|
|
309
|
+
### `pre_warning_exclude_patterns`
|
|
310
|
+
|
|
311
|
+
哪些文件跳过事先预警评估。常用于生成文件、迁移脚本、第三方目录等。
|
|
287
312
|
|
|
288
313
|
---
|
|
289
314
|
|
|
@@ -28,9 +28,10 @@ const copyMap = [
|
|
|
28
28
|
{ src: path.join(REFS, 'lib', 'guard-doctor.js'), dst: path.join('lib', 'guard-doctor.js') },
|
|
29
29
|
{ src: path.join(REFS, 'bin'), dst: 'bin', type: 'dir' },
|
|
30
30
|
|
|
31
|
-
{ src: path.join(ROOT, 'SKILL.md'), dst: path.join('skill', 'SKILL.md') },
|
|
32
|
-
{ src: path.join(ROOT, 'ROADMAP.md'), dst: path.join('skill', 'ROADMAP.md') },
|
|
33
|
-
{ src: path.join(
|
|
31
|
+
{ src: path.join(ROOT, 'SKILL.md'), dst: path.join('skill', 'SKILL.md') },
|
|
32
|
+
{ src: path.join(ROOT, 'ROADMAP.md'), dst: path.join('skill', 'ROADMAP.md') },
|
|
33
|
+
{ src: path.join(ROOT, 'LICENSE'), dst: 'LICENSE' },
|
|
34
|
+
{ src: path.join(REFS, 'cursor-guard.example.json'), dst: path.join('skill', 'cursor-guard.example.json') },
|
|
34
35
|
{ src: path.join(REFS, 'cursor-guard.schema.json'), dst: path.join('skill', 'cursor-guard.schema.json') },
|
|
35
36
|
{ src: path.join(REFS, 'config-reference.md'), dst: path.join('skill', 'config-reference.md') },
|
|
36
37
|
{ src: path.join(REFS, 'config-reference.zh-CN.md'), dst: path.join('skill', 'config-reference.zh-CN.md') },
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
Parameters
|
|
4
|
+
|
|
5
|
+
Licensor: zhangqiang8vipp
|
|
6
|
+
Licensed Work: cursor-guard
|
|
7
|
+
The Licensed Work is (c) 2026 zhangqiang8vipp
|
|
8
|
+
Additional Use Grant: You may make use of the Licensed Work, provided that
|
|
9
|
+
you may not use the Licensed Work for a Commercial Use.
|
|
10
|
+
"Commercial Use" means distribution, sale, licensing,
|
|
11
|
+
sublicensing, or providing the Licensed Work (or any
|
|
12
|
+
derivative work) to third parties as a paid product or
|
|
13
|
+
service, or incorporating it into a paid product or
|
|
14
|
+
service offered to third parties.
|
|
15
|
+
Change Date: 2056-03-22
|
|
16
|
+
Change License: Apache License, Version 2.0
|
|
17
|
+
|
|
18
|
+
For information about alternative licensing arrangements for the Licensed Work,
|
|
19
|
+
please contact: zhangqiang8vipp
|
|
20
|
+
|
|
21
|
+
Notice
|
|
22
|
+
|
|
23
|
+
Business Source License 1.1
|
|
24
|
+
|
|
25
|
+
Terms
|
|
26
|
+
|
|
27
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
28
|
+
works, redistribute, and make non-commercial use of the Licensed Work. The
|
|
29
|
+
Licensor may make an Additional Use Grant, above, permitting limited commercial
|
|
30
|
+
use.
|
|
31
|
+
|
|
32
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
33
|
+
available distribution of a specific version of the Licensed Work under this
|
|
34
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
35
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
36
|
+
above terminate.
|
|
37
|
+
|
|
38
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
39
|
+
currently in effect as described in this License, you must purchase a
|
|
40
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
41
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
42
|
+
|
|
43
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
44
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
45
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
46
|
+
for each version of the Licensed Work released by Licensor.
|
|
47
|
+
|
|
48
|
+
You must conspicuously display this License on each original or modified copy
|
|
49
|
+
of the Licensed Work. If you receive the Licensed Work in original or
|
|
50
|
+
modified form from a third party, the terms and conditions set forth in this
|
|
51
|
+
License apply to your use of that work.
|
|
52
|
+
|
|
53
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
54
|
+
terminate your rights under this License for the current and all other
|
|
55
|
+
versions of the Licensed Work.
|
|
56
|
+
|
|
57
|
+
This License does not grant you any right in any trademark or logo of
|
|
58
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
59
|
+
Licensor as expressly required by this License).
|
|
60
|
+
|
|
61
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
62
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
63
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
64
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
65
|
+
TITLE.
|