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.
Files changed (54) hide show
  1. package/README.md +94 -28
  2. package/README.zh-CN.md +91 -25
  3. package/ROADMAP.md +51 -9
  4. package/SKILL.md +32 -22
  5. package/package.json +1 -1
  6. package/references/config-reference.md +68 -7
  7. package/references/config-reference.zh-CN.md +68 -7
  8. package/references/cursor-guard.example.json +11 -7
  9. package/references/cursor-guard.schema.json +30 -7
  10. package/references/dashboard/public/app.js +73 -27
  11. package/references/dashboard/public/index.html +8 -7
  12. package/references/lib/auto-backup.js +40 -2
  13. package/references/lib/core/backups.js +46 -16
  14. package/references/lib/core/core.test.js +101 -22
  15. package/references/lib/core/dashboard.js +37 -23
  16. package/references/lib/core/doctor.js +19 -13
  17. package/references/lib/core/pre-warning.js +296 -0
  18. package/references/lib/core/snapshot.js +24 -2
  19. package/references/lib/core/status.js +15 -7
  20. package/references/lib/utils.js +46 -20
  21. package/references/mcp/mcp.test.js +60 -12
  22. package/references/mcp/server.js +72 -60
  23. package/references/quickstart.zh-CN.md +46 -21
  24. package/references/vscode-extension/build-vsix.js +4 -3
  25. package/references/vscode-extension/dist/LICENSE +65 -0
  26. package/references/vscode-extension/dist/{cursor-guard-ide-4.9.0.vsix → cursor-guard-ide-4.9.6.vsix} +0 -0
  27. package/references/vscode-extension/dist/dashboard/public/app.js +73 -27
  28. package/references/vscode-extension/dist/dashboard/public/index.html +8 -7
  29. package/references/vscode-extension/dist/extension.js +498 -296
  30. package/references/vscode-extension/dist/guard-version.json +1 -1
  31. package/references/vscode-extension/dist/lib/auto-backup.js +40 -2
  32. package/references/vscode-extension/dist/lib/core/backups.js +46 -16
  33. package/references/vscode-extension/dist/lib/core/dashboard.js +37 -23
  34. package/references/vscode-extension/dist/lib/core/doctor.js +19 -13
  35. package/references/vscode-extension/dist/lib/core/pre-warning.js +296 -0
  36. package/references/vscode-extension/dist/lib/core/snapshot.js +24 -2
  37. package/references/vscode-extension/dist/lib/core/status.js +15 -7
  38. package/references/vscode-extension/dist/lib/sidebar-webview.js +502 -433
  39. package/references/vscode-extension/dist/lib/status-bar.js +95 -68
  40. package/references/vscode-extension/dist/lib/tree-view.js +174 -114
  41. package/references/vscode-extension/dist/lib/utils.js +46 -20
  42. package/references/vscode-extension/dist/mcp/server.js +393 -30
  43. package/references/vscode-extension/dist/package.json +1 -1
  44. package/references/vscode-extension/dist/skill/ROADMAP.md +51 -9
  45. package/references/vscode-extension/dist/skill/SKILL.md +32 -22
  46. package/references/vscode-extension/dist/skill/config-reference.md +68 -7
  47. package/references/vscode-extension/dist/skill/config-reference.zh-CN.md +68 -7
  48. package/references/vscode-extension/dist/skill/cursor-guard.example.json +11 -7
  49. package/references/vscode-extension/dist/skill/cursor-guard.schema.json +30 -7
  50. package/references/vscode-extension/extension.js +498 -296
  51. package/references/vscode-extension/lib/sidebar-webview.js +502 -433
  52. package/references/vscode-extension/lib/status-bar.js +95 -68
  53. package/references/vscode-extension/lib/tree-view.js +174 -114
  54. package/references/vscode-extension/package.json +1 -1
@@ -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
- return { cfg, loaded: true, error: null, warnings };
243
- } catch (e) {
244
- return { cfg, loaded: false, error: e.message };
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
- // Cleanup
363
- serverProcess.kill();
364
- fs.rmSync(tmpDir, { recursive: true, force: true });
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);
@@ -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
- "retention": {
210
- "mode": "days",
211
- "days": 30,
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
- - 开启主动变更频率检测(10 秒内 20+ 文件变更时告警)
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(REFS, 'cursor-guard.example.json'), dst: path.join('skill', 'cursor-guard.example.json') },
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.