cursor-guard 1.3.2 → 2.0.2

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.
@@ -1,359 +1,27 @@
1
1
  <#
2
2
  .SYNOPSIS
3
- Auto-backup script for Cursor Guard.
4
- Periodically snapshots protected files to a local Git branch using
5
- plumbing commands — never switches branches or disturbs the main index.
6
- Reads .cursor-guard.json for scope, secrets, and retention settings.
7
-
3
+ Thin wrapper launches the Node.js auto-backup implementation.
8
4
  .USAGE
9
- # Run in a separate PowerShell window while working in Cursor:
10
5
  .\auto-backup.ps1 -Path "D:\MyProject"
11
-
12
- # Custom interval (default 60 seconds):
13
6
  .\auto-backup.ps1 -Path "D:\MyProject" -IntervalSeconds 30
14
-
15
- # Stop: Ctrl+C or close the PowerShell window.
16
-
17
7
  .NOTES
18
- - Snapshots go to branch `cursor-guard/auto-backup` via plumbing commands.
19
- - Never switches branches, never touches the main index.
20
- - Does NOT push to any remote.
21
- - Sensitive files matching secrets_patterns are auto-excluded.
22
- - Shadow copies are cleaned per retention policy (default: keep 30 days).
23
- - Log file: .cursor-guard-backup/backup.log
24
- - IMPORTANT: Run this script in a SEPARATE PowerShell window, NOT inside
25
- Cursor's integrated terminal. Cursor's terminal injects --trailer flags
26
- into git commit commands, which corrupts plumbing calls like commit-tree.
8
+ Requires Node.js >= 18. Run in a SEPARATE terminal, not inside Cursor.
27
9
  #>
28
-
29
10
  param(
30
11
  [Parameter(Mandatory)]
31
12
  [string]$Path,
32
-
33
13
  [int]$IntervalSeconds = 0
34
14
  )
35
15
 
36
- $ErrorActionPreference = "Stop"
37
- $resolved = (Resolve-Path $Path).Path
38
- Set-Location $resolved
39
-
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"
50
- $backupDir = Join-Path $resolved ".cursor-guard-backup"
51
- $logFilePath = Join-Path $backupDir "backup.log"
52
-
53
- # ── Cleanup on exit ───────────────────────────────────────────────
54
- function Invoke-Cleanup {
55
- $env:GIT_INDEX_FILE = $null
56
- Remove-Item $guardIndex -Force -ErrorAction SilentlyContinue
57
- Remove-Item $lockFile -Force -ErrorAction SilentlyContinue
58
- }
59
- trap { Invoke-Cleanup; break }
60
-
61
- # ── Defaults ──────────────────────────────────────────────────────
62
- $protectPatterns = @()
63
- $ignorePatterns = @()
64
- $secretsPatterns = @(".env", ".env.*", "*.key", "*.pem", "*.p12", "*.pfx", "credentials*")
65
- $backupStrategy = "git"
66
- $retentionMode = "days"
67
- $retentionDays = 30
68
- $retentionMaxCnt = 100
69
- $retentionMaxMB = 500
70
-
71
- # ── Load .cursor-guard.json ──────────────────────────────────────
72
- $cfgPath = Join-Path $resolved ".cursor-guard.json"
73
- if (Test-Path $cfgPath) {
74
- try {
75
- $cfg = Get-Content $cfgPath -Raw | ConvertFrom-Json
76
- if ($cfg.protect) { $protectPatterns = @($cfg.protect) }
77
- if ($cfg.ignore) { $ignorePatterns = @($cfg.ignore) }
78
- if ($cfg.secrets_patterns) { $secretsPatterns = @($cfg.secrets_patterns) }
79
- if ($cfg.backup_strategy) { $backupStrategy = $cfg.backup_strategy }
80
- if ($cfg.auto_backup_interval_seconds -and $IntervalSeconds -eq 0) {
81
- $IntervalSeconds = $cfg.auto_backup_interval_seconds
82
- }
83
- if ($cfg.retention) {
84
- if ($cfg.retention.mode) { $retentionMode = $cfg.retention.mode }
85
- if ($cfg.retention.days) { $retentionDays = $cfg.retention.days }
86
- if ($cfg.retention.max_count) { $retentionMaxCnt = $cfg.retention.max_count }
87
- if ($cfg.retention.max_size_mb) { $retentionMaxMB = $cfg.retention.max_size_mb }
88
- }
89
- Write-Host "[guard] Config loaded protect=$($protectPatterns.Count) ignore=$($ignorePatterns.Count) retention=$retentionMode" -ForegroundColor Cyan
90
- }
91
- catch {
92
- Write-Host "[guard] WARNING: .cursor-guard.json parse error - using defaults." -ForegroundColor Yellow
93
- Write-Host " $_" -ForegroundColor Yellow
94
- }
95
- }
96
- if ($IntervalSeconds -eq 0) { $IntervalSeconds = 60 }
97
-
98
- # ── Git repo check ───────────────────────────────────────────────
99
- $isRepo = git rev-parse --is-inside-work-tree 2>$null
100
- if ($isRepo -ne "true") {
101
- $ans = Read-Host "Directory is not a Git repo. Initialize? (y/n)"
102
- if ($ans -eq 'y') {
103
- git init
104
- $gi = Join-Path $resolved ".gitignore"
105
- $entry = ".cursor-guard-backup/"
106
- if (Test-Path $gi) {
107
- $content = Get-Content $gi -Raw
108
- if ($content -notmatch [regex]::Escape($entry)) {
109
- Add-Content $gi "`n$entry"
110
- }
111
- } else {
112
- Set-Content $gi $entry
113
- }
114
- git add -A; git commit -m "guard: initial snapshot" --no-verify
115
- Write-Host "[guard] Repo initialized with snapshot." -ForegroundColor Green
116
- } else {
117
- Write-Host "[guard] Git is required. Exiting." -ForegroundColor Red
118
- exit 1
119
- }
120
- }
121
-
122
- # ── Lock file (prevent multiple instances) ───────────────────────
123
- if (Test-Path $lockFile) {
124
- Write-Host "[guard] ERROR: Lock file exists ($lockFile)." -ForegroundColor Red
125
- Write-Host " If no other instance is running, delete it and retry." -ForegroundColor Red
16
+ $nodeCmd = if (Get-Command node -ErrorAction SilentlyContinue) { "node" } else { $null }
17
+ if (-not $nodeCmd) {
18
+ Write-Host "[guard] ERROR: Node.js not found. Install Node.js >= 18 first." -ForegroundColor Red
19
+ Write-Host " https://nodejs.org/" -ForegroundColor Yellow
126
20
  exit 1
127
21
  }
128
- Set-Content $lockFile "pid=$PID`nstarted=$(Get-Date -Format 'o')"
129
-
130
- # ── Backup branch ───────────────────────────────────────────────
131
- $branch = "cursor-guard/auto-backup"
132
- $branchRef = "refs/heads/$branch"
133
- if (-not (git rev-parse --verify $branchRef 2>$null)) {
134
- git branch $branch HEAD 2>$null
135
- Write-Host "[guard] Created branch: $branch" -ForegroundColor Green
136
- }
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
-
152
- # ── Log directory & helpers ──────────────────────────────────────
153
- if (-not (Test-Path $backupDir)) { New-Item -ItemType Directory -Force $backupDir | Out-Null }
154
-
155
- function Write-Log {
156
- param([string]$Msg, [ConsoleColor]$Color = "Green")
157
- $line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $Msg"
158
- Add-Content -Path $logFilePath -Value $line -ErrorAction SilentlyContinue
159
- Write-Host "[guard] $line" -ForegroundColor $Color
160
- }
161
-
162
- # ── Secrets filter ───────────────────────────────────────────────
163
- function Remove-SecretsFromIndex {
164
- $files = git ls-files --cached 2>$null
165
- if (-not $files) { return }
166
- $excluded = @()
167
- foreach ($f in $files) {
168
- $leaf = Split-Path $f -Leaf
169
- foreach ($pat in $secretsPatterns) {
170
- $re = '^' + [regex]::Escape($pat).Replace('\*','.*').Replace('\?','.') + '$'
171
- if ($f -match $re -or $leaf -match $re) {
172
- git rm --cached --ignore-unmatch -q -- $f 2>$null
173
- $excluded += $f
174
- break
175
- }
176
- }
177
- }
178
- if ($excluded.Count -gt 0) {
179
- Write-Log "Secrets auto-excluded: $($excluded -join ', ')" Yellow
180
- }
181
- }
182
-
183
- # ── Retention cleanup ────────────────────────────────────────────
184
- function Invoke-RetentionCleanup {
185
- # Clean shadow-copy directories named yyyyMMdd_HHmmss
186
- $dirs = Get-ChildItem $backupDir -Directory -ErrorAction SilentlyContinue |
187
- Where-Object { $_.Name -match '^\d{8}_\d{6}$' } |
188
- Sort-Object Name -Descending
189
- if ($dirs -and $dirs.Count -gt 0) {
190
- $removed = 0
191
- switch ($retentionMode) {
192
- "days" {
193
- $cutoff = (Get-Date).AddDays(-$retentionDays)
194
- foreach ($d in $dirs) {
195
- try {
196
- $dt = [datetime]::ParseExact($d.Name, "yyyyMMdd_HHmmss", $null)
197
- if ($dt -lt $cutoff) { Remove-Item $d.FullName -Recurse -Force; $removed++ }
198
- } catch {}
199
- }
200
- }
201
- "count" {
202
- if ($dirs.Count -gt $retentionMaxCnt) {
203
- $dirs | Select-Object -Skip $retentionMaxCnt |
204
- ForEach-Object { Remove-Item $_.FullName -Recurse -Force; $removed++ }
205
- }
206
- }
207
- "size" {
208
- $totalMB = (Get-ChildItem $backupDir -Recurse -File -ErrorAction SilentlyContinue |
209
- Measure-Object Length -Sum).Sum / 1MB
210
- $oldest = $dirs | Sort-Object Name
211
- foreach ($d in $oldest) {
212
- if ($totalMB -le $retentionMaxMB) { break }
213
- $sz = (Get-ChildItem $d.FullName -Recurse -File |
214
- Measure-Object Length -Sum).Sum / 1MB
215
- Remove-Item $d.FullName -Recurse -Force
216
- $totalMB -= $sz; $removed++
217
- }
218
- }
219
- }
220
- if ($removed -gt 0) {
221
- Write-Log "Retention ($retentionMode): cleaned $removed old snapshot(s)" DarkGray
222
- }
223
- }
224
-
225
- # Disk-space warning
226
- try {
227
- $letter = (Split-Path $resolved -Qualifier) -replace ':$',''
228
- $drv = Get-PSDrive $letter
229
- $freeGB = [math]::Round($drv.Free / 1GB, 1)
230
- if ($freeGB -lt 1) { Write-Log "WARNING: disk critically low - ${freeGB} GB free" Red }
231
- elseif ($freeGB -lt 5) { Write-Log "Disk note: ${freeGB} GB free" Yellow }
232
- } catch {}
233
- }
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
-
275
- # ── Banner ───────────────────────────────────────────────────────
276
- Write-Host ""
277
- Write-Host "[guard] Watching '$resolved' every ${IntervalSeconds}s (Ctrl+C to stop)" -ForegroundColor Cyan
278
- Write-Host "[guard] Strategy: $backupStrategy | Branch: $branch | Retention: $retentionMode ($retentionDays days / $retentionMaxCnt count / ${retentionMaxMB} MB)" -ForegroundColor Cyan
279
- Write-Host "[guard] Log: $logFilePath" -ForegroundColor Cyan
280
- Write-Host ""
281
22
 
282
- # ── Main loop ────────────────────────────────────────────────────
283
- $cycle = 0
284
- try {
285
- while ($true) {
286
- Start-Sleep -Seconds $IntervalSeconds
287
- $cycle++
23
+ $script = Join-Path (Join-Path $PSScriptRoot "bin") "cursor-guard-backup.js"
24
+ $args_ = @($script, "--path", $Path)
25
+ if ($IntervalSeconds -gt 0) { $args_ += @("--interval", $IntervalSeconds) }
288
26
 
289
- $dirty = git status --porcelain 2>$null
290
- if (-not $dirty) { continue }
291
-
292
- # ── Git branch snapshot ──────────────────────────────────
293
- if ($backupStrategy -eq "git" -or $backupStrategy -eq "both") {
294
- try {
295
- $env:GIT_INDEX_FILE = $guardIndex
296
-
297
- $parentHash = git rev-parse --verify $branchRef 2>$null
298
- if ($parentHash) { git read-tree $branchRef 2>$null }
299
-
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
- }
308
-
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
- }
340
- }
341
- finally {
342
- $env:GIT_INDEX_FILE = $null
343
- Remove-Item $guardIndex -Force -ErrorAction SilentlyContinue
344
- }
345
- }
346
-
347
- # ── Shadow copy ──────────────────────────────────────────
348
- if ($backupStrategy -eq "shadow" -or $backupStrategy -eq "both") {
349
- Invoke-ShadowCopy | Out-Null
350
- }
351
-
352
- # Periodic retention cleanup every 10 cycles
353
- if ($cycle % 10 -eq 0) { Invoke-RetentionCleanup }
354
- }
355
- }
356
- finally {
357
- Invoke-Cleanup
358
- Write-Host "`n[guard] Stopped." -ForegroundColor Cyan
359
- }
27
+ & $nodeCmd @args_
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ # Thin wrapper — launches the Node.js auto-backup implementation.
3
+ # Usage: ./auto-backup.sh /path/to/project [interval_seconds]
4
+ # Requires: Node.js >= 18
5
+
6
+ set -e
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+
10
+ if ! command -v node &>/dev/null; then
11
+ echo "[guard] ERROR: Node.js not found. Install Node.js >= 18 first."
12
+ echo " https://nodejs.org/"
13
+ exit 1
14
+ fi
15
+
16
+ TARGET="${1:-.}"
17
+ INTERVAL="${2:-0}"
18
+
19
+ exec node "$SCRIPT_DIR/bin/cursor-guard-backup.js" --path "$TARGET" --interval "$INTERVAL"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const { parseArgs } = require('../lib/utils');
6
+
7
+ const args = parseArgs(process.argv);
8
+ const targetPath = args.path || '.';
9
+ const interval = parseInt(args.interval, 10) || 0;
10
+
11
+ const resolved = path.resolve(targetPath);
12
+
13
+ const { runBackup } = require('../lib/auto-backup');
14
+ runBackup(resolved, interval);
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const { parseArgs } = require('../lib/utils');
6
+
7
+ const args = parseArgs(process.argv);
8
+ const targetPath = args.path || '.';
9
+ const resolved = path.resolve(targetPath);
10
+
11
+ const { runDoctor } = require('../lib/guard-doctor');
12
+ const exitCode = runDoctor(resolved);
13
+ process.exit(exitCode);
@@ -67,7 +67,7 @@ Blacklist glob patterns. Matching files are excluded from protection even if the
67
67
  - **Minimum**: `5`
68
68
  - **Default**: `60`
69
69
 
70
- Interval in seconds for `auto-backup.ps1` to check for changes and create snapshots.
70
+ Interval in seconds for the auto-backup script to check for changes and create snapshots.
71
71
 
72
72
  ```json
73
73
  "auto_backup_interval_seconds": 60
@@ -80,7 +80,9 @@ Interval in seconds for `auto-backup.ps1` to check for changes and create snapsh
80
80
  - **Type**: `string[]` (glob patterns)
81
81
  - **Default**: built-in list (see below)
82
82
 
83
- Glob patterns for sensitive files. Matching files are **auto-excluded** from backup, even if within `protect` scope. Built-in defaults (always active): `.env`, `.env.*`, `*.key`, `*.pem`, `*.p12`, `*.pfx`, `credentials*`. Set this field to override with your own patterns.
83
+ Glob patterns for sensitive files. Matching files are **auto-excluded** from backup, even if within `protect` scope. Built-in defaults: `.env`, `.env.*`, `*.key`, `*.pem`, `*.p12`, `*.pfx`, `credentials*`.
84
+
85
+ **Setting this field replaces the built-in defaults entirely.** If you only need to add patterns, use `secrets_patterns_extra` instead.
84
86
 
85
87
  ```json
86
88
  "secrets_patterns": [".env", ".env.*", "*.key", "*.pem"]
@@ -88,6 +90,19 @@ Glob patterns for sensitive files. Matching files are **auto-excluded** from bac
88
90
 
89
91
  ---
90
92
 
93
+ ## `secrets_patterns_extra`
94
+
95
+ - **Type**: `string[]` (glob patterns)
96
+ - **Default**: not set
97
+
98
+ Additional glob patterns **appended** to the current `secrets_patterns` (including defaults). Use this to add custom patterns without losing the built-in protection for `.p12`, `.pfx`, `credentials*`, etc.
99
+
100
+ ```json
101
+ "secrets_patterns_extra": ["*.secret", "tokens.*"]
102
+ ```
103
+
104
+ ---
105
+
91
106
  ## `pre_restore_backup`
92
107
 
93
108
  - **Type**: `string`
@@ -132,3 +147,29 @@ Retention policy for **shadow copies** only. Git branch snapshots are not auto-c
132
147
  "max_size_mb": 500
133
148
  }
134
149
  ```
150
+
151
+ ---
152
+
153
+ ## `git_retention`
154
+
155
+ - **Type**: `object`
156
+ - **Default**: `{ "enabled": false, "mode": "count", "max_count": 200 }`
157
+
158
+ Retention policy for the **`cursor-guard/auto-backup` Git branch**. By default, auto-backup commits accumulate indefinitely. Enable this to automatically prune old commits.
159
+
160
+ ### Sub-fields
161
+
162
+ | Field | Type | Default | Description |
163
+ |-------|------|---------|-------------|
164
+ | `enabled` | `boolean` | `false` | Enable automatic pruning. When false, branch grows without limit. |
165
+ | `mode` | `"days"` \| `"count"` | `"count"` | Pruning strategy |
166
+ | `days` | `integer` | `30` | Keep commits from last N days (when mode=days) |
167
+ | `max_count` | `integer` | `200` | Keep N newest commits (when mode=count, minimum 10) |
168
+
169
+ ```json
170
+ "git_retention": {
171
+ "enabled": true,
172
+ "mode": "count",
173
+ "max_count": 200
174
+ }
175
+ ```
@@ -67,7 +67,7 @@
67
67
  - **最小值**:`5`
68
68
  - **默认值**:`60`
69
69
 
70
- 自动备份脚本 `auto-backup.ps1` 检查变更并创建快照的间隔秒数。
70
+ 自动备份脚本检查变更并创建快照的间隔秒数。
71
71
 
72
72
  ```json
73
73
  "auto_backup_interval_seconds": 60
@@ -80,7 +80,9 @@
80
80
  - **类型**:`string[]`(glob 模式)
81
81
  - **默认值**:内置列表(见下)
82
82
 
83
- 敏感文件 glob 模式。匹配的文件**自动排除**备份,即使在 `protect` 范围内。内置默认值(始终生效):`.env`、`.env.*`、`*.key`、`*.pem`、`*.p12`、`*.pfx`、`credentials*`。设置此字段可覆盖为自定义模式。
83
+ 敏感文件 glob 模式。匹配的文件**自动排除**备份,即使在 `protect` 范围内。内置默认值:`.env`、`.env.*`、`*.key`、`*.pem`、`*.p12`、`*.pfx`、`credentials*`。
84
+
85
+ **设置此字段会完全替换内置默认值。** 如果只想追加模式,请使用 `secrets_patterns_extra`。
84
86
 
85
87
  ```json
86
88
  "secrets_patterns": [".env", ".env.*", "*.key", "*.pem"]
@@ -88,6 +90,19 @@
88
90
 
89
91
  ---
90
92
 
93
+ ## `secrets_patterns_extra`
94
+
95
+ - **类型**:`string[]`(glob 模式)
96
+ - **默认值**:未设置
97
+
98
+ 追加到当前 `secrets_patterns`(含默认值)的额外 glob 模式。使用此字段可在不丢失 `.p12`、`.pfx`、`credentials*` 等内置保护的情况下添加自定义模式。
99
+
100
+ ```json
101
+ "secrets_patterns_extra": ["*.secret", "tokens.*"]
102
+ ```
103
+
104
+ ---
105
+
91
106
  ## `pre_restore_backup`
92
107
 
93
108
  - **类型**:`string`
@@ -132,3 +147,29 @@
132
147
  "max_size_mb": 500
133
148
  }
134
149
  ```
150
+
151
+ ---
152
+
153
+ ## `git_retention`
154
+
155
+ - **类型**:`object`
156
+ - **默认值**:`{ "enabled": false, "mode": "count", "max_count": 200 }`
157
+
158
+ **`cursor-guard/auto-backup` Git 分支**的保留策略。默认情况下自动备份提交会无限累积。启用此项可自动裁剪旧提交。
159
+
160
+ ### 子字段
161
+
162
+ | 字段 | 类型 | 默认值 | 说明 |
163
+ |------|------|--------|------|
164
+ | `enabled` | `boolean` | `false` | 启用自动裁剪。关闭时分支无限增长。 |
165
+ | `mode` | `"days"` \| `"count"` | `"count"` | 裁剪策略 |
166
+ | `days` | `integer` | `30` | 保留最近 N 天的提交(mode=days 时生效) |
167
+ | `max_count` | `integer` | `200` | 保留最新 N 个提交(mode=count 时生效,最少 10) |
168
+
169
+ ```json
170
+ "git_retention": {
171
+ "enabled": true,
172
+ "mode": "count",
173
+ "max_count": 200
174
+ }
175
+ ```
@@ -26,11 +26,18 @@
26
26
  "*.key",
27
27
  "*.pem"
28
28
  ],
29
+ "secrets_patterns_extra": [],
29
30
  "pre_restore_backup": "always",
30
31
  "retention": {
31
32
  "mode": "days",
32
33
  "days": 30,
33
34
  "max_count": 100,
34
35
  "max_size_mb": 500
36
+ },
37
+ "git_retention": {
38
+ "enabled": false,
39
+ "mode": "count",
40
+ "days": 30,
41
+ "max_count": 200
35
42
  }
36
43
  }
@@ -24,12 +24,17 @@
24
24
  "type": "integer",
25
25
  "minimum": 5,
26
26
  "default": 60,
27
- "description": "Interval in seconds for auto-backup.ps1 to check for changes."
27
+ "description": "Interval in seconds for auto-backup script to check for changes."
28
28
  },
29
29
  "secrets_patterns": {
30
30
  "type": "array",
31
31
  "items": { "type": "string" },
32
- "description": "Glob patterns for sensitive files auto-excluded from backups. Built-in defaults: .env, .env.*, *.key, *.pem, *.p12, *.pfx, credentials*"
32
+ "description": "Glob patterns for sensitive files auto-excluded from backups. Overrides built-in defaults: .env, .env.*, *.key, *.pem, *.p12, *.pfx, credentials*. Use secrets_patterns_extra to add without replacing."
33
+ },
34
+ "secrets_patterns_extra": {
35
+ "type": "array",
36
+ "items": { "type": "string" },
37
+ "description": "Additional glob patterns appended to secrets_patterns (including defaults). Use this to add patterns without losing built-in protection."
33
38
  },
34
39
  "pre_restore_backup": {
35
40
  "type": "string",
@@ -67,7 +72,37 @@
67
72
  }
68
73
  },
69
74
  "additionalProperties": false
75
+ },
76
+ "git_retention": {
77
+ "type": "object",
78
+ "description": "Controls automatic cleanup of old commits on the cursor-guard/auto-backup branch.",
79
+ "properties": {
80
+ "enabled": {
81
+ "type": "boolean",
82
+ "default": false,
83
+ "description": "Enable automatic pruning of old auto-backup commits. Default false (manual cleanup)."
84
+ },
85
+ "mode": {
86
+ "type": "string",
87
+ "enum": ["days", "count"],
88
+ "default": "count",
89
+ "description": "'days': keep commits from the last N days. 'count': keep N newest commits."
90
+ },
91
+ "days": {
92
+ "type": "integer",
93
+ "minimum": 1,
94
+ "default": 30,
95
+ "description": "Number of days to keep auto-backup commits (when mode='days')."
96
+ },
97
+ "max_count": {
98
+ "type": "integer",
99
+ "minimum": 10,
100
+ "default": 200,
101
+ "description": "Maximum number of auto-backup commits to keep (when mode='count')."
102
+ }
103
+ },
104
+ "additionalProperties": false
70
105
  }
71
106
  },
72
- "additionalProperties": true
107
+ "additionalProperties": false
73
108
  }
@@ -0,0 +1,22 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Thin wrapper — launches the Node.js guard-doctor implementation.
4
+ .USAGE
5
+ .\guard-doctor.ps1 -Path "D:\MyProject"
6
+ .NOTES
7
+ Requires Node.js >= 18.
8
+ #>
9
+ param(
10
+ [Parameter(Mandatory)]
11
+ [string]$Path
12
+ )
13
+
14
+ $nodeCmd = if (Get-Command node -ErrorAction SilentlyContinue) { "node" } else { $null }
15
+ if (-not $nodeCmd) {
16
+ Write-Host "[guard] ERROR: Node.js not found. Install Node.js >= 18 first." -ForegroundColor Red
17
+ Write-Host " https://nodejs.org/" -ForegroundColor Yellow
18
+ exit 1
19
+ }
20
+
21
+ $script = Join-Path (Join-Path $PSScriptRoot "bin") "cursor-guard-doctor.js"
22
+ & $nodeCmd $script --path $Path
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ # Thin wrapper — launches the Node.js guard-doctor implementation.
3
+ # Usage: ./guard-doctor.sh /path/to/project
4
+ # Requires: Node.js >= 18
5
+
6
+ set -e
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+
10
+ if ! command -v node &>/dev/null; then
11
+ echo "[guard] ERROR: Node.js not found. Install Node.js >= 18 first."
12
+ echo " https://nodejs.org/"
13
+ exit 1
14
+ fi
15
+
16
+ TARGET="${1:-.}"
17
+
18
+ exec node "$SCRIPT_DIR/bin/cursor-guard-doctor.js" --path "$TARGET"