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.
- package/README.md +130 -10
- package/README.zh-CN.md +130 -10
- package/ROADMAP.md +65 -8
- package/SKILL.md +32 -22
- package/package.json +3 -2
- 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 -1
- package/references/vscode-extension/dist/LICENSE +65 -0
- package/references/vscode-extension/dist/{cursor-guard-ide-4.9.1.vsix → cursor-guard-ide-4.9.8.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 +406 -5
- 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/dashboard-manager.js +102 -52
- package/references/vscode-extension/dist/lib/locale.js +36 -0
- package/references/vscode-extension/dist/lib/sidebar-webview.js +1027 -281
- 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 +395 -31
- package/references/vscode-extension/dist/media/brand-placeholder.png +0 -0
- package/references/vscode-extension/dist/package.json +1 -1
- package/references/vscode-extension/dist/skill/ROADMAP.md +65 -8
- 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 +406 -5
- package/references/vscode-extension/lib/dashboard-manager.js +102 -52
- package/references/vscode-extension/lib/locale.js +36 -0
- package/references/vscode-extension/lib/sidebar-webview.js +1027 -281
- package/references/vscode-extension/lib/status-bar.js +95 -68
- package/references/vscode-extension/lib/tree-view.js +174 -114
- package/references/vscode-extension/media/brand-placeholder.png +0 -0
- package/references/vscode-extension/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -62,17 +62,24 @@ On first trigger in a session, check if the workspace root contains `.cursor-gua
|
|
|
62
62
|
// "count": keep N newest commits. "days": keep commits from last N days.
|
|
63
63
|
"git_retention": { "enabled": false, "mode": "count", "max_count": 200 },
|
|
64
64
|
|
|
65
|
-
// V4: Proactive change-velocity detection (default: on).
|
|
66
|
-
// When enabled, the watcher monitors file change frequency and raises
|
|
67
|
-
// alerts when abnormal patterns are detected (e.g. 20+ files in 10s).
|
|
68
|
-
"proactive_alert": true,
|
|
69
|
-
"alert_thresholds": {
|
|
70
|
-
"files_per_window": 20, // trigger threshold
|
|
71
|
-
"window_seconds": 10, // sliding window
|
|
72
|
-
"cooldown_seconds": 60 // min gap between alerts
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
// V4: Proactive change-velocity detection (default: on).
|
|
66
|
+
// When enabled, the watcher monitors file change frequency and raises
|
|
67
|
+
// alerts when abnormal patterns are detected (e.g. 20+ files in 10s).
|
|
68
|
+
"proactive_alert": true,
|
|
69
|
+
"alert_thresholds": {
|
|
70
|
+
"files_per_window": 20, // trigger threshold
|
|
71
|
+
"window_seconds": 10, // sliding window
|
|
72
|
+
"cooldown_seconds": 60 // min gap between alerts
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// V4.9.7: destructive partial-delete pre-warning (default: off).
|
|
76
|
+
// Triggered when deletion risk is high or whole methods/functions disappear.
|
|
77
|
+
"enable_pre_warning": false,
|
|
78
|
+
"pre_warning_threshold": 30,
|
|
79
|
+
"pre_warning_mode": "popup",
|
|
80
|
+
"pre_warning_exclude_patterns": []
|
|
81
|
+
}
|
|
82
|
+
```
|
|
76
83
|
|
|
77
84
|
**Resolution rules:**
|
|
78
85
|
- `protect` set + `ignore` set → file must match a `protect` pattern AND not match any `ignore` pattern.
|
|
@@ -82,10 +89,12 @@ On first trigger in a session, check if the workspace root contains `.cursor-gua
|
|
|
82
89
|
|
|
83
90
|
**`secrets_patterns`**: Glob patterns for sensitive files (`.env`, keys, certificates). Matching files are **auto-excluded** from backup commits, even within `protect` scope. Built-in defaults: `.env`, `.env.*`, `*.key`, `*.pem`, `*.p12`, `*.pfx`, `credentials*`. Set this field to override.
|
|
84
91
|
|
|
85
|
-
**`retention`**: Controls automatic cleanup of old shadow-copy snapshots in `.cursor-guard-backup/`:
|
|
86
|
-
- `"days"` (default): keep snapshots from the last N days (default **30**).
|
|
87
|
-
- `"count"`: keep the N most recent snapshots (default 100).
|
|
88
|
-
- `"size"`: keep total shadow-copy folder under N MB (default 500).
|
|
92
|
+
**`retention`**: Controls automatic cleanup of old shadow-copy snapshots in `.cursor-guard-backup/`:
|
|
93
|
+
- `"days"` (default): keep snapshots from the last N days (default **30**).
|
|
94
|
+
- `"count"`: keep the N most recent snapshots (default 100).
|
|
95
|
+
- `"size"`: keep total shadow-copy folder under N MB (default 500).
|
|
96
|
+
|
|
97
|
+
**`enable_pre_warning` / `pre_warning_*`**: Optional IDE-side "last brake" for destructive partial deletions. When enabled, the extension scores deletion-heavy edits, removed methods/functions, and suspicious line drops. `popup` interrupts with quick actions, `dashboard` only surfaces the warning in UI/status, and `silent` only records it. Active warnings are exposed through `backup_status` / `dashboard` as `_activePreWarning`.
|
|
89
98
|
|
|
90
99
|
**If no config file exists**, the agent operates in "protect everything" mode (backward compatible). Mention to the user that they can create `.cursor-guard.json` to narrow scope — see [references/cursor-guard.example.json](references/cursor-guard.example.json).
|
|
91
100
|
|
|
@@ -111,12 +120,13 @@ cursor-guard provides an **MCP server** (`cursor-guard-mcp`) as an optional enha
|
|
|
111
120
|
| Change-velocity alerts | `alert_status` | manual: check alert file in .git/ or .cursor-guard-backup/ |
|
|
112
121
|
|
|
113
122
|
**Rules**:
|
|
114
|
-
- MCP results are JSON — parse `status`, `error`, and data fields; do not re-run shell to verify.
|
|
115
|
-
- If an MCP call returns an `error` field, report it to the user and fall back to the shell path for that operation.
|
|
116
|
-
- All Hard Rules (§Hard Rules) still apply regardless of execution path. MCP tools enforce them internally (e.g. `restore_file` creates a pre-restore snapshot by default).
|
|
117
|
-
- If MCP is not configured, the skill works exactly as before — **no degradation**.
|
|
118
|
-
- `
|
|
119
|
-
- `
|
|
123
|
+
- MCP results are JSON — parse `status`, `error`, and data fields; do not re-run shell to verify.
|
|
124
|
+
- If an MCP call returns an `error` field, report it to the user and fall back to the shell path for that operation.
|
|
125
|
+
- All Hard Rules (§Hard Rules) still apply regardless of execution path. MCP tools enforce them internally (e.g. `restore_file` creates a pre-restore snapshot by default).
|
|
126
|
+
- If MCP is not configured, the skill works exactly as before — **no degradation**.
|
|
127
|
+
- `backup_status` and `dashboard` may include `_activePreWarning`; when present, surface it as an active delete-risk warning before giving the rest of the health summary.
|
|
128
|
+
- `doctor_fix` is safe to call — each fix is idempotent. Use `dry_run: true` to preview changes before applying. Typical fixes: create missing config, init git repo, gitignore backup dir, remove stale lock file.
|
|
129
|
+
- `restore_project` with `preview: false` executes a full restore including pre-restore snapshot. Always call with `preview: true` first, show the result to the user, and only execute after explicit confirmation.
|
|
120
130
|
|
|
121
131
|
When the target file of an edit **falls outside the protected scope**, the agent:
|
|
122
132
|
- Still applies "Read before Write" (Hard Rule §2).
|
|
@@ -603,7 +613,7 @@ Skip the block for unrelated turns.
|
|
|
603
613
|
- Recovery commands: [references/recovery.md](references/recovery.md)
|
|
604
614
|
- Auto-backup (Node.js core): [references/lib/auto-backup.js](references/lib/auto-backup.js)
|
|
605
615
|
- Guard doctor (Node.js core): [references/lib/guard-doctor.js](references/lib/guard-doctor.js)
|
|
606
|
-
- Core modules: [references/lib/core/](references/lib/core/) (doctor, doctor-fix, snapshot, backups, restore, status, anomaly, dashboard)
|
|
616
|
+
- Core modules: [references/lib/core/](references/lib/core/) (doctor, doctor-fix, snapshot, backups, restore, status, anomaly, pre-warning, dashboard)
|
|
607
617
|
- MCP server: [references/mcp/server.js](references/mcp/server.js) (9 tools: doctor, doctor_fix, backup_status, list_backups, snapshot_now, restore_file, restore_project, dashboard, alert_status)
|
|
608
618
|
- Web dashboard: [references/dashboard/](references/dashboard/) (local read-only web UI — `node references/dashboard/server.js --path <project>`)
|
|
609
619
|
- Shared utilities: [references/lib/utils.js](references/lib/utils.js)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-guard",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.8",
|
|
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",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"node": ">=18"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
-
"test": "node references/lib/utils.test.js && node references/lib/core/core.test.js && node references/mcp/mcp.test.js"
|
|
27
|
+
"test": "node references/lib/utils.test.js && node references/lib/core/core.test.js && node references/mcp/mcp.test.js",
|
|
28
|
+
"release:checklist": "node scripts/print-release-checklist.js"
|
|
28
29
|
},
|
|
29
30
|
"bin": {
|
|
30
31
|
"cursor-guard-init": "references/bin/cursor-guard-init.js",
|
|
@@ -206,10 +206,71 @@ Thresholds for proactive change-velocity alerts. Only effective when `proactive_
|
|
|
206
206
|
| `window_seconds` | `integer` | `10` | Sliding time window in seconds for counting file changes |
|
|
207
207
|
| `cooldown_seconds` | `integer` | `60` | Minimum seconds between consecutive alerts to avoid noise |
|
|
208
208
|
|
|
209
|
-
```json
|
|
210
|
-
"alert_thresholds": {
|
|
211
|
-
"files_per_window": 20,
|
|
212
|
-
"window_seconds": 10,
|
|
213
|
-
"cooldown_seconds": 60
|
|
214
|
-
}
|
|
215
|
-
```
|
|
209
|
+
```json
|
|
210
|
+
"alert_thresholds": {
|
|
211
|
+
"files_per_window": 20,
|
|
212
|
+
"window_seconds": 10,
|
|
213
|
+
"cooldown_seconds": 60
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## `enable_pre_warning`
|
|
220
|
+
|
|
221
|
+
- **Type**: `boolean`
|
|
222
|
+
- **Default**: `false`
|
|
223
|
+
|
|
224
|
+
Enable destructive-edit pre-warning. When enabled, the IDE extension evaluates deletion-heavy edits and removed methods/functions before they quietly land. Active warnings are persisted so `backup_status`, `dashboard`, the sidebar, and the browser dashboard can surface them.
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
"enable_pre_warning": true
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## `pre_warning_threshold`
|
|
233
|
+
|
|
234
|
+
- **Type**: `integer`
|
|
235
|
+
- **Minimum**: `1`
|
|
236
|
+
- **Maximum**: `100`
|
|
237
|
+
- **Default**: `30`
|
|
238
|
+
|
|
239
|
+
Risk threshold for triggering a pre-warning. The score is based primarily on deletion ratio, but method/function removal is treated as inherently risky and can still trigger a warning even if the line percentage is below the threshold.
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
"pre_warning_threshold": 30
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## `pre_warning_mode`
|
|
248
|
+
|
|
249
|
+
- **Type**: `string`
|
|
250
|
+
- **Allowed**: `"popup"` | `"dashboard"` | `"silent"`
|
|
251
|
+
- **Default**: `"popup"`
|
|
252
|
+
|
|
253
|
+
Controls how the warning is presented inside the IDE extension.
|
|
254
|
+
|
|
255
|
+
| Value | Description |
|
|
256
|
+
|-------|-------------|
|
|
257
|
+
| `"popup"` | Show an interactive warning with quick actions such as undo / diff review |
|
|
258
|
+
| `"dashboard"` | Do not interrupt editing; highlight the risk in dashboard, sidebar, and status surfaces |
|
|
259
|
+
| `"silent"` | Persist/log the warning without UI interruption |
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
"pre_warning_mode": "popup"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## `pre_warning_exclude_patterns`
|
|
268
|
+
|
|
269
|
+
- **Type**: `string[]` (glob patterns)
|
|
270
|
+
- **Default**: not set
|
|
271
|
+
|
|
272
|
+
Glob patterns that skip pre-warning evaluation. Useful for generated code, migrations, vendored directories, lockfiles, or other files where large deletions are expected and not actionable.
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
"pre_warning_exclude_patterns": ["generated/**", "vendor/**"]
|
|
276
|
+
```
|
|
@@ -206,10 +206,71 @@
|
|
|
206
206
|
| `window_seconds` | `integer` | `10` | 统计文件变更的滑动时间窗口(秒) |
|
|
207
207
|
| `cooldown_seconds` | `integer` | `60` | 连续告警之间的最小间隔(秒),避免噪声 |
|
|
208
208
|
|
|
209
|
-
```json
|
|
210
|
-
"alert_thresholds": {
|
|
211
|
-
"files_per_window": 20,
|
|
212
|
-
"window_seconds": 10,
|
|
213
|
-
"cooldown_seconds": 60
|
|
214
|
-
}
|
|
215
|
-
```
|
|
209
|
+
```json
|
|
210
|
+
"alert_thresholds": {
|
|
211
|
+
"files_per_window": 20,
|
|
212
|
+
"window_seconds": 10,
|
|
213
|
+
"cooldown_seconds": 60
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## `enable_pre_warning`
|
|
220
|
+
|
|
221
|
+
- **类型**:`boolean`
|
|
222
|
+
- **默认值**:`false`
|
|
223
|
+
|
|
224
|
+
开启破坏性编辑的事先预警。开启后,IDE 扩展会在高比例删行或方法/函数被移除时先做风险评估;触发后的活跃预警会持久化,`backup_status`、`dashboard`、侧边栏和浏览器仪表盘都能看到。
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
"enable_pre_warning": true
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## `pre_warning_threshold`
|
|
233
|
+
|
|
234
|
+
- **类型**:`integer`
|
|
235
|
+
- **最小值**:`1`
|
|
236
|
+
- **最大值**:`100`
|
|
237
|
+
- **默认值**:`30`
|
|
238
|
+
|
|
239
|
+
触发事先预警的风险阈值。评分主要基于删行比例,但“删掉方法/函数”会被视为天然高风险,即使删行占比没到阈值,也可能直接触发预警。
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
"pre_warning_threshold": 30
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## `pre_warning_mode`
|
|
248
|
+
|
|
249
|
+
- **类型**:`string`
|
|
250
|
+
- **可选值**:`"popup"` | `"dashboard"` | `"silent"`
|
|
251
|
+
- **默认值**:`"popup"`
|
|
252
|
+
|
|
253
|
+
控制 IDE 扩展里如何呈现预警。
|
|
254
|
+
|
|
255
|
+
| 值 | 说明 |
|
|
256
|
+
|----|------|
|
|
257
|
+
| `"popup"` | 弹出可交互提醒,支持快速撤销、查看 diff 等动作 |
|
|
258
|
+
| `"dashboard"` | 不打断编辑,只在仪表盘、侧边栏、状态面上高亮风险 |
|
|
259
|
+
| `"silent"` | 仅持久化 / 记录预警,不主动弹出 UI |
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
"pre_warning_mode": "popup"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## `pre_warning_exclude_patterns`
|
|
268
|
+
|
|
269
|
+
- **类型**:`string[]`(glob 模式)
|
|
270
|
+
- **默认值**:未设置
|
|
271
|
+
|
|
272
|
+
跳过事先预警评估的 glob 模式。适合生成代码、迁移脚本、第三方 vendored 目录、锁文件等“经常大段删除但无需人工拦截”的文件。
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
"pre_warning_exclude_patterns": ["generated/**", "vendor/**"]
|
|
276
|
+
```
|
|
@@ -34,10 +34,14 @@
|
|
|
34
34
|
"days": 30,
|
|
35
35
|
"max_count": 200
|
|
36
36
|
},
|
|
37
|
-
"proactive_alert": true,
|
|
38
|
-
"alert_thresholds": {
|
|
39
|
-
"files_per_window": 20,
|
|
40
|
-
"window_seconds": 10,
|
|
41
|
-
"cooldown_seconds": 60
|
|
42
|
-
}
|
|
43
|
-
|
|
37
|
+
"proactive_alert": true,
|
|
38
|
+
"alert_thresholds": {
|
|
39
|
+
"files_per_window": 20,
|
|
40
|
+
"window_seconds": 10,
|
|
41
|
+
"cooldown_seconds": 60
|
|
42
|
+
},
|
|
43
|
+
"enable_pre_warning": false,
|
|
44
|
+
"pre_warning_threshold": 30,
|
|
45
|
+
"pre_warning_mode": "popup",
|
|
46
|
+
"pre_warning_exclude_patterns": []
|
|
47
|
+
}
|
|
@@ -36,13 +36,36 @@
|
|
|
36
36
|
"items": { "type": "string" },
|
|
37
37
|
"description": "Additional glob patterns appended to secrets_patterns (including defaults). Use this to add patterns without losing built-in protection."
|
|
38
38
|
},
|
|
39
|
-
"pre_restore_backup": {
|
|
40
|
-
"type": "string",
|
|
41
|
-
"enum": ["always", "ask", "never"],
|
|
42
|
-
"default": "always",
|
|
43
|
-
"description": "'always' (default): automatically preserve current version before every restore. 'ask': prompt the user each time to choose whether to preserve. 'never': skip preservation entirely (not recommended)."
|
|
44
|
-
},
|
|
45
|
-
"
|
|
39
|
+
"pre_restore_backup": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"enum": ["always", "ask", "never"],
|
|
42
|
+
"default": "always",
|
|
43
|
+
"description": "'always' (default): automatically preserve current version before every restore. 'ask': prompt the user each time to choose whether to preserve. 'never': skip preservation entirely (not recommended)."
|
|
44
|
+
},
|
|
45
|
+
"enable_pre_warning": {
|
|
46
|
+
"type": "boolean",
|
|
47
|
+
"default": false,
|
|
48
|
+
"description": "Enable pre-warning before risky partial deletions in the IDE extension. Default false for backwards compatibility."
|
|
49
|
+
},
|
|
50
|
+
"pre_warning_threshold": {
|
|
51
|
+
"type": "integer",
|
|
52
|
+
"minimum": 1,
|
|
53
|
+
"maximum": 100,
|
|
54
|
+
"default": 30,
|
|
55
|
+
"description": "Deletion risk percentage threshold that triggers a pre-warning. Method/function removal may trigger even when below this threshold."
|
|
56
|
+
},
|
|
57
|
+
"pre_warning_mode": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"enum": ["popup", "dashboard", "silent"],
|
|
60
|
+
"default": "popup",
|
|
61
|
+
"description": "'popup': show an immediate IDE warning with actions. 'dashboard': mark the project in dashboard/status UI only. 'silent': record the warning in history without surfacing UI alerts."
|
|
62
|
+
},
|
|
63
|
+
"pre_warning_exclude_patterns": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"items": { "type": "string" },
|
|
66
|
+
"description": "Glob patterns excluded from pre-warning checks. Useful for generated files or intentionally volatile code."
|
|
67
|
+
},
|
|
68
|
+
"retention": {
|
|
46
69
|
"type": "object",
|
|
47
70
|
"description": "Controls automatic cleanup of old shadow-copy snapshots.",
|
|
48
71
|
"properties": {
|
|
@@ -60,9 +60,16 @@ const I18N = {
|
|
|
60
60
|
'alert.action.deleted': 'Deleted',
|
|
61
61
|
'alert.action.renamed': 'Renamed',
|
|
62
62
|
'alert.breakdown': '{added} added, {modified} modified, {deleted} deleted',
|
|
63
|
-
'alert.suggestion': 'Check recent changes and consider creating a manual snapshot',
|
|
64
|
-
'alert.viewFiles': 'View file details ({n} files)',
|
|
65
|
-
'
|
|
63
|
+
'alert.suggestion': 'Check recent changes and consider creating a manual snapshot',
|
|
64
|
+
'alert.viewFiles': 'View file details ({n} files)',
|
|
65
|
+
'preWarning.title': 'Pre-Warning',
|
|
66
|
+
'preWarning.none': 'No destructive edit risk detected',
|
|
67
|
+
'preWarning.active': 'Delete Risk',
|
|
68
|
+
'preWarning.file': 'File',
|
|
69
|
+
'preWarning.risk': 'Risk',
|
|
70
|
+
'preWarning.methods': 'Methods removed',
|
|
71
|
+
'preWarning.suggestion': 'Review this deletion before applying or restore from the latest snapshot',
|
|
72
|
+
'modal.alertFiles': 'Alert File Details',
|
|
66
73
|
'modal.col.restore': 'Restore',
|
|
67
74
|
'modal.copyRestore': 'Copy cmd',
|
|
68
75
|
'modal.restorePreDelete':'Restore pre-delete',
|
|
@@ -170,8 +177,9 @@ const I18N = {
|
|
|
170
177
|
'issue.disk_critically_low': 'Disk space critically low ({gb} GB free)',
|
|
171
178
|
'issue.disk_low': 'Disk space low ({gb} GB free)',
|
|
172
179
|
'issue.git_backup_stale': 'Last git backup is stale ({rel})',
|
|
173
|
-
'issue.active_alert': 'Active alert: {type} — {count} files in {window}s',
|
|
174
|
-
'issue.
|
|
180
|
+
'issue.active_alert': 'Active alert: {type} — {count} files in {window}s',
|
|
181
|
+
'issue.pre_warning_active': 'Pre-warning active: {summary}',
|
|
182
|
+
'issue.alert_high_velocity': 'High volume of file changes detected. Consider reviewing recent modifications and creating a manual snapshot.',
|
|
175
183
|
|
|
176
184
|
'check.Git installed': 'Git installed',
|
|
177
185
|
'check.Git repository': 'Git repository',
|
|
@@ -288,9 +296,16 @@ const I18N = {
|
|
|
288
296
|
'alert.action.deleted': '删除',
|
|
289
297
|
'alert.action.renamed': '重命名',
|
|
290
298
|
'alert.breakdown': '新增 {added} · 修改 {modified} · 删除 {deleted}',
|
|
291
|
-
'alert.suggestion': '建议检查近期变更,并考虑手动创建快照',
|
|
292
|
-
'alert.viewFiles': '查看文件详情({n} 个文件)',
|
|
293
|
-
'
|
|
299
|
+
'alert.suggestion': '建议检查近期变更,并考虑手动创建快照',
|
|
300
|
+
'alert.viewFiles': '查看文件详情({n} 个文件)',
|
|
301
|
+
'preWarning.title': '事先预警',
|
|
302
|
+
'preWarning.none': '未检测到破坏性编辑风险',
|
|
303
|
+
'preWarning.active': '删除风险',
|
|
304
|
+
'preWarning.file': '文件',
|
|
305
|
+
'preWarning.risk': '风险',
|
|
306
|
+
'preWarning.methods': '移除的方法数',
|
|
307
|
+
'preWarning.suggestion': '建议在应用前检查这次删除,或直接从最新快照恢复',
|
|
308
|
+
'modal.alertFiles': '告警文件详情',
|
|
294
309
|
'modal.col.restore': '恢复',
|
|
295
310
|
'modal.copyRestore': '复制命令',
|
|
296
311
|
'modal.restorePreDelete':'恢复删除前',
|
|
@@ -399,7 +414,8 @@ const I18N = {
|
|
|
399
414
|
'issue.disk_low': '磁盘空间不足({gb} GB 可用)',
|
|
400
415
|
'issue.git_backup_stale': '最近 Git 备份已过时({rel})',
|
|
401
416
|
'issue.active_alert': '活跃告警:{type}——{count} 个文件在 {window} 秒内变更',
|
|
402
|
-
'issue.
|
|
417
|
+
'issue.pre_warning_active': '事先预警生效:{summary}',
|
|
418
|
+
'issue.alert_high_velocity': '检测到大量文件变更,建议检查最近修改并手动创建快照。',
|
|
403
419
|
|
|
404
420
|
'check.Git installed': 'Git 安装状态',
|
|
405
421
|
'check.Git repository': 'Git 仓库',
|
|
@@ -557,8 +573,9 @@ const ISSUE_PATTERNS = [
|
|
|
557
573
|
{ re: /^Disk space low \((.+?) GB free\)$/, key: 'issue.disk_low', extract: ['gb'] },
|
|
558
574
|
{ re: /^Last git backup is stale \((.+?)\)$/, key: 'issue.git_backup_stale', extract: ['rel'] },
|
|
559
575
|
{ re: /^Active alert: (.+?) — (\d+) files in (\d+)s$/, key: 'issue.active_alert', extract: ['type', 'count', 'window'] },
|
|
560
|
-
{ re: /^
|
|
561
|
-
|
|
576
|
+
{ re: /^Pre-warning active: (.+)$/, key: 'issue.pre_warning_active', extract: ['summary'] },
|
|
577
|
+
{ re: /^High volume of file changes/, key: 'issue.alert_high_velocity' },
|
|
578
|
+
];
|
|
562
579
|
|
|
563
580
|
function translateIssue(text) {
|
|
564
581
|
for (const p of ISSUE_PATTERNS) {
|
|
@@ -850,13 +867,14 @@ function renderAll() {
|
|
|
850
867
|
|
|
851
868
|
/* ── Rendering: Overview ──────────────────────────────────── */
|
|
852
869
|
|
|
853
|
-
function renderOverview(d) {
|
|
854
|
-
renderHealthCard(d.health);
|
|
855
|
-
renderGitBackupCard(d.lastBackup);
|
|
856
|
-
renderShadowBackupCard(d.lastBackup);
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
870
|
+
function renderOverview(d) {
|
|
871
|
+
renderHealthCard(d.health);
|
|
872
|
+
renderGitBackupCard(d.lastBackup);
|
|
873
|
+
renderShadowBackupCard(d.lastBackup);
|
|
874
|
+
renderPreWarningCard(d.preWarnings);
|
|
875
|
+
renderWatcherCard(d.watcher);
|
|
876
|
+
renderAlertCard(d.alerts);
|
|
877
|
+
}
|
|
860
878
|
|
|
861
879
|
function renderHealthCard(health) {
|
|
862
880
|
const el = $('#card-health');
|
|
@@ -889,9 +907,9 @@ function renderGitBackupCard(lastBackup) {
|
|
|
889
907
|
`;
|
|
890
908
|
}
|
|
891
909
|
|
|
892
|
-
function renderShadowBackupCard(lastBackup) {
|
|
893
|
-
const el = $('#card-shadow-backup');
|
|
894
|
-
const s = lastBackup?.shadow;
|
|
910
|
+
function renderShadowBackupCard(lastBackup) {
|
|
911
|
+
const el = $('#card-shadow-backup');
|
|
912
|
+
const s = lastBackup?.shadow;
|
|
895
913
|
if (!s) {
|
|
896
914
|
el.innerHTML = `<div class="card-label">${t('shadowBackup.title')}</div><div class="card-empty">${t('shadowBackup.none')}</div>`;
|
|
897
915
|
return;
|
|
@@ -899,12 +917,40 @@ function renderShadowBackupCard(lastBackup) {
|
|
|
899
917
|
el.innerHTML = `
|
|
900
918
|
<div class="card-label">${t('shadowBackup.title')}</div>
|
|
901
919
|
<div class="card-value">${esc(relativeTime(s.timestamp))}</div>
|
|
902
|
-
<div class="card-detail text-muted text-sm">${esc(formatTime(s.timestamp))}</div>
|
|
903
|
-
`;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
function
|
|
907
|
-
const el = $('#card-
|
|
920
|
+
<div class="card-detail text-muted text-sm">${esc(formatTime(s.timestamp))}</div>
|
|
921
|
+
`;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function renderPreWarningCard(preWarnings) {
|
|
925
|
+
const el = $('#card-prewarning');
|
|
926
|
+
if (!preWarnings?.active) {
|
|
927
|
+
el.innerHTML = `
|
|
928
|
+
<div class="card-label">${t('preWarning.title')}</div>
|
|
929
|
+
<div class="card-status"><span class="status-dot status-healthy"></span><span>${t('preWarning.none')}</span></div>
|
|
930
|
+
`;
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const warning = preWarnings.latest || {};
|
|
935
|
+
const file = warning.file || '-';
|
|
936
|
+
const risk = warning.riskPercent !== undefined ? `${warning.riskPercent}%` : '-';
|
|
937
|
+
const methods = warning.removedMethodCount || 0;
|
|
938
|
+
|
|
939
|
+
el.innerHTML = `
|
|
940
|
+
<div class="card-label">${t('preWarning.title')}</div>
|
|
941
|
+
<div class="card-status"><span class="status-dot status-warning"></span><span class="status-text status-warning">${t('preWarning.active')}</span></div>
|
|
942
|
+
<div class="alert-details">
|
|
943
|
+
<div class="alert-detail-row"><span class="alert-detail-label">${t('preWarning.file')}</span><span class="text-mono">${esc(file)}</span></div>
|
|
944
|
+
<div class="alert-detail-row"><span class="alert-detail-label">${t('preWarning.risk')}</span><span>${esc(risk)}</span></div>
|
|
945
|
+
${methods > 0 ? `<div class="alert-detail-row"><span class="alert-detail-label">${t('preWarning.methods')}</span><span>${methods}</span></div>` : ''}
|
|
946
|
+
<div class="alert-detail-row alert-breakdown text-sm">${esc(warning.summary || t('preWarning.suggestion'))}</div>
|
|
947
|
+
<div class="alert-detail-row alert-suggestion text-sm text-muted">${t('preWarning.suggestion')}</div>
|
|
948
|
+
</div>
|
|
949
|
+
`;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function renderWatcherCard(watcher) {
|
|
953
|
+
const el = $('#card-watcher');
|
|
908
954
|
let st = 'stopped';
|
|
909
955
|
if (watcher?.running) st = 'running';
|
|
910
956
|
else if (watcher?.stale) st = 'stale';
|
|
@@ -44,13 +44,14 @@
|
|
|
44
44
|
<section id="screen-overview" class="screen">
|
|
45
45
|
<h2 class="section-title" data-i18n="overview.title">Overview</h2>
|
|
46
46
|
<div id="overview-grid" class="card-grid">
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
</
|
|
47
|
+
<div id="card-health" class="card card-health"><div class="skeleton-block"></div></div>
|
|
48
|
+
<div id="card-git-backup" class="card"><div class="skeleton-block"></div></div>
|
|
49
|
+
<div id="card-shadow-backup" class="card"><div class="skeleton-block"></div></div>
|
|
50
|
+
<div id="card-prewarning" class="card"><div class="skeleton-block"></div></div>
|
|
51
|
+
<div id="card-watcher" class="card"><div class="skeleton-block"></div></div>
|
|
52
|
+
<div id="card-alert" class="card"><div class="skeleton-block"></div></div>
|
|
53
|
+
</div>
|
|
54
|
+
</section>
|
|
54
55
|
|
|
55
56
|
<!-- Screen 2: Backups & Recovery ─────────────────────── -->
|
|
56
57
|
<section id="screen-backups" class="screen">
|
|
@@ -22,6 +22,45 @@ function isProcessAlive(pid) {
|
|
|
22
22
|
catch { return false; }
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Ignore fs.watch events that originate inside the Git directory (including our snapshots).
|
|
27
|
+
* This is the **full** fix for the feedback loop: snapshot writes under `.git/` must never
|
|
28
|
+
* schedule another backup. No time-based cooldown — rely entirely on path semantics.
|
|
29
|
+
*
|
|
30
|
+
* On Windows, `filename` is often relative to the repo root but WITHOUT a `.git/` prefix
|
|
31
|
+
* (e.g. `HEAD`, `objects/ab/...`, `refs/guard/auto-backup`).
|
|
32
|
+
*/
|
|
33
|
+
const GIT_ONLY_TOP_NAMES = new Set([
|
|
34
|
+
'HEAD', 'FETCH_HEAD', 'ORIG_HEAD', 'MERGE_HEAD', 'COMMIT_EDITMSG', 'index', 'packed-refs',
|
|
35
|
+
'cursor-guard-index', 'cursor-guard-index.lock', 'cursor-guard.lock',
|
|
36
|
+
'refs', 'objects', 'logs', 'hooks', 'info', 'worktrees', 'modules', 'description',
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
function shouldIgnoreFsWatchEvent(projectDir, filename, realGitDir) {
|
|
40
|
+
if (!filename) return true;
|
|
41
|
+
const norm = String(filename).replace(/\\/g, '/');
|
|
42
|
+
if (norm.startsWith('.git/') || norm === '.git') return true;
|
|
43
|
+
if (norm.startsWith('.cursor-guard-backup')) return true;
|
|
44
|
+
|
|
45
|
+
const gitRoot = realGitDir || path.join(projectDir, '.git');
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
if (norm.includes('/') || GIT_ONLY_TOP_NAMES.has(norm)) {
|
|
49
|
+
const candidate = path.join(gitRoot, norm);
|
|
50
|
+
if (fs.existsSync(candidate)) return true;
|
|
51
|
+
}
|
|
52
|
+
} catch { /* ignore */ }
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const abs = path.resolve(projectDir, filename);
|
|
56
|
+
const gitDirAbs = path.resolve(gitRoot);
|
|
57
|
+
const rel = path.relative(gitDirAbs, abs);
|
|
58
|
+
if (rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))) return true;
|
|
59
|
+
} catch { /* ignore */ }
|
|
60
|
+
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
25
64
|
// ── Main ────────────────────────────────────────────────────────
|
|
26
65
|
|
|
27
66
|
async function runBackup(projectDir, intervalOverride, opts = {}) {
|
|
@@ -323,9 +362,8 @@ async function runBackup(projectDir, intervalOverride, opts = {}) {
|
|
|
323
362
|
|
|
324
363
|
watcher.on('change', (_eventType, filename) => {
|
|
325
364
|
if (!filename) return;
|
|
365
|
+
if (shouldIgnoreFsWatchEvent(projectDir, filename, gDir)) return;
|
|
326
366
|
const f = filename.replace(/\\/g, '/');
|
|
327
|
-
if (f.startsWith('.git/') || f.startsWith('.git\\')) return;
|
|
328
|
-
if (f.startsWith('.cursor-guard-backup')) return;
|
|
329
367
|
if (f === '.cursor-guard.json') {
|
|
330
368
|
hotReloadConfig();
|
|
331
369
|
return;
|