cursor-guard 4.9.1 → 4.9.8

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 (60) hide show
  1. package/README.md +130 -10
  2. package/README.zh-CN.md +130 -10
  3. package/ROADMAP.md +65 -8
  4. package/SKILL.md +32 -22
  5. package/package.json +3 -2
  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 -1
  25. package/references/vscode-extension/dist/LICENSE +65 -0
  26. package/references/vscode-extension/dist/{cursor-guard-ide-4.9.1.vsix → cursor-guard-ide-4.9.8.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 +406 -5
  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/dashboard-manager.js +102 -52
  39. package/references/vscode-extension/dist/lib/locale.js +36 -0
  40. package/references/vscode-extension/dist/lib/sidebar-webview.js +1027 -281
  41. package/references/vscode-extension/dist/lib/status-bar.js +95 -68
  42. package/references/vscode-extension/dist/lib/tree-view.js +174 -114
  43. package/references/vscode-extension/dist/lib/utils.js +46 -20
  44. package/references/vscode-extension/dist/mcp/server.js +395 -31
  45. package/references/vscode-extension/dist/media/brand-placeholder.png +0 -0
  46. package/references/vscode-extension/dist/package.json +1 -1
  47. package/references/vscode-extension/dist/skill/ROADMAP.md +65 -8
  48. package/references/vscode-extension/dist/skill/SKILL.md +32 -22
  49. package/references/vscode-extension/dist/skill/config-reference.md +68 -7
  50. package/references/vscode-extension/dist/skill/config-reference.zh-CN.md +68 -7
  51. package/references/vscode-extension/dist/skill/cursor-guard.example.json +11 -7
  52. package/references/vscode-extension/dist/skill/cursor-guard.schema.json +30 -7
  53. package/references/vscode-extension/extension.js +406 -5
  54. package/references/vscode-extension/lib/dashboard-manager.js +102 -52
  55. package/references/vscode-extension/lib/locale.js +36 -0
  56. package/references/vscode-extension/lib/sidebar-webview.js +1027 -281
  57. package/references/vscode-extension/lib/status-bar.js +95 -68
  58. package/references/vscode-extension/lib/tree-view.js +174 -114
  59. package/references/vscode-extension/media/brand-placeholder.png +0 -0
  60. package/references/vscode-extension/package.json +1 -1
@@ -1,68 +1,95 @@
1
- 'use strict';
2
-
3
- const vscode = require('vscode');
4
-
5
- class StatusBarController {
6
- constructor(poller) {
7
- this._item = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
8
- this._item.command = 'cursorGuard.openDashboard';
9
- this._item.tooltip = 'Cursor Guard click to open dashboard';
10
- this._setIdle();
11
- this._item.show();
12
-
13
- this._sub = poller.onChange(data => this._update(data));
14
- }
15
-
16
- _setIdle() {
17
- this._item.text = '$(shield) Guard: init...';
18
- this._item.backgroundColor = undefined;
19
- this._item.color = new vscode.ThemeColor('statusBar.foreground');
20
- }
21
-
22
- _update(data) {
23
- let hasAlert = false;
24
- let watcherRunning = false;
25
- let alertFileCount = 0;
26
- let projectCount = 0;
27
-
28
- for (const [, p] of data) {
29
- projectCount++;
30
- const d = p.dashboard;
31
- if (!d) continue;
32
- if (d.alerts?.active) {
33
- hasAlert = true;
34
- alertFileCount = d.alerts.latest?.fileCount || 0;
35
- }
36
- if (d.watcher?.running) watcherRunning = true;
37
- }
38
-
39
- if (hasAlert) {
40
- this._item.text = `$(bell~spin) Guard: ${alertFileCount} files!`;
41
- this._item.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground');
42
- this._item.color = undefined;
43
- this._item.tooltip = `Cursor Guard — ALERT: ${alertFileCount} files changed rapidly. Click to open dashboard.`;
44
- } else if (watcherRunning) {
45
- this._item.text = '$(shield) Guard: OK';
46
- this._item.backgroundColor = undefined;
47
- this._item.color = new vscode.ThemeColor('statusBar.foreground');
48
- this._item.tooltip = 'Cursor Guard — watcher running, no alerts. Click to open dashboard.';
49
- } else if (projectCount > 0) {
50
- this._item.text = '$(eye-closed) Guard: Unprotected';
51
- this._item.backgroundColor = undefined;
52
- this._item.color = new vscode.ThemeColor('statusBar.foreground');
53
- this._item.tooltip = 'Cursor Guard — watcher NOT running. Click to open dashboard, or use Command Palette > Start Watcher.';
54
- } else {
55
- this._item.text = '$(shield) Guard';
56
- this._item.backgroundColor = undefined;
57
- this._item.color = new vscode.ThemeColor('statusBar.foreground');
58
- this._item.tooltip = 'Cursor Guard — no projects detected';
59
- }
60
- }
61
-
62
- dispose() {
63
- this._sub?.dispose();
64
- this._item.dispose();
65
- }
66
- }
67
-
68
- module.exports = { StatusBarController };
1
+ 'use strict';
2
+
3
+ const vscode = require('vscode');
4
+
5
+ class StatusBarController {
6
+ constructor(poller) {
7
+ this._item = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
8
+ this._item.command = 'cursorGuard.openDashboard';
9
+ this._item.tooltip = 'Cursor Guard - click to open dashboard';
10
+ this._setIdle();
11
+ this._item.show();
12
+
13
+ this._sub = poller.onChange(data => this._update(data));
14
+ }
15
+
16
+ _setIdle() {
17
+ this._item.text = '$(shield) Guard: init...';
18
+ this._item.backgroundColor = undefined;
19
+ this._item.color = new vscode.ThemeColor('statusBar.foreground');
20
+ }
21
+
22
+ _update(data) {
23
+ let hasPreWarning = false;
24
+ let hasAlert = false;
25
+ let watcherRunning = false;
26
+ let preWarningLabel = '';
27
+ let alertFileCount = 0;
28
+ let projectCount = 0;
29
+
30
+ for (const [, project] of data) {
31
+ projectCount++;
32
+ const dashboard = project.dashboard;
33
+ if (!dashboard) continue;
34
+
35
+ if (dashboard.preWarnings?.active && !hasPreWarning) {
36
+ hasPreWarning = true;
37
+ const latest = dashboard.preWarnings.latest;
38
+ preWarningLabel = latest?.file
39
+ ? `${latest.file} (${latest.riskPercent || '?'}%)`
40
+ : `${dashboard.preWarnings.count || 1} pending`;
41
+ }
42
+
43
+ if (dashboard.alerts?.active) {
44
+ hasAlert = true;
45
+ alertFileCount = dashboard.alerts.latest?.fileCount || 0;
46
+ }
47
+
48
+ if (dashboard.watcher?.running) watcherRunning = true;
49
+ }
50
+
51
+ if (hasPreWarning) {
52
+ this._item.text = `$(warning) Guard: Delete Risk`;
53
+ this._item.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground');
54
+ this._item.color = undefined;
55
+ this._item.tooltip = `Cursor Guard - pre-warning active: ${preWarningLabel}. Click to open dashboard.`;
56
+ return;
57
+ }
58
+
59
+ if (hasAlert) {
60
+ this._item.text = `$(bell~spin) Guard: ${alertFileCount} files!`;
61
+ this._item.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground');
62
+ this._item.color = undefined;
63
+ this._item.tooltip = `Cursor Guard - alert: ${alertFileCount} files changed rapidly. Click to open dashboard.`;
64
+ return;
65
+ }
66
+
67
+ if (watcherRunning) {
68
+ this._item.text = '$(shield) Guard: OK';
69
+ this._item.backgroundColor = undefined;
70
+ this._item.color = new vscode.ThemeColor('statusBar.foreground');
71
+ this._item.tooltip = 'Cursor Guard - watcher running, no active alerts. Click to open dashboard.';
72
+ return;
73
+ }
74
+
75
+ if (projectCount > 0) {
76
+ this._item.text = '$(eye-closed) Guard: Unprotected';
77
+ this._item.backgroundColor = undefined;
78
+ this._item.color = new vscode.ThemeColor('statusBar.foreground');
79
+ this._item.tooltip = 'Cursor Guard - watcher not running. Click to open dashboard or start watcher from the Command Palette.';
80
+ return;
81
+ }
82
+
83
+ this._item.text = '$(shield) Guard';
84
+ this._item.backgroundColor = undefined;
85
+ this._item.color = new vscode.ThemeColor('statusBar.foreground');
86
+ this._item.tooltip = 'Cursor Guard - no projects detected';
87
+ }
88
+
89
+ dispose() {
90
+ this._sub?.dispose();
91
+ this._item.dispose();
92
+ }
93
+ }
94
+
95
+ module.exports = { StatusBarController };
@@ -1,114 +1,174 @@
1
- 'use strict';
2
-
3
- const vscode = require('vscode');
4
-
5
- const C = {
6
- green: new vscode.ThemeColor('charts.green'),
7
- red: new vscode.ThemeColor('charts.red'),
8
- yellow: new vscode.ThemeColor('charts.yellow'),
9
- blue: new vscode.ThemeColor('charts.blue'),
10
- purple: new vscode.ThemeColor('charts.purple'),
11
- orange: new vscode.ThemeColor('charts.orange'),
12
- };
13
-
14
- class GuardTreeView {
15
- constructor(poller, dashMgr) {
16
- this._poller = poller;
17
- this._dashMgr = dashMgr;
18
- this._onDidChange = new vscode.EventEmitter();
19
- this.onDidChangeTreeData = this._onDidChange.event;
20
-
21
- this._treeView = vscode.window.createTreeView('cursorGuardProjects', {
22
- treeDataProvider: this,
23
- showCollapseAll: false,
24
- });
25
-
26
- this._sub = poller.onChange(() => this._onDidChange.fire());
27
- }
28
-
29
- refresh() { this._onDidChange.fire(); }
30
- getTreeItem(el) { return el; }
31
-
32
- getChildren(el) {
33
- if (!el) return this._getRootItems();
34
- if (el.contextValue === 'project') return this._getProjectStatus(el.projectId);
35
- return [];
36
- }
37
-
38
- _getRootItems() {
39
- const data = this._poller.data;
40
- if (data.size === 0) {
41
- return [_item('No projects detected', 'info', { icon: 'info', color: C.yellow, desc: 'Add .cursor-guard.json' })];
42
- }
43
- const items = [];
44
- for (const [id, p] of data) {
45
- const d = p.dashboard;
46
- const hasAlert = d?.alerts?.active;
47
- const watcherOk = d?.watcher?.running;
48
- const color = hasAlert ? C.red : watcherOk ? C.green : C.yellow;
49
- const status = hasAlert ? `ALERT ${d.alerts.latest?.fileCount || ''} files` : watcherOk ? 'Protected' : 'Unprotected';
50
- const item = _item(p.name || id, 'project', {
51
- icon: hasAlert ? 'bell' : watcherOk ? 'shield' : 'eye-closed',
52
- color,
53
- desc: status,
54
- collapsible: vscode.TreeItemCollapsibleState.Expanded,
55
- });
56
- item.projectId = id;
57
- items.push(item);
58
- }
59
- return items;
60
- }
61
-
62
- _getProjectStatus(pid) {
63
- const p = this._poller.data.get(pid);
64
- if (!p?.dashboard) return [_item('Loading...', 'loading', { icon: 'loading~spin', color: C.blue })];
65
- const d = p.dashboard;
66
- const items = [];
67
-
68
- if (d.watcher?.running) {
69
- const w = _item('Watcher: Running', 'watcher', { icon: 'eye', color: C.green, desc: `PID ${d.watcher.pid || '?'}` });
70
- items.push(w);
71
- } else {
72
- const w = _item('Watcher: Stopped', 'watcher', { icon: 'eye-closed', color: C.red });
73
- w.command = { command: 'cursorGuard.startWatcher', title: 'Start Watcher' };
74
- w.tooltip = 'Click to start watcher';
75
- items.push(w);
76
- }
77
-
78
- const gitC = d.counts?.git?.commits || 0;
79
- const shadowC = d.counts?.shadow?.snapshots || 0;
80
- const lastAgo = d.lastBackup?.git?.relativeTime || 'never';
81
- items.push(_item(`Backups: ${gitC + shadowC}`, 'stat', { icon: 'history', color: C.blue, desc: `last ${lastAgo}` }));
82
-
83
- const health = d.health?.status || 'unknown';
84
- const hColor = health === 'healthy' ? C.green : health === 'critical' ? C.red : C.yellow;
85
- const hIcon = health === 'healthy' ? 'pass-filled' : health === 'critical' ? 'error' : 'warning';
86
- items.push(_item(`Health: ${health}`, 'health', { icon: hIcon, color: hColor }));
87
-
88
- const openItem = _item('Open Dashboard', 'action', { icon: 'browser', color: C.blue });
89
- openItem.command = { command: 'cursorGuard.openDashboard', title: 'Open' };
90
- items.push(openItem);
91
-
92
- const snapItem = _item('Snapshot Now', 'action', { icon: 'device-camera', color: C.purple });
93
- snapItem.command = { command: 'cursorGuard.snapshotNow', title: 'Snap' };
94
- items.push(snapItem);
95
-
96
- return items;
97
- }
98
-
99
- dispose() {
100
- this._sub?.dispose();
101
- this._treeView.dispose();
102
- this._onDidChange.dispose();
103
- }
104
- }
105
-
106
- function _item(label, ctx, opts = {}) {
107
- const ti = new vscode.TreeItem(label, opts.collapsible || vscode.TreeItemCollapsibleState.None);
108
- ti.contextValue = ctx;
109
- if (opts.icon) ti.iconPath = new vscode.ThemeIcon(opts.icon, opts.color);
110
- if (opts.desc) ti.description = opts.desc;
111
- return ti;
112
- }
113
-
114
- module.exports = { GuardTreeView };
1
+ 'use strict';
2
+
3
+ const vscode = require('vscode');
4
+
5
+ const C = {
6
+ green: new vscode.ThemeColor('charts.green'),
7
+ red: new vscode.ThemeColor('charts.red'),
8
+ yellow: new vscode.ThemeColor('charts.yellow'),
9
+ blue: new vscode.ThemeColor('charts.blue'),
10
+ purple: new vscode.ThemeColor('charts.purple'),
11
+ orange: new vscode.ThemeColor('charts.orange'),
12
+ };
13
+
14
+ class GuardTreeView {
15
+ constructor(poller, dashMgr) {
16
+ this._poller = poller;
17
+ this._dashMgr = dashMgr;
18
+ this._onDidChange = new vscode.EventEmitter();
19
+ this.onDidChangeTreeData = this._onDidChange.event;
20
+
21
+ this._treeView = vscode.window.createTreeView('cursorGuardProjects', {
22
+ treeDataProvider: this,
23
+ showCollapseAll: false,
24
+ });
25
+
26
+ this._sub = poller.onChange(() => this._onDidChange.fire());
27
+ }
28
+
29
+ refresh() { this._onDidChange.fire(); }
30
+ getTreeItem(el) { return el; }
31
+
32
+ getChildren(el) {
33
+ if (!el) return this._getRootItems();
34
+ if (el.contextValue === 'project') return this._getProjectStatus(el.projectId);
35
+ return [];
36
+ }
37
+
38
+ _getRootItems() {
39
+ const data = this._poller.data;
40
+ if (data.size === 0) {
41
+ return [_item('No projects detected', 'info', {
42
+ icon: 'info',
43
+ color: C.yellow,
44
+ desc: 'Add .cursor-guard.json',
45
+ })];
46
+ }
47
+
48
+ const items = [];
49
+ for (const [id, project] of data) {
50
+ const dashboard = project.dashboard;
51
+ const hasPreWarning = dashboard?.preWarnings?.active;
52
+ const hasAlert = dashboard?.alerts?.active;
53
+ const watcherOk = dashboard?.watcher?.running;
54
+
55
+ let color = C.yellow;
56
+ let status = 'Unprotected';
57
+ let icon = 'eye-closed';
58
+
59
+ if (hasPreWarning) {
60
+ color = C.orange;
61
+ icon = 'warning';
62
+ const latest = dashboard.preWarnings.latest;
63
+ status = latest?.file
64
+ ? `Delete risk in ${latest.file}`
65
+ : `Delete risk (${dashboard.preWarnings.count || 1})`;
66
+ } else if (hasAlert) {
67
+ color = C.red;
68
+ icon = 'bell';
69
+ status = `Alert ${dashboard.alerts.latest?.fileCount || ''} files`;
70
+ } else if (watcherOk) {
71
+ color = C.green;
72
+ icon = 'shield';
73
+ status = 'Protected';
74
+ }
75
+
76
+ const item = _item(project.name || id, 'project', {
77
+ icon,
78
+ color,
79
+ desc: status,
80
+ collapsible: vscode.TreeItemCollapsibleState.Expanded,
81
+ });
82
+ item.projectId = id;
83
+ items.push(item);
84
+ }
85
+
86
+ return items;
87
+ }
88
+
89
+ _getProjectStatus(pid) {
90
+ const project = this._poller.data.get(pid);
91
+ if (!project?.dashboard) {
92
+ return [_item('Loading...', 'loading', { icon: 'loading~spin', color: C.blue })];
93
+ }
94
+
95
+ const dashboard = project.dashboard;
96
+ const items = [];
97
+
98
+ if (dashboard.preWarnings?.active) {
99
+ const latest = dashboard.preWarnings.latest || {};
100
+ items.push(_item('Pre-Warning: Active', 'prewarning', {
101
+ icon: 'warning',
102
+ color: C.orange,
103
+ desc: latest.riskPercent ? `${latest.riskPercent}% risk` : undefined,
104
+ }));
105
+
106
+ const detail = latest.file
107
+ ? `${latest.file} - ${latest.summary || 'Review pending deletion'}`
108
+ : `${dashboard.preWarnings.count || 1} destructive edit warning(s) pending`;
109
+ items.push(_item(detail, 'prewarning-detail', {
110
+ icon: 'note',
111
+ color: C.orange,
112
+ }));
113
+ }
114
+
115
+ if (dashboard.watcher?.running) {
116
+ items.push(_item('Watcher: Running', 'watcher', {
117
+ icon: 'eye',
118
+ color: C.green,
119
+ desc: `PID ${dashboard.watcher.pid || '?'}`,
120
+ }));
121
+ } else {
122
+ const watcherItem = _item('Watcher: Stopped', 'watcher', {
123
+ icon: 'eye-closed',
124
+ color: C.red,
125
+ });
126
+ watcherItem.command = { command: 'cursorGuard.startWatcher', title: 'Start Watcher' };
127
+ watcherItem.tooltip = 'Click to start watcher';
128
+ items.push(watcherItem);
129
+ }
130
+
131
+ const gitCount = dashboard.counts?.git?.commits || 0;
132
+ const shadowCount = dashboard.counts?.shadow?.snapshots || 0;
133
+ const lastAgo = dashboard.lastBackup?.git?.relativeTime || 'never';
134
+ items.push(_item(`Backups: ${gitCount + shadowCount}`, 'stat', {
135
+ icon: 'history',
136
+ color: C.blue,
137
+ desc: `last ${lastAgo}`,
138
+ }));
139
+
140
+ const health = dashboard.health?.status || 'unknown';
141
+ const healthColor = health === 'healthy' ? C.green : health === 'critical' ? C.red : C.yellow;
142
+ const healthIcon = health === 'healthy' ? 'pass-filled' : health === 'critical' ? 'error' : 'warning';
143
+ items.push(_item(`Health: ${health}`, 'health', {
144
+ icon: healthIcon,
145
+ color: healthColor,
146
+ }));
147
+
148
+ const openItem = _item('Open Dashboard', 'action', { icon: 'browser', color: C.blue });
149
+ openItem.command = { command: 'cursorGuard.openDashboard', title: 'Open' };
150
+ items.push(openItem);
151
+
152
+ const snapItem = _item('Snapshot Now', 'action', { icon: 'device-camera', color: C.purple });
153
+ snapItem.command = { command: 'cursorGuard.snapshotNow', title: 'Snap' };
154
+ items.push(snapItem);
155
+
156
+ return items;
157
+ }
158
+
159
+ dispose() {
160
+ this._sub?.dispose();
161
+ this._treeView.dispose();
162
+ this._onDidChange.dispose();
163
+ }
164
+ }
165
+
166
+ function _item(label, ctx, opts = {}) {
167
+ const treeItem = new vscode.TreeItem(label, opts.collapsible || vscode.TreeItemCollapsibleState.None);
168
+ treeItem.contextValue = ctx;
169
+ if (opts.icon) treeItem.iconPath = new vscode.ThemeIcon(opts.icon, opts.color);
170
+ if (opts.desc) treeItem.description = opts.desc;
171
+ return treeItem;
172
+ }
173
+
174
+ module.exports = { GuardTreeView };
@@ -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