cursor-guard 1.0.1 → 1.2.0
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 +103 -84
- package/README.zh-CN.md +237 -0
- package/SKILL.md +175 -8
- package/package.json +2 -1
- package/references/auto-backup.ps1 +121 -57
- package/references/recovery.md +135 -7
package/README.md
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
# Cursor Guard
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/cursor-guard)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
3
6
|
Protects your code from accidental AI overwrite or deletion in [Cursor](https://cursor.com).
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
**[中文文档](README.zh-CN.md)**
|
|
6
9
|
|
|
7
10
|
---
|
|
8
11
|
|
|
9
|
-
## What It Does
|
|
12
|
+
## What It Does
|
|
10
13
|
|
|
11
14
|
When Cursor's AI agent edits your files, there's a risk of accidental overwrites, deletions, or loss of work. **Cursor Guard** enforces a safety protocol:
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **Secrets filtering / 敏感文件过滤** — Sensitive files (`.env`, keys, certificates) are auto-excluded from backups / `.env`、密钥、证书等敏感文件自动排除
|
|
21
|
-
- **Auto-backup script / 自动备份脚本** — A PowerShell watcher that periodically snapshots to a dedicated Git branch without disturbing your working tree / 定期快照到独立 Git 分支,不干扰工作区
|
|
16
|
+
- **Mandatory pre-write snapshots** — Git commit or shadow copy before any destructive operation
|
|
17
|
+
- **Read before Write** — The agent must read a file before overwriting it
|
|
18
|
+
- **Review before apply** — Diff previews and explicit confirmation for dangerous ops
|
|
19
|
+
- **Deterministic recovery** — Clear priority-ordered recovery paths (Git → shadow copies → conversation context → editor history)
|
|
20
|
+
- **Configurable scope** — Protect only what matters via `.cursor-guard.json`
|
|
21
|
+
- **Secrets filtering** — Sensitive files (`.env`, keys, certificates) are auto-excluded from backups
|
|
22
|
+
- **Auto-backup script** — A PowerShell watcher that periodically snapshots to a dedicated Git branch without disturbing your working tree
|
|
22
23
|
|
|
23
24
|
---
|
|
24
25
|
|
|
25
|
-
## Installation
|
|
26
|
+
## Installation
|
|
26
27
|
|
|
27
|
-
### Method 1: npm
|
|
28
|
+
### Method 1: npm
|
|
28
29
|
|
|
29
30
|
```bash
|
|
30
31
|
npm install cursor-guard
|
|
@@ -32,106 +33,98 @@ npm install cursor-guard
|
|
|
32
33
|
|
|
33
34
|
After installation, copy the skill files to your Cursor skills directory:
|
|
34
35
|
|
|
35
|
-
安装后,将技能文件复制到 Cursor 技能目录:
|
|
36
|
-
|
|
37
36
|
**Windows (PowerShell):**
|
|
38
37
|
|
|
39
38
|
```powershell
|
|
40
|
-
# Global
|
|
39
|
+
# Global (all projects)
|
|
41
40
|
Copy-Item -Recurse node_modules/cursor-guard "$env:USERPROFILE/.cursor/skills/cursor-guard"
|
|
42
41
|
|
|
43
|
-
# Per-project
|
|
42
|
+
# Per-project (current project only)
|
|
44
43
|
Copy-Item -Recurse node_modules/cursor-guard .cursor/skills/cursor-guard
|
|
45
44
|
```
|
|
46
45
|
|
|
47
46
|
**macOS / Linux:**
|
|
48
47
|
|
|
49
48
|
```bash
|
|
50
|
-
# Global
|
|
49
|
+
# Global
|
|
51
50
|
cp -r node_modules/cursor-guard ~/.cursor/skills/cursor-guard
|
|
52
51
|
|
|
53
|
-
# Per-project
|
|
52
|
+
# Per-project
|
|
54
53
|
cp -r node_modules/cursor-guard .cursor/skills/cursor-guard
|
|
55
54
|
```
|
|
56
55
|
|
|
57
56
|
After copying, you can remove the npm dependency if you don't need it in `node_modules`:
|
|
58
57
|
|
|
59
|
-
复制完成后,如果不需要保留在 `node_modules` 中,可以卸载:
|
|
60
|
-
|
|
61
58
|
```bash
|
|
62
59
|
npm uninstall cursor-guard
|
|
63
60
|
```
|
|
64
61
|
|
|
65
|
-
### Method 2: Git clone
|
|
62
|
+
### Method 2: Git clone
|
|
66
63
|
|
|
67
64
|
```bash
|
|
68
|
-
# Global
|
|
65
|
+
# Global
|
|
69
66
|
git clone https://github.com/zhangqiang8vipp/cursor-guard.git ~/.cursor/skills/cursor-guard
|
|
70
67
|
|
|
71
|
-
# Per-project
|
|
68
|
+
# Per-project
|
|
72
69
|
git clone https://github.com/zhangqiang8vipp/cursor-guard.git .cursor/skills/cursor-guard
|
|
73
70
|
```
|
|
74
71
|
|
|
75
|
-
### Method 3: Manual download
|
|
72
|
+
### Method 3: Manual download
|
|
76
73
|
|
|
77
74
|
Download from [GitHub Releases](https://github.com/zhangqiang8vipp/cursor-guard/releases) and extract to:
|
|
78
75
|
|
|
79
|
-
从 [GitHub Releases](https://github.com/zhangqiang8vipp/cursor-guard/releases) 下载并解压到:
|
|
80
|
-
|
|
81
76
|
```
|
|
82
|
-
~/.cursor/skills/cursor-guard/
|
|
83
|
-
<project-root>/.cursor/skills/cursor-guard/
|
|
77
|
+
~/.cursor/skills/cursor-guard/ # Global
|
|
78
|
+
<project-root>/.cursor/skills/cursor-guard/ # Per-project
|
|
84
79
|
```
|
|
85
80
|
|
|
86
|
-
### Verify Installation
|
|
81
|
+
### Verify Installation
|
|
87
82
|
|
|
88
|
-
After installation, your directory structure should look like this
|
|
83
|
+
After installation, your directory structure should look like this:
|
|
89
84
|
|
|
90
85
|
```
|
|
91
86
|
.cursor/skills/cursor-guard/
|
|
92
|
-
├── SKILL.md # AI agent instructions
|
|
87
|
+
├── SKILL.md # AI agent instructions
|
|
93
88
|
├── README.md
|
|
94
89
|
├── LICENSE
|
|
95
90
|
└── references/
|
|
96
|
-
├── auto-backup.ps1 # Auto-backup script
|
|
97
|
-
├── recovery.md # Recovery commands
|
|
98
|
-
├── cursor-guard.example.json # Example config
|
|
99
|
-
└── cursor-guard.schema.json # Config schema
|
|
91
|
+
├── auto-backup.ps1 # Auto-backup script
|
|
92
|
+
├── recovery.md # Recovery commands
|
|
93
|
+
├── cursor-guard.example.json # Example config
|
|
94
|
+
└── cursor-guard.schema.json # Config schema
|
|
100
95
|
```
|
|
101
96
|
|
|
102
|
-
The skill activates automatically when the AI agent detects risky operations
|
|
103
|
-
|
|
104
|
-
技能会在 AI 代理检测到高风险操作(文件编辑、删除、重命名)或你提到恢复相关词汇时自动激活。无需其他设置,安装即生效。
|
|
97
|
+
The skill activates automatically when the AI agent detects risky operations or when you mention recovery-related terms. No extra setup needed.
|
|
105
98
|
|
|
106
99
|
---
|
|
107
100
|
|
|
108
|
-
## Quick Start
|
|
101
|
+
## Quick Start
|
|
109
102
|
|
|
110
|
-
1. **Install the skill** using any method above
|
|
103
|
+
1. **Install the skill** using any method above
|
|
111
104
|
|
|
112
|
-
2. **Open Cursor** and start an Agent conversation
|
|
105
|
+
2. **Open Cursor** and start an Agent conversation
|
|
113
106
|
|
|
114
|
-
3. **The skill works automatically** — when the AI agent tries to edit files, it will:
|
|
115
|
-
- Create a Git snapshot before writing
|
|
116
|
-
- Read files before overwriting
|
|
117
|
-
- Show diff previews for dangerous operations
|
|
118
|
-
- Report a status block after each protected operation
|
|
107
|
+
3. **The skill works automatically** — when the AI agent tries to edit files, it will:
|
|
108
|
+
- Create a Git snapshot before writing
|
|
109
|
+
- Read files before overwriting
|
|
110
|
+
- Show diff previews for dangerous operations
|
|
111
|
+
- Report a status block after each protected operation
|
|
119
112
|
|
|
120
|
-
4. **(Optional) Add project config** to customize protection scope
|
|
113
|
+
4. **(Optional) Add project config** to customize protection scope:
|
|
121
114
|
|
|
122
115
|
```bash
|
|
123
116
|
cp .cursor/skills/cursor-guard/references/cursor-guard.example.json .cursor-guard.json
|
|
124
117
|
```
|
|
125
118
|
|
|
126
|
-
5. **(Optional) Run auto-backup** in a separate terminal
|
|
119
|
+
5. **(Optional) Run auto-backup** in a separate terminal:
|
|
127
120
|
|
|
128
121
|
```powershell
|
|
129
122
|
.\auto-backup.ps1 -Path "D:\MyProject"
|
|
130
123
|
```
|
|
131
124
|
|
|
132
|
-
### Project Configuration
|
|
125
|
+
### Project Configuration
|
|
133
126
|
|
|
134
|
-
Edit `.cursor-guard.json` to define which files to protect
|
|
127
|
+
Edit `.cursor-guard.json` to define which files to protect:
|
|
135
128
|
|
|
136
129
|
```json
|
|
137
130
|
{
|
|
@@ -145,74 +138,100 @@ Edit `.cursor-guard.json` to define which files to protect / 编辑 `.cursor-gua
|
|
|
145
138
|
|
|
146
139
|
---
|
|
147
140
|
|
|
148
|
-
## Auto-Backup Script
|
|
141
|
+
## Auto-Backup Script
|
|
149
142
|
|
|
150
143
|
Run in a separate terminal while working in Cursor:
|
|
151
144
|
|
|
152
|
-
在使用 Cursor 时,在**单独的终端窗口**中运行:
|
|
153
|
-
|
|
154
145
|
```powershell
|
|
155
146
|
.\auto-backup.ps1 -Path "D:\MyProject"
|
|
156
147
|
|
|
157
|
-
# Custom interval (default 60s)
|
|
148
|
+
# Custom interval (default 60s):
|
|
158
149
|
.\auto-backup.ps1 -Path "D:\MyProject" -IntervalSeconds 30
|
|
159
150
|
```
|
|
160
151
|
|
|
161
152
|
The script uses Git plumbing commands to snapshot to `cursor-guard/auto-backup` branch — it never switches branches or touches your working index.
|
|
162
153
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
> **Note / 注意**: Run this script in a separate PowerShell window, NOT inside Cursor's integrated terminal. Cursor's terminal may interfere with Git plumbing commands.
|
|
166
|
-
>
|
|
167
|
-
> 请在独立的 PowerShell 窗口中运行此脚本,不要在 Cursor 的集成终端中运行,因为 Cursor 终端可能干扰 Git 底层命令。
|
|
154
|
+
> **Note**: Run this script in a separate PowerShell window, NOT inside Cursor's integrated terminal. Cursor's terminal may interfere with Git plumbing commands.
|
|
168
155
|
|
|
169
156
|
---
|
|
170
157
|
|
|
171
|
-
## Recovery
|
|
158
|
+
## Recovery
|
|
159
|
+
|
|
160
|
+
If something goes wrong, just tell the AI agent in natural language:
|
|
172
161
|
|
|
173
|
-
|
|
162
|
+
### By time
|
|
174
163
|
|
|
175
|
-
|
|
164
|
+
> "restore to 5 minutes ago"
|
|
165
|
+
> "go back to yesterday's version"
|
|
166
|
+
> "restore to 3pm today"
|
|
167
|
+
|
|
168
|
+
### By version
|
|
169
|
+
|
|
170
|
+
> "undo the last change"
|
|
171
|
+
> "go back 3 versions"
|
|
172
|
+
> "restore to the previous version"
|
|
173
|
+
|
|
174
|
+
### By file
|
|
175
|
+
|
|
176
|
+
> "restore src/app.py to 10 minutes ago"
|
|
177
|
+
> "restore src/app.py to the previous version"
|
|
178
|
+
|
|
179
|
+
The agent will automatically search Git history and auto-backup snapshots, show you matching versions to choose from, and restore after your confirmation.
|
|
180
|
+
|
|
181
|
+
### Recovery priority
|
|
176
182
|
|
|
177
183
|
1. **Git** — `git restore`, `git reset`, `git reflog`
|
|
178
|
-
2. **
|
|
179
|
-
3. **
|
|
180
|
-
4. **
|
|
184
|
+
2. **Auto-backup branch** — `cursor-guard/auto-backup`
|
|
185
|
+
3. **Shadow copies** — `.cursor-guard-backup/<timestamp>/`
|
|
186
|
+
4. **Conversation context** — Original file content captured by agent Read calls
|
|
187
|
+
5. **Editor history** — VS Code/Cursor Timeline (auxiliary)
|
|
181
188
|
|
|
182
|
-
See [references/recovery.md](references/recovery.md) for detailed commands
|
|
189
|
+
See [references/recovery.md](references/recovery.md) for detailed commands.
|
|
183
190
|
|
|
184
191
|
---
|
|
185
192
|
|
|
186
|
-
## Trigger Keywords
|
|
193
|
+
## Trigger Keywords
|
|
187
194
|
|
|
188
|
-
The skill activates on these signals
|
|
195
|
+
The skill activates on these signals:
|
|
189
196
|
|
|
190
|
-
- File edits, deletes, renames by the AI agent
|
|
191
|
-
- Recovery requests
|
|
192
|
-
-
|
|
197
|
+
- File edits, deletes, renames by the AI agent
|
|
198
|
+
- Recovery requests: "rollback", "undo", "recover", "restore"
|
|
199
|
+
- Time-based recovery: "restore to N minutes ago", "go back to yesterday"
|
|
200
|
+
- Version-based recovery: "previous version", "go back N versions"
|
|
201
|
+
- History issues: checkpoints missing, Timeline not working, save failures
|
|
193
202
|
|
|
194
203
|
---
|
|
195
204
|
|
|
196
|
-
## Files
|
|
205
|
+
## Files
|
|
197
206
|
|
|
198
|
-
| File
|
|
207
|
+
| File | Purpose |
|
|
199
208
|
|------|---------|
|
|
200
|
-
| `SKILL.md` | Main skill instructions for the AI agent
|
|
201
|
-
| `references/auto-backup.ps1` | PowerShell auto-backup watcher script
|
|
202
|
-
| `references/recovery.md` | Recovery command templates
|
|
203
|
-
| `references/cursor-guard.example.json` | Example project configuration
|
|
204
|
-
| `references/cursor-guard.schema.json` | JSON Schema for config validation
|
|
209
|
+
| `SKILL.md` | Main skill instructions for the AI agent |
|
|
210
|
+
| `references/auto-backup.ps1` | PowerShell auto-backup watcher script |
|
|
211
|
+
| `references/recovery.md` | Recovery command templates |
|
|
212
|
+
| `references/cursor-guard.example.json` | Example project configuration |
|
|
213
|
+
| `references/cursor-guard.schema.json` | JSON Schema for config validation |
|
|
205
214
|
|
|
206
215
|
---
|
|
207
216
|
|
|
208
|
-
##
|
|
217
|
+
## Known Limitations
|
|
218
|
+
|
|
219
|
+
- **Binary files**: Git diffs and snapshots work on text files. Binary files (images, compiled assets) are stored but cannot be meaningfully diffed or partially restored.
|
|
220
|
+
- **Untracked files**: Files never committed to Git cannot be recovered from Git history. Shadow copy (`backup_strategy: "shadow"` or `"both"`) is the only safety net for untracked files.
|
|
221
|
+
- **Concurrent agents**: If multiple AI agent threads write to the same file simultaneously, snapshots cannot prevent race conditions. Avoid parallel edits to the same file.
|
|
222
|
+
- **External tools modifying the index**: Tools that alter Git's index (e.g. other Git GUIs, IDE Git integrations) while `auto-backup.ps1` is running may conflict. The script uses a temporary index to minimize this, but edge cases exist.
|
|
223
|
+
- **Git worktree**: The auto-backup script supports worktree layouts (`git rev-parse --git-dir`), but has not been tested with all exotic setups (e.g. `--separate-git-dir`).
|
|
224
|
+
- **Cursor terminal interference**: Cursor's integrated terminal injects `--trailer` flags into `git commit` commands, which breaks plumbing commands like `commit-tree`. Always run `auto-backup.ps1` in a **separate PowerShell window**.
|
|
225
|
+
- **Large repos**: For very large repositories, `git add -A` in the backup loop may be slow. Use `protect` patterns in `.cursor-guard.json` to narrow scope.
|
|
226
|
+
|
|
227
|
+
## Requirements
|
|
209
228
|
|
|
210
|
-
- **Git** — for primary backup strategy
|
|
211
|
-
- **PowerShell 5.1+** — for auto-backup script (Windows built-in)
|
|
212
|
-
- **Cursor IDE** — with Agent mode enabled
|
|
229
|
+
- **Git** — for primary backup strategy
|
|
230
|
+
- **PowerShell 5.1+** — for auto-backup script (Windows built-in)
|
|
231
|
+
- **Cursor IDE** — with Agent mode enabled
|
|
213
232
|
|
|
214
233
|
---
|
|
215
234
|
|
|
216
|
-
## License
|
|
235
|
+
## License
|
|
217
236
|
|
|
218
237
|
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Cursor Guard
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/cursor-guard)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
保护你的代码免受 [Cursor](https://cursor.com) AI 代理意外覆写或删除。
|
|
7
|
+
|
|
8
|
+
**[English](README.md)**
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 功能介绍
|
|
13
|
+
|
|
14
|
+
当 Cursor 的 AI 代理编辑你的文件时,可能会意外覆盖、删除或丢失代码。**Cursor Guard** 强制执行一套安全协议:
|
|
15
|
+
|
|
16
|
+
- **强制写前快照** — 在任何破坏性操作前自动 Git 提交或影子拷贝
|
|
17
|
+
- **先读后写** — 代理必须先读取文件内容,才能覆写
|
|
18
|
+
- **预览再执行** — 危险操作前展示 diff 预览并要求确认
|
|
19
|
+
- **确定性恢复** — 按优先级的恢复路径(Git → 影子拷贝 → 对话上下文 → 编辑器历史)
|
|
20
|
+
- **可配置保护范围** — 通过 `.cursor-guard.json` 配置文件只保护你关心的文件
|
|
21
|
+
- **敏感文件过滤** — `.env`、密钥、证书等敏感文件自动排除备份
|
|
22
|
+
- **自动备份脚本** — 定期快照到独立 Git 分支,不干扰工作区
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 安装
|
|
27
|
+
|
|
28
|
+
### 方式一:npm 安装
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install cursor-guard
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
安装后,将技能文件复制到 Cursor 技能目录:
|
|
35
|
+
|
|
36
|
+
**Windows (PowerShell):**
|
|
37
|
+
|
|
38
|
+
```powershell
|
|
39
|
+
# 全局安装(所有项目生效)
|
|
40
|
+
Copy-Item -Recurse node_modules/cursor-guard "$env:USERPROFILE/.cursor/skills/cursor-guard"
|
|
41
|
+
|
|
42
|
+
# 项目级安装(仅当前项目生效)
|
|
43
|
+
Copy-Item -Recurse node_modules/cursor-guard .cursor/skills/cursor-guard
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**macOS / Linux:**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 全局安装
|
|
50
|
+
cp -r node_modules/cursor-guard ~/.cursor/skills/cursor-guard
|
|
51
|
+
|
|
52
|
+
# 项目级安装
|
|
53
|
+
cp -r node_modules/cursor-guard .cursor/skills/cursor-guard
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
复制完成后,如果不需要保留在 `node_modules` 中,可以卸载:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm uninstall cursor-guard
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 方式二:Git 克隆
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# 全局安装
|
|
66
|
+
git clone https://github.com/zhangqiang8vipp/cursor-guard.git ~/.cursor/skills/cursor-guard
|
|
67
|
+
|
|
68
|
+
# 项目级安装
|
|
69
|
+
git clone https://github.com/zhangqiang8vipp/cursor-guard.git .cursor/skills/cursor-guard
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 方式三:手动下载
|
|
73
|
+
|
|
74
|
+
从 [GitHub Releases](https://github.com/zhangqiang8vipp/cursor-guard/releases) 下载并解压到:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
~/.cursor/skills/cursor-guard/ # 全局
|
|
78
|
+
<项目根目录>/.cursor/skills/cursor-guard/ # 项目级
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 验证安装
|
|
82
|
+
|
|
83
|
+
安装后目录结构应如下所示:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
.cursor/skills/cursor-guard/
|
|
87
|
+
├── SKILL.md # AI 代理指令
|
|
88
|
+
├── README.md
|
|
89
|
+
├── LICENSE
|
|
90
|
+
└── references/
|
|
91
|
+
├── auto-backup.ps1 # 自动备份脚本
|
|
92
|
+
├── recovery.md # 恢复命令模板
|
|
93
|
+
├── cursor-guard.example.json # 示例配置
|
|
94
|
+
└── cursor-guard.schema.json # 配置 Schema
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
技能会在 AI 代理检测到高风险操作(文件编辑、删除、重命名)或你提到恢复相关词汇时自动激活。无需其他设置,安装即生效。
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 快速上手
|
|
102
|
+
|
|
103
|
+
1. **安装技能** — 用以上任意方式安装
|
|
104
|
+
|
|
105
|
+
2. **打开 Cursor** — 开始一个 Agent 对话
|
|
106
|
+
|
|
107
|
+
3. **技能自动生效** — 当 AI 代理尝试编辑文件时,会自动:
|
|
108
|
+
- 写入前创建 Git 快照
|
|
109
|
+
- 覆写前先读取文件
|
|
110
|
+
- 危险操作前展示 diff 预览
|
|
111
|
+
- 每次受保护操作后报告状态
|
|
112
|
+
|
|
113
|
+
4. **(可选)添加项目配置** — 自定义保护范围:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
cp .cursor/skills/cursor-guard/references/cursor-guard.example.json .cursor-guard.json
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
5. **(可选)运行自动备份** — 在独立终端运行:
|
|
120
|
+
|
|
121
|
+
```powershell
|
|
122
|
+
.\auto-backup.ps1 -Path "D:\MyProject"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 项目配置
|
|
126
|
+
|
|
127
|
+
编辑 `.cursor-guard.json` 定义保护哪些文件:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"protect": ["src/**", "lib/**", "package.json"],
|
|
132
|
+
"ignore": ["node_modules/**", "dist/**"],
|
|
133
|
+
"auto_backup_interval_seconds": 60,
|
|
134
|
+
"secrets_patterns": [".env", ".env.*", "*.key", "*.pem"],
|
|
135
|
+
"retention": { "mode": "days", "days": 30 }
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 自动备份脚本
|
|
142
|
+
|
|
143
|
+
在使用 Cursor 时,在**单独的终端窗口**中运行:
|
|
144
|
+
|
|
145
|
+
```powershell
|
|
146
|
+
.\auto-backup.ps1 -Path "D:\MyProject"
|
|
147
|
+
|
|
148
|
+
# 自定义间隔(默认 60 秒):
|
|
149
|
+
.\auto-backup.ps1 -Path "D:\MyProject" -IntervalSeconds 30
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
脚本使用 Git 底层命令快照到 `cursor-guard/auto-backup` 分支——不会切换分支,也不会影响你的工作索引。
|
|
153
|
+
|
|
154
|
+
> **注意**:请在独立的 PowerShell 窗口中运行此脚本,不要在 Cursor 的集成终端中运行,因为 Cursor 终端可能干扰 Git 底层命令。
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 恢复
|
|
159
|
+
|
|
160
|
+
出问题时,直接用自然语言告诉 AI 代理即可:
|
|
161
|
+
|
|
162
|
+
### 按时间恢复
|
|
163
|
+
|
|
164
|
+
> "帮我恢复到5分钟前"
|
|
165
|
+
> "恢复到今天下午3点的状态"
|
|
166
|
+
> "回到昨天的版本"
|
|
167
|
+
|
|
168
|
+
### 按版本恢复
|
|
169
|
+
|
|
170
|
+
> "恢复到上一个版本"
|
|
171
|
+
> "回到前3个版本"
|
|
172
|
+
> "撤销最近两次修改"
|
|
173
|
+
|
|
174
|
+
### 指定文件恢复
|
|
175
|
+
|
|
176
|
+
> "把 src/app.py 恢复到10分钟前"
|
|
177
|
+
> "把 src/app.py 恢复到上一个版本"
|
|
178
|
+
|
|
179
|
+
代理会自动搜索 Git 历史和自动备份快照,列出匹配版本供你选择,确认后执行恢复。
|
|
180
|
+
|
|
181
|
+
### 恢复优先级
|
|
182
|
+
|
|
183
|
+
1. **Git** — `git restore`, `git reset`, `git reflog`
|
|
184
|
+
2. **自动备份分支** — `cursor-guard/auto-backup`
|
|
185
|
+
3. **影子拷贝** — `.cursor-guard-backup/<时间戳>/`
|
|
186
|
+
4. **对话上下文** — 代理 Read 调用捕获的原始文件内容
|
|
187
|
+
5. **编辑器历史** — VS Code/Cursor Timeline(辅助)
|
|
188
|
+
|
|
189
|
+
详细恢复命令见 [references/recovery.md](references/recovery.md)。
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 触发关键词
|
|
194
|
+
|
|
195
|
+
技能在以下信号时激活:
|
|
196
|
+
|
|
197
|
+
- AI 代理的文件编辑、删除、重命名
|
|
198
|
+
- 恢复请求:"回滚"、"误删"、"丢版本"、"改不回来"
|
|
199
|
+
- 按时间恢复:"恢复到N分钟前"、"恢复到下午3点"、"回到昨天"
|
|
200
|
+
- 按版本恢复:"恢复到上一个版本"、"前N个版本"、"撤销最近N次修改"
|
|
201
|
+
- 历史问题:Checkpoint 丢失、Timeline 不工作、保存失败
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 文件说明
|
|
206
|
+
|
|
207
|
+
| 文件 | 用途 |
|
|
208
|
+
|------|------|
|
|
209
|
+
| `SKILL.md` | AI 代理的主要技能指令 |
|
|
210
|
+
| `references/auto-backup.ps1` | PowerShell 自动备份监控脚本 |
|
|
211
|
+
| `references/recovery.md` | 恢复命令模板 |
|
|
212
|
+
| `references/cursor-guard.example.json` | 示例项目配置 |
|
|
213
|
+
| `references/cursor-guard.schema.json` | 配置文件的 JSON Schema |
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 已知限制
|
|
218
|
+
|
|
219
|
+
- **二进制文件**:Git 快照可以存储二进制文件(图片、编译产物),但无法进行有意义的 diff 或部分恢复。
|
|
220
|
+
- **未跟踪文件**:从未提交到 Git 的文件无法从 Git 历史恢复。影子拷贝(`backup_strategy: "shadow"` 或 `"both"`)是未跟踪文件的唯一安全网。
|
|
221
|
+
- **并发 Agent**:如果多个 AI 代理线程同时写入同一文件,快照无法防止竞态条件。请避免并行编辑同一文件。
|
|
222
|
+
- **外部工具修改索引**:在 `auto-backup.ps1` 运行期间,其他修改 Git 索引的工具(如 Git GUI、IDE Git 集成)可能冲突。脚本使用临时索引来最小化风险,但边缘情况仍存在。
|
|
223
|
+
- **Git worktree**:自动备份脚本支持 worktree 布局(`git rev-parse --git-dir`),但未在所有特殊配置下测试(如 `--separate-git-dir`)。
|
|
224
|
+
- **Cursor 终端干扰**:Cursor 集成终端会向 `git commit` 命令注入 `--trailer` 标志,导致 `commit-tree` 等底层命令异常。请始终在**独立的 PowerShell 窗口**中运行 `auto-backup.ps1`。
|
|
225
|
+
- **大型仓库**:对于非常大的仓库,备份循环中的 `git add -A` 可能较慢。使用 `.cursor-guard.json` 中的 `protect` 模式缩小范围。
|
|
226
|
+
|
|
227
|
+
## 环境要求
|
|
228
|
+
|
|
229
|
+
- **Git** — 主要备份策略
|
|
230
|
+
- **PowerShell 5.1+** — 自动备份脚本(Windows 自带)
|
|
231
|
+
- **Cursor IDE** — 需启用 Agent 模式
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 许可证
|
|
236
|
+
|
|
237
|
+
MIT
|
package/SKILL.md
CHANGED
|
@@ -19,6 +19,7 @@ Use this skill when any of the following appear:
|
|
|
19
19
|
- **History confusion**: Checkpoints missing, Timeline/local history not rolling back, "save failed" after external writes.
|
|
20
20
|
- **Parallel context**: Multiple repos or branches; unclear which folder is the workspace root.
|
|
21
21
|
- **Recovery asks**: e.g. "改不回来", "丢版本", "回滚", "reflog", "误删", or English equivalents.
|
|
22
|
+
- **Time/version recovery**: e.g. "恢复到5分钟前", "恢复到前3个版本", "回到上一个版本", "restore to 10 minutes ago", "go back 2 versions", "恢复到下午3点的状态".
|
|
22
23
|
|
|
23
24
|
If none of the above, do not expand scope; answer normally.
|
|
24
25
|
|
|
@@ -33,6 +34,9 @@ On first trigger in a session, check if the workspace root contains `.cursor-gua
|
|
|
33
34
|
"protect": ["src/**", "lib/**", "package.json"],
|
|
34
35
|
"ignore": ["node_modules/**", "dist/**", "*.log"],
|
|
35
36
|
|
|
37
|
+
// "git": auto-backup to a dedicated branch (default)
|
|
38
|
+
// "shadow": file copies to .cursor-guard-backup/<timestamp>/
|
|
39
|
+
// "both": git branch snapshot + shadow copies
|
|
36
40
|
"backup_strategy": "git",
|
|
37
41
|
"auto_backup_interval_seconds": 60,
|
|
38
42
|
|
|
@@ -87,15 +91,46 @@ When the target file of an edit **falls outside the protected scope**, the agent
|
|
|
87
91
|
|
|
88
92
|
**Before any High-risk operation on a protected file:**
|
|
89
93
|
|
|
94
|
+
Use a **temporary index and dedicated ref** so the user's staged/unstaged state is never touched:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# 1. Create temp index from HEAD
|
|
98
|
+
GIT_INDEX_FILE=.git/guard-snapshot-index git read-tree HEAD
|
|
99
|
+
|
|
100
|
+
# 2. Stage working-tree files into temp index
|
|
101
|
+
GIT_INDEX_FILE=.git/guard-snapshot-index git add -A
|
|
102
|
+
|
|
103
|
+
# 3. Write tree and create commit on a guard ref (not on the user's branch)
|
|
104
|
+
TREE=$(GIT_INDEX_FILE=.git/guard-snapshot-index git write-tree)
|
|
105
|
+
COMMIT=$(git commit-tree $TREE -p HEAD -m "guard: snapshot before ai edit")
|
|
106
|
+
git update-ref refs/guard/snapshot $COMMIT
|
|
107
|
+
|
|
108
|
+
# 4. Cleanup
|
|
109
|
+
rm .git/guard-snapshot-index
|
|
90
110
|
```
|
|
91
|
-
|
|
111
|
+
|
|
112
|
+
**PowerShell equivalent** (for agent Shell calls):
|
|
113
|
+
|
|
114
|
+
```powershell
|
|
115
|
+
$guardIdx = Join-Path (git rev-parse --git-dir) "guard-snapshot-index"
|
|
116
|
+
$env:GIT_INDEX_FILE = $guardIdx
|
|
117
|
+
git read-tree HEAD
|
|
118
|
+
git add -A
|
|
119
|
+
$tree = git write-tree
|
|
120
|
+
$env:GIT_INDEX_FILE = $null
|
|
121
|
+
Remove-Item $guardIdx -Force -ErrorAction SilentlyContinue
|
|
122
|
+
$commit = git commit-tree $tree -p HEAD -m "guard: snapshot before ai edit"
|
|
123
|
+
git update-ref refs/guard/snapshot $commit
|
|
92
124
|
```
|
|
93
125
|
|
|
94
126
|
- Run this via Shell tool BEFORE the first Write / StrReplace / Delete call.
|
|
95
|
-
-
|
|
96
|
-
- If `
|
|
127
|
+
- The user's index (staged files) and current branch are **never modified**.
|
|
128
|
+
- If `.cursor-guard.json` exists with `protect` patterns, scope `git add` to those paths instead of `-A`.
|
|
97
129
|
- Record the commit hash (short) and report it to the user.
|
|
98
|
-
-
|
|
130
|
+
- To restore: `git restore --source=refs/guard/snapshot -- <file>`.
|
|
131
|
+
- Before writing the tree, check staged files against `secrets_patterns` (§0). Exclude any matches and warn the user.
|
|
132
|
+
|
|
133
|
+
**Simplified fallback**: If the plumbing approach fails (e.g. `commit-tree` not available), the agent MAY fall back to `git stash push -m "guard: snapshot" --keep-index` + `git stash pop` to shelter and restore the user's state. Report which method was used in the status block.
|
|
99
134
|
|
|
100
135
|
**Before any Medium-risk operation:**
|
|
101
136
|
|
|
@@ -151,14 +186,26 @@ When editing 3+ files in one task:
|
|
|
151
186
|
|
|
152
187
|
## 4. Backup Strategy (Priority)
|
|
153
188
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
189
|
+
There are two distinct backup mechanisms. Do not confuse them:
|
|
190
|
+
|
|
191
|
+
| | **Git branch snapshot** | **Shadow copy** |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| **What** | Commits to `cursor-guard/auto-backup` branch via plumbing | File copies to `.cursor-guard-backup/<timestamp>/` |
|
|
194
|
+
| **Who creates** | `auto-backup.ps1` (when `backup_strategy` = `git` or `both`) | `auto-backup.ps1` (when `backup_strategy` = `shadow` or `both`); or the agent manually (§2b) |
|
|
195
|
+
| **Who cleans up** | Manual: `git branch -D cursor-guard/auto-backup` | `auto-backup.ps1` per `retention` config; or manual |
|
|
196
|
+
| **Restore** | `git restore --source=cursor-guard/auto-backup -- <file>` | `Copy-Item ".cursor-guard-backup/<ts>/<file>" "<path>"` |
|
|
197
|
+
| **Requires Git** | Yes | No (fallback for non-git repos) |
|
|
198
|
+
|
|
199
|
+
**Priority order for the agent:**
|
|
200
|
+
|
|
201
|
+
1. **Guard ref snapshot** (`refs/guard/snapshot`) — agent creates before each high-risk edit using temp index (§2a). Does not pollute user's branch or staging area.
|
|
202
|
+
2. **Git branch auto-backup** (`cursor-guard/auto-backup`) — periodic snapshots by `auto-backup.ps1`.
|
|
203
|
+
3. **Shadow copy** (`.cursor-guard-backup/`) — fallback for non-git repos, or as extra insurance when `backup_strategy = "both"`.
|
|
157
204
|
4. **Editor habits** — Ctrl+S frequently; optional extensions are user-configured, mention only if asked.
|
|
158
205
|
|
|
159
206
|
**Hard default:** Do NOT `git push` unless the user explicitly asks. Scope = **local only**.
|
|
160
207
|
|
|
161
|
-
**Retention:**
|
|
208
|
+
**Retention:** The `retention` config in `.cursor-guard.json` controls cleanup of **shadow copy directories** only. Git branch snapshots are not subject to retention; clean them manually (see [references/recovery.md](references/recovery.md)).
|
|
162
209
|
|
|
163
210
|
---
|
|
164
211
|
|
|
@@ -172,6 +219,126 @@ When editing 3+ files in one task:
|
|
|
172
219
|
|
|
173
220
|
---
|
|
174
221
|
|
|
222
|
+
## 5a. Time-Based & Version-Based Recovery
|
|
223
|
+
|
|
224
|
+
When the user requests recovery using time or version references, follow this workflow:
|
|
225
|
+
|
|
226
|
+
### Trigger Phrases (Chinese & English)
|
|
227
|
+
|
|
228
|
+
| Pattern | Type | Example |
|
|
229
|
+
|---------|------|---------|
|
|
230
|
+
| "恢复到 N 分钟/小时前", "N minutes/hours ago" | Time-based | "恢复到5分钟前", "restore to 10 minutes ago" |
|
|
231
|
+
| "恢复到前 N 个版本", "go back N versions" | Version-based | "恢复到前3个版本", "go back 2 versions" |
|
|
232
|
+
| "恢复到上一个版本", "previous version" | Version-based (N=1) | "回到上一个版本", "undo last change" |
|
|
233
|
+
| "恢复到今天下午3点", "restore to 3pm" | Time-based (absolute) | "恢复到下午3点的状态" |
|
|
234
|
+
| "恢复到昨天的版本", "yesterday's version" | Time-based | "恢复到昨天" |
|
|
235
|
+
|
|
236
|
+
### Step 1: Parse the Request
|
|
237
|
+
|
|
238
|
+
Extract two things from the user's request:
|
|
239
|
+
- **Target scope**: specific file(s) or entire project?
|
|
240
|
+
- **Reference point**: a time expression or a version count?
|
|
241
|
+
|
|
242
|
+
If unclear, ask: "你想恢复哪个文件?还是整个项目?" / "Which file(s) do you want to restore?"
|
|
243
|
+
|
|
244
|
+
### Step 2: Find Matching Commits
|
|
245
|
+
|
|
246
|
+
**For time-based requests**, the goal is to find the **latest commit AT or BEFORE the target time** — not commits after it.
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
# "恢复到5分钟前" → find the most recent commit BEFORE that point
|
|
250
|
+
git log --oneline --before="5 minutes ago" -5 -- <file>
|
|
251
|
+
|
|
252
|
+
# Auto-backup branch (if exists)
|
|
253
|
+
git log cursor-guard/auto-backup --oneline --before="5 minutes ago" -5 -- <file>
|
|
254
|
+
|
|
255
|
+
# Reflog as fallback (shows all HEAD movements)
|
|
256
|
+
git reflog --before="5 minutes ago" -5
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Time expression mapping (always use `--before` to find the state AT that point):
|
|
260
|
+
- "5分钟前" / "5 minutes ago" → `--before="5 minutes ago"`
|
|
261
|
+
- "1小时前" / "1 hour ago" → `--before="1 hour ago"`
|
|
262
|
+
- "今天下午3点" → `--before="today 15:00"`
|
|
263
|
+
- "昨天" / "yesterday" → `--before="yesterday 23:59"`
|
|
264
|
+
|
|
265
|
+
**Key rule**: the first result from `--before` is the closest commit at or before the target time — that is the correct restore point.
|
|
266
|
+
|
|
267
|
+
**For version-based requests**, use commit offset:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
# N versions ago on current branch
|
|
271
|
+
git log --oneline -<N+5> -- <file>
|
|
272
|
+
|
|
273
|
+
# Or use HEAD~N directly
|
|
274
|
+
git show HEAD~<N>:<file>
|
|
275
|
+
|
|
276
|
+
# Auto-backup branch
|
|
277
|
+
git log cursor-guard/auto-backup --oneline -<N+5> -- <file>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Step 3: Present Candidates to User
|
|
281
|
+
|
|
282
|
+
**Selection rule**: prefer the **latest commit AT or BEFORE the target time**. This is always candidate #1 in the `--before` results. Only if no commit exists before the target time, inform the user and offer the closest commit after it as an approximation.
|
|
283
|
+
|
|
284
|
+
Show a numbered list of matching commits with timestamps:
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
Found these snapshots / 找到以下快照:
|
|
288
|
+
|
|
289
|
+
→ 1. [abc1234] 2026-03-21 16:05:32 — guard: snapshot before ai edit ← closest before target
|
|
290
|
+
2. [def5678] 2026-03-21 16:02:15 — guard: auto-backup 2026-03-21 16:02:15
|
|
291
|
+
3. [ghi9012] 2026-03-21 15:58:40 — feat: add login page
|
|
292
|
+
|
|
293
|
+
Recommended: #1 (closest to target time). Restore this one? / 推荐 #1(最接近目标时间)。恢复这个?
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Rules**:
|
|
297
|
+
- If only ONE candidate is found, confirm with the user before restoring.
|
|
298
|
+
- If MULTIPLE candidates, pre-select #1 (closest before target) but let the user pick another.
|
|
299
|
+
- If NO candidates before the target time:
|
|
300
|
+
- Check auto-backup branch: `git rev-parse --verify cursor-guard/auto-backup`
|
|
301
|
+
- Check shadow copies: `Get-ChildItem .cursor-guard-backup/ -Directory | Sort-Object Name -Descending`
|
|
302
|
+
- If still nothing, report clearly: "No snapshot found before that time. The earliest available is [hash] at [time]. Do you want to use it?"
|
|
303
|
+
- **Never silently pick a version.** Always show and confirm.
|
|
304
|
+
|
|
305
|
+
### Step 4: Execute Recovery
|
|
306
|
+
|
|
307
|
+
**Single file recovery:**
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
git restore --source=<commit-hash> -- <path/to/file>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Entire project recovery** (destructive — require explicit confirmation):
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Show what will change first
|
|
317
|
+
git diff <commit-hash> -- .
|
|
318
|
+
|
|
319
|
+
# After user confirms:
|
|
320
|
+
git restore --source=<commit-hash> -- .
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**From shadow copy:**
|
|
324
|
+
|
|
325
|
+
```powershell
|
|
326
|
+
# Find the closest timestamp directory
|
|
327
|
+
Get-ChildItem .cursor-guard-backup/ -Directory | Sort-Object Name -Descending
|
|
328
|
+
|
|
329
|
+
# Restore
|
|
330
|
+
Copy-Item ".cursor-guard-backup/<timestamp>/<file>" "<original-path>"
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Step 5: Verify & Report
|
|
334
|
+
|
|
335
|
+
After restoring, always:
|
|
336
|
+
1. Show the restored file content (or diff) so the user can verify
|
|
337
|
+
2. Report the recovery in the status block (§6)
|
|
338
|
+
3. Suggest creating a new snapshot of the current (restored) state
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
175
342
|
## 6. Output to User (When This Skill Was Used)
|
|
176
343
|
|
|
177
344
|
When you followed this skill's workflow, end with a short **status block**:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-guard",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
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",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"files": [
|
|
23
23
|
"SKILL.md",
|
|
24
24
|
"README.md",
|
|
25
|
+
"README.zh-CN.md",
|
|
25
26
|
"LICENSE",
|
|
26
27
|
"references/"
|
|
27
28
|
],
|
|
@@ -37,10 +37,16 @@ $ErrorActionPreference = "Stop"
|
|
|
37
37
|
$resolved = (Resolve-Path $Path).Path
|
|
38
38
|
Set-Location $resolved
|
|
39
39
|
|
|
40
|
-
# ── Paths
|
|
41
|
-
$gitDir =
|
|
42
|
-
|
|
43
|
-
$
|
|
40
|
+
# ── Paths (worktree-safe: uses git rev-parse instead of hard-coding .git) ──
|
|
41
|
+
$gitDir = (git rev-parse --git-dir 2>$null)
|
|
42
|
+
if (-not $gitDir) {
|
|
43
|
+
$gitDir = Join-Path $resolved ".git"
|
|
44
|
+
} else {
|
|
45
|
+
$gitDir = (Resolve-Path $gitDir -ErrorAction SilentlyContinue).Path
|
|
46
|
+
if (-not $gitDir) { $gitDir = Join-Path $resolved ".git" }
|
|
47
|
+
}
|
|
48
|
+
$lockFile = Join-Path $gitDir "cursor-guard.lock"
|
|
49
|
+
$guardIndex = Join-Path $gitDir "cursor-guard-index"
|
|
44
50
|
$backupDir = Join-Path $resolved ".cursor-guard-backup"
|
|
45
51
|
$logFilePath = Join-Path $backupDir "backup.log"
|
|
46
52
|
|
|
@@ -56,6 +62,7 @@ trap { Invoke-Cleanup; break }
|
|
|
56
62
|
$protectPatterns = @()
|
|
57
63
|
$ignorePatterns = @()
|
|
58
64
|
$secretsPatterns = @(".env", ".env.*", "*.key", "*.pem", "*.p12", "*.pfx", "credentials*")
|
|
65
|
+
$backupStrategy = "git"
|
|
59
66
|
$retentionMode = "days"
|
|
60
67
|
$retentionDays = 30
|
|
61
68
|
$retentionMaxCnt = 100
|
|
@@ -69,6 +76,7 @@ if (Test-Path $cfgPath) {
|
|
|
69
76
|
if ($cfg.protect) { $protectPatterns = @($cfg.protect) }
|
|
70
77
|
if ($cfg.ignore) { $ignorePatterns = @($cfg.ignore) }
|
|
71
78
|
if ($cfg.secrets_patterns) { $secretsPatterns = @($cfg.secrets_patterns) }
|
|
79
|
+
if ($cfg.backup_strategy) { $backupStrategy = $cfg.backup_strategy }
|
|
72
80
|
if ($cfg.auto_backup_interval_seconds -and $IntervalSeconds -eq 0) {
|
|
73
81
|
$IntervalSeconds = $cfg.auto_backup_interval_seconds
|
|
74
82
|
}
|
|
@@ -88,7 +96,8 @@ if (Test-Path $cfgPath) {
|
|
|
88
96
|
if ($IntervalSeconds -eq 0) { $IntervalSeconds = 60 }
|
|
89
97
|
|
|
90
98
|
# ── Git repo check ───────────────────────────────────────────────
|
|
91
|
-
|
|
99
|
+
$isRepo = git rev-parse --is-inside-work-tree 2>$null
|
|
100
|
+
if ($isRepo -ne "true") {
|
|
92
101
|
$ans = Read-Host "Directory is not a Git repo. Initialize? (y/n)"
|
|
93
102
|
if ($ans -eq 'y') {
|
|
94
103
|
git init
|
|
@@ -126,6 +135,20 @@ if (-not (git rev-parse --verify $branchRef 2>$null)) {
|
|
|
126
135
|
Write-Host "[guard] Created branch: $branch" -ForegroundColor Green
|
|
127
136
|
}
|
|
128
137
|
|
|
138
|
+
# ── Ensure .cursor-guard-backup/ is git-ignored ─────────────────
|
|
139
|
+
$excludeFile = Join-Path $gitDir "info/exclude"
|
|
140
|
+
$excludeDir = Split-Path $excludeFile
|
|
141
|
+
if (-not (Test-Path $excludeDir)) { New-Item -ItemType Directory -Force $excludeDir | Out-Null }
|
|
142
|
+
$excludeEntry = ".cursor-guard-backup/"
|
|
143
|
+
if (Test-Path $excludeFile) {
|
|
144
|
+
$content = Get-Content $excludeFile -Raw -ErrorAction SilentlyContinue
|
|
145
|
+
if (-not $content -or $content -notmatch [regex]::Escape($excludeEntry)) {
|
|
146
|
+
Add-Content $excludeFile "`n$excludeEntry"
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
Set-Content $excludeFile $excludeEntry
|
|
150
|
+
}
|
|
151
|
+
|
|
129
152
|
# ── Log directory & helpers ──────────────────────────────────────
|
|
130
153
|
if (-not (Test-Path $backupDir)) { New-Item -ItemType Directory -Force $backupDir | Out-Null }
|
|
131
154
|
|
|
@@ -209,10 +232,50 @@ function Invoke-RetentionCleanup {
|
|
|
209
232
|
} catch {}
|
|
210
233
|
}
|
|
211
234
|
|
|
235
|
+
# ── Shadow copy helper ────────────────────────────────────────────
|
|
236
|
+
function Invoke-ShadowCopy {
|
|
237
|
+
$ts = Get-Date -Format 'yyyyMMdd_HHmmss'
|
|
238
|
+
$snapDir = Join-Path $backupDir $ts
|
|
239
|
+
New-Item -ItemType Directory -Force $snapDir | Out-Null
|
|
240
|
+
|
|
241
|
+
$files = if ($protectPatterns.Count -gt 0) {
|
|
242
|
+
$protectPatterns | ForEach-Object { Get-ChildItem $resolved -Recurse -File -Filter $_ -ErrorAction SilentlyContinue }
|
|
243
|
+
} else {
|
|
244
|
+
Get-ChildItem $resolved -Recurse -File -ErrorAction SilentlyContinue |
|
|
245
|
+
Where-Object { $_.FullName -notmatch '[\\/](\.git|\.cursor-guard-backup|node_modules)[\\/]' }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
$copied = 0
|
|
249
|
+
foreach ($f in $files) {
|
|
250
|
+
$rel = $f.FullName.Substring($resolved.Length + 1)
|
|
251
|
+
$skip = $false
|
|
252
|
+
foreach ($ig in $ignorePatterns) {
|
|
253
|
+
$re = '^' + [regex]::Escape($ig).Replace('\*\*','.*').Replace('\*','[^/\\]*').Replace('\?','.') + '$'
|
|
254
|
+
if ($rel -match $re) { $skip = $true; break }
|
|
255
|
+
}
|
|
256
|
+
foreach ($pat in $secretsPatterns) {
|
|
257
|
+
$re = '^' + [regex]::Escape($pat).Replace('\*','.*').Replace('\?','.') + '$'
|
|
258
|
+
if ($rel -match $re -or $f.Name -match $re) { $skip = $true; break }
|
|
259
|
+
}
|
|
260
|
+
if ($skip) { continue }
|
|
261
|
+
$dest = Join-Path $snapDir $rel
|
|
262
|
+
$destDir = Split-Path $dest
|
|
263
|
+
if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Force $destDir | Out-Null }
|
|
264
|
+
Copy-Item $f.FullName $dest -Force
|
|
265
|
+
$copied++
|
|
266
|
+
}
|
|
267
|
+
if ($copied -gt 0) {
|
|
268
|
+
Write-Log "Shadow copy $ts ($copied files)"
|
|
269
|
+
} else {
|
|
270
|
+
Remove-Item $snapDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
271
|
+
}
|
|
272
|
+
return $copied
|
|
273
|
+
}
|
|
274
|
+
|
|
212
275
|
# ── Banner ───────────────────────────────────────────────────────
|
|
213
276
|
Write-Host ""
|
|
214
277
|
Write-Host "[guard] Watching '$resolved' every ${IntervalSeconds}s (Ctrl+C to stop)" -ForegroundColor Cyan
|
|
215
|
-
Write-Host "[guard] Branch: $branch | Retention: $retentionMode ($retentionDays days / $retentionMaxCnt count / ${retentionMaxMB} MB)" -ForegroundColor Cyan
|
|
278
|
+
Write-Host "[guard] Strategy: $backupStrategy | Branch: $branch | Retention: $retentionMode ($retentionDays days / $retentionMaxCnt count / ${retentionMaxMB} MB)" -ForegroundColor Cyan
|
|
216
279
|
Write-Host "[guard] Log: $logFilePath" -ForegroundColor Cyan
|
|
217
280
|
Write-Host ""
|
|
218
281
|
|
|
@@ -226,63 +289,64 @@ try {
|
|
|
226
289
|
$dirty = git status --porcelain 2>$null
|
|
227
290
|
if (-not $dirty) { continue }
|
|
228
291
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
# Seed temp index from the current backup-branch tree
|
|
234
|
-
$parentHash = git rev-parse --verify $branchRef 2>$null
|
|
235
|
-
if ($parentHash) { git read-tree $branchRef 2>$null }
|
|
236
|
-
|
|
237
|
-
# Stage protected files from the working tree into temp index
|
|
238
|
-
if ($protectPatterns.Count -gt 0) {
|
|
239
|
-
foreach ($p in $protectPatterns) { git add -- $p 2>$null }
|
|
240
|
-
} else {
|
|
241
|
-
git add -A 2>$null
|
|
242
|
-
}
|
|
243
|
-
foreach ($ig in $ignorePatterns) {
|
|
244
|
-
git rm --cached --ignore-unmatch -rq -- $ig 2>$null
|
|
245
|
-
}
|
|
292
|
+
# ── Git branch snapshot ──────────────────────────────────
|
|
293
|
+
if ($backupStrategy -eq "git" -or $backupStrategy -eq "both") {
|
|
294
|
+
try {
|
|
295
|
+
$env:GIT_INDEX_FILE = $guardIndex
|
|
246
296
|
|
|
247
|
-
|
|
297
|
+
$parentHash = git rev-parse --verify $branchRef 2>$null
|
|
298
|
+
if ($parentHash) { git read-tree $branchRef 2>$null }
|
|
248
299
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
# Create commit via plumbing — no checkout, no hooks, no branch switch
|
|
259
|
-
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
260
|
-
$msg = "guard: auto-backup $ts"
|
|
261
|
-
$commitHash = if ($parentHash) {
|
|
262
|
-
git commit-tree $newTree -p $parentHash -m $msg
|
|
263
|
-
} else {
|
|
264
|
-
git commit-tree $newTree -m $msg
|
|
265
|
-
}
|
|
266
|
-
git update-ref $branchRef $commitHash
|
|
300
|
+
if ($protectPatterns.Count -gt 0) {
|
|
301
|
+
foreach ($p in $protectPatterns) { git add -- $p 2>$null }
|
|
302
|
+
} else {
|
|
303
|
+
git add -A 2>$null
|
|
304
|
+
}
|
|
305
|
+
foreach ($ig in $ignorePatterns) {
|
|
306
|
+
git rm --cached --ignore-unmatch -rq -- $ig 2>$null
|
|
307
|
+
}
|
|
267
308
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
309
|
+
Remove-SecretsFromIndex
|
|
310
|
+
|
|
311
|
+
$newTree = git write-tree
|
|
312
|
+
$parentTree = if ($parentHash) { git rev-parse "${branchRef}^{tree}" 2>$null } else { $null }
|
|
313
|
+
|
|
314
|
+
if ($newTree -eq $parentTree) {
|
|
315
|
+
Write-Host "[guard] $(Get-Date -Format 'HH:mm:ss') tree unchanged, skipped." -ForegroundColor DarkGray
|
|
316
|
+
} else {
|
|
317
|
+
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
318
|
+
$msg = "guard: auto-backup $ts"
|
|
319
|
+
$commitHash = if ($parentHash) {
|
|
320
|
+
git commit-tree $newTree -p $parentHash -m $msg
|
|
321
|
+
} else {
|
|
322
|
+
git commit-tree $newTree -m $msg
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (-not $commitHash) {
|
|
326
|
+
Write-Log "ERROR: commit-tree failed, snapshot skipped" Red
|
|
327
|
+
} else {
|
|
328
|
+
git update-ref $branchRef $commitHash
|
|
329
|
+
$short = $commitHash.Substring(0, 7)
|
|
330
|
+
if ($parentTree) {
|
|
331
|
+
$diff = git diff-tree --no-commit-id --name-only -r $parentTree $newTree 2>$null
|
|
332
|
+
$count = if ($diff) { @($diff).Count } else { 0 }
|
|
333
|
+
} else {
|
|
334
|
+
$all = git ls-tree --name-only -r $newTree 2>$null
|
|
335
|
+
$count = if ($all) { @($all).Count } else { 0 }
|
|
336
|
+
}
|
|
337
|
+
Write-Log "Git snapshot $short ($count files)"
|
|
338
|
+
}
|
|
339
|
+
}
|
|
271
340
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
$diff = git diff-tree --no-commit-id --name-only -r $parentTree $newTree 2>$null
|
|
276
|
-
$count = if ($diff) { @($diff).Count } else { 0 }
|
|
277
|
-
} else {
|
|
278
|
-
$all = git ls-tree --name-only -r $newTree 2>$null
|
|
279
|
-
$count = if ($all) { @($all).Count } else { 0 }
|
|
341
|
+
finally {
|
|
342
|
+
$env:GIT_INDEX_FILE = $null
|
|
343
|
+
Remove-Item $guardIndex -Force -ErrorAction SilentlyContinue
|
|
280
344
|
}
|
|
281
|
-
Write-Log "Snapshot $short ($count files)"
|
|
282
345
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
346
|
+
|
|
347
|
+
# ── Shadow copy ──────────────────────────────────────────
|
|
348
|
+
if ($backupStrategy -eq "shadow" -or $backupStrategy -eq "both") {
|
|
349
|
+
Invoke-ShadowCopy | Out-Null
|
|
286
350
|
}
|
|
287
351
|
|
|
288
352
|
# Periodic retention cleanup every 10 cycles
|
package/references/recovery.md
CHANGED
|
@@ -34,21 +34,40 @@ git restore --source=<commit> -- <path/to/file>
|
|
|
34
34
|
|
|
35
35
|
## Undo uncommitted changes (entire repo) — **destructive**
|
|
36
36
|
|
|
37
|
+
**Always preview first / 务必先预览:**
|
|
38
|
+
|
|
37
39
|
```bash
|
|
40
|
+
# Step 1: Preview what will be reverted / 预览将要还原的内容
|
|
41
|
+
git diff
|
|
42
|
+
|
|
43
|
+
# Step 2: Preview what untracked files would be removed / 预览将被删除的未跟踪文件
|
|
44
|
+
git clean -fdn # dry-run: shows what WOULD be deleted
|
|
45
|
+
|
|
46
|
+
# Step 3: Only after user confirms / 用户确认后再执行
|
|
38
47
|
git restore .
|
|
39
|
-
git clean -fd # removes untracked files/dirs — DANGEROUS
|
|
40
48
|
```
|
|
41
49
|
|
|
42
|
-
Only suggest `git clean -fd` if the user explicitly wants untracked removal; warn about data loss
|
|
50
|
+
Only suggest `git clean -fd` if the user explicitly wants untracked removal; warn about data loss:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# DANGEROUS: removes untracked files — only after explicit confirmation
|
|
54
|
+
# 危险:删除未跟踪文件——仅在用户明确确认后执行
|
|
55
|
+
git clean -fd
|
|
56
|
+
```
|
|
43
57
|
|
|
44
58
|
## Recover "lost" commits / after reset
|
|
45
59
|
|
|
46
60
|
```bash
|
|
61
|
+
# Step 1: Find the lost commit / 找到丢失的提交
|
|
47
62
|
git reflog
|
|
48
|
-
|
|
63
|
+
|
|
64
|
+
# Step 2: Create a recovery branch (safe, non-destructive) / 创建恢复分支(安全)
|
|
49
65
|
git branch recover-branch <hash>
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
|
|
67
|
+
# Step 3 (alternative): Hard reset — DESTRUCTIVE, preview first
|
|
68
|
+
# 硬重置——破坏性操作,先预览
|
|
69
|
+
git diff HEAD <hash> --stat # preview what changes
|
|
70
|
+
git reset --hard <hash> # only after confirmation
|
|
52
71
|
```
|
|
53
72
|
|
|
54
73
|
## Restore deleted file from HEAD (if it was committed)
|
|
@@ -65,6 +84,98 @@ git stash list
|
|
|
65
84
|
git stash pop
|
|
66
85
|
```
|
|
67
86
|
|
|
87
|
+
## Restore to N minutes/hours ago / 恢复到 N 分钟/小时前
|
|
88
|
+
|
|
89
|
+
The goal is to find the **latest commit AT or BEFORE** the target time.
|
|
90
|
+
|
|
91
|
+
目标是找到目标时间点**之前(含)最近的一次提交**。
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Find the most recent commit BEFORE N minutes ago
|
|
95
|
+
# 查找 N 分钟前之前最近的提交
|
|
96
|
+
git log --oneline --before="5 minutes ago" -5 -- <file>
|
|
97
|
+
|
|
98
|
+
# Find the most recent commit BEFORE N hours ago
|
|
99
|
+
# 查找 N 小时前之前最近的提交
|
|
100
|
+
git log --oneline --before="2 hours ago" -5 -- <file>
|
|
101
|
+
|
|
102
|
+
# Reflog as fallback (captures resets, amends, etc.)
|
|
103
|
+
# Reflog 作为兜底(能捕获 reset、amend 等操作)
|
|
104
|
+
git reflog --before="5 minutes ago" -5
|
|
105
|
+
|
|
106
|
+
# Restore file to the first (closest) commit found above
|
|
107
|
+
# 恢复到上面找到的第一个(最近的)提交
|
|
108
|
+
git restore --source=<commit-hash> -- <file>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Restore to a specific time / 恢复到指定时间点
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Find the closest commit before a specific time
|
|
115
|
+
# 查找指定时间点之前最近的提交
|
|
116
|
+
git log --oneline --before="2026-03-21 15:00" -5 -- <file>
|
|
117
|
+
git log --oneline --before="today 15:00" -5 -- <file>
|
|
118
|
+
|
|
119
|
+
# Yesterday / 昨天
|
|
120
|
+
git log --oneline --after="yesterday 00:00" --before="yesterday 23:59" -- <file>
|
|
121
|
+
|
|
122
|
+
# Restore
|
|
123
|
+
git restore --source=<commit-hash> -- <file>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Restore to N versions ago / 恢复到前 N 个版本
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Show recent N commits for a file
|
|
130
|
+
# 查看某文件最近 N 个提交
|
|
131
|
+
git log --oneline -10 -- <file>
|
|
132
|
+
|
|
133
|
+
# Restore to previous version (1 version ago)
|
|
134
|
+
# 恢复到上一个版本
|
|
135
|
+
git restore --source=HEAD~1 -- <file>
|
|
136
|
+
|
|
137
|
+
# Restore to 3 versions ago
|
|
138
|
+
# 恢复到前 3 个版本
|
|
139
|
+
git restore --source=HEAD~3 -- <file>
|
|
140
|
+
|
|
141
|
+
# Preview what the file looked like N versions ago (without restoring)
|
|
142
|
+
# 预览 N 个版本前的文件内容(不实际恢复)
|
|
143
|
+
git show HEAD~3:<file>
|
|
144
|
+
|
|
145
|
+
# Diff current vs N versions ago
|
|
146
|
+
# 对比当前和 N 个版本前的差异
|
|
147
|
+
git diff HEAD~3 -- <file>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
> **Note / 注意**: `HEAD~N` counts commits on the current branch. If you want the Nth commit that *changed this specific file*, use:
|
|
151
|
+
>
|
|
152
|
+
> `HEAD~N` 是按分支提交计数。如果想找第 N 次**修改该文件**的提交,用:
|
|
153
|
+
>
|
|
154
|
+
> ```bash
|
|
155
|
+
> # Find the 3rd most recent commit that touched this file
|
|
156
|
+
> git log --oneline -3 -- <file>
|
|
157
|
+
> # Then use the hash from the output
|
|
158
|
+
> git restore --source=<hash> -- <file>
|
|
159
|
+
> ```
|
|
160
|
+
|
|
161
|
+
## Restore entire project to a point in time / 恢复整个项目到某个时间点
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# CAUTION: This affects ALL files. Review carefully!
|
|
165
|
+
# 注意:这会影响所有文件,请仔细检查!
|
|
166
|
+
|
|
167
|
+
# Step 1: Find the target commit / 第一步:找到目标提交
|
|
168
|
+
git log --oneline --before="10 minutes ago" -5
|
|
169
|
+
|
|
170
|
+
# Step 2: Preview what will change / 第二步:预览变更
|
|
171
|
+
git diff <commit-hash> -- .
|
|
172
|
+
|
|
173
|
+
# Step 3: Restore (after confirmation) / 第三步:恢复(确认后)
|
|
174
|
+
git restore --source=<commit-hash> -- .
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
68
179
|
## Recover from auto-backup branch
|
|
69
180
|
|
|
70
181
|
The `auto-backup.ps1` script stores periodic snapshots on a dedicated branch via plumbing commands:
|
|
@@ -81,6 +192,14 @@ git restore --source=<commit-hash> -- <path/to/file>
|
|
|
81
192
|
|
|
82
193
|
# Diff your working copy against the auto-backup version
|
|
83
194
|
git diff cursor-guard/auto-backup -- <path/to/file>
|
|
195
|
+
|
|
196
|
+
# Time-based: find auto-backup snapshot from before N minutes ago
|
|
197
|
+
# 按时间查找:N 分钟前之前最近的自动备份快照
|
|
198
|
+
git log cursor-guard/auto-backup --oneline --before="5 minutes ago" -5 -- <path/to/file>
|
|
199
|
+
|
|
200
|
+
# Version-based: list recent N auto-backup snapshots
|
|
201
|
+
# 按版本查找:最近 N 个自动备份快照
|
|
202
|
+
git log cursor-guard/auto-backup --oneline -10 -- <path/to/file>
|
|
84
203
|
```
|
|
85
204
|
|
|
86
205
|
## If not a Git repo yet
|
|
@@ -100,11 +219,20 @@ This does **not** recover past work from before `init`.
|
|
|
100
219
|
If `.cursor-guard-backup/` exists, find snapshots:
|
|
101
220
|
|
|
102
221
|
```powershell
|
|
103
|
-
# List all backup timestamps
|
|
222
|
+
# List all backup timestamps / 列出所有备份时间戳
|
|
104
223
|
Get-ChildItem .cursor-guard-backup/ -Directory | Sort-Object Name -Descending
|
|
105
224
|
|
|
106
|
-
# Restore a specific file from a timestamp
|
|
225
|
+
# Restore a specific file from a timestamp / 从某个时间戳恢复文件
|
|
107
226
|
Copy-Item ".cursor-guard-backup/<timestamp>/<filename>" "<original-path>"
|
|
227
|
+
|
|
228
|
+
# Find the closest shadow copy to N minutes ago / 查找 N 分钟前最近的影子拷贝
|
|
229
|
+
$target = (Get-Date).AddMinutes(-5)
|
|
230
|
+
Get-ChildItem .cursor-guard-backup/ -Directory |
|
|
231
|
+
Where-Object { $_.Name -match '^\d{8}_\d{6}$' } |
|
|
232
|
+
ForEach-Object {
|
|
233
|
+
$dt = [datetime]::ParseExact($_.Name, "yyyyMMdd_HHmmss", $null)
|
|
234
|
+
[PSCustomObject]@{ Name = $_.Name; Time = $dt; Diff = [math]::Abs(($dt - $target).TotalSeconds) }
|
|
235
|
+
} | Sort-Object Diff | Select-Object -First 3
|
|
108
236
|
```
|
|
109
237
|
|
|
110
238
|
---
|