cursor-guard 1.4.0 → 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.
- package/README.md +54 -20
- package/README.zh-CN.md +54 -20
- package/SKILL.md +35 -8
- package/package.json +10 -2
- package/references/auto-backup.ps1 +10 -462
- package/references/auto-backup.sh +19 -0
- package/references/bin/cursor-guard-backup.js +14 -0
- package/references/bin/cursor-guard-doctor.js +13 -0
- package/references/config-reference.md +17 -2
- package/references/config-reference.zh-CN.md +17 -2
- package/references/cursor-guard.example.json +1 -0
- package/references/cursor-guard.schema.json +8 -3
- package/references/guard-doctor.ps1 +9 -213
- package/references/guard-doctor.sh +18 -0
- package/references/lib/auto-backup.js +508 -0
- package/references/lib/guard-doctor.js +233 -0
- package/references/lib/utils.js +325 -0
- package/references/lib/utils.test.js +329 -0
|
@@ -1,226 +1,22 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Thin wrapper — launches the Node.js guard-doctor implementation.
|
|
5
4
|
.USAGE
|
|
6
5
|
.\guard-doctor.ps1 -Path "D:\MyProject"
|
|
7
|
-
|
|
8
6
|
.NOTES
|
|
9
|
-
|
|
10
|
-
backup strategy vs environment compatibility, ignore effectiveness,
|
|
11
|
-
pre-restore refs, shadow copy directory, disk space, and more.
|
|
7
|
+
Requires Node.js >= 18.
|
|
12
8
|
#>
|
|
13
|
-
|
|
14
9
|
param(
|
|
15
10
|
[Parameter(Mandatory)]
|
|
16
11
|
[string]$Path
|
|
17
12
|
)
|
|
18
13
|
|
|
19
|
-
$
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
function Write-Check {
|
|
26
|
-
param([string]$Name, [string]$Status, [string]$Detail = "")
|
|
27
|
-
switch ($Status) {
|
|
28
|
-
"PASS" { $color = "Green"; $script:pass++ }
|
|
29
|
-
"WARN" { $color = "Yellow"; $script:warn++ }
|
|
30
|
-
"FAIL" { $color = "Red"; $script:fail++ }
|
|
31
|
-
default { $color = "Gray" }
|
|
32
|
-
}
|
|
33
|
-
$line = " [$Status] $Name"
|
|
34
|
-
if ($Detail) { $line += " — $Detail" }
|
|
35
|
-
Write-Host $line -ForegroundColor $color
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
Write-Host ""
|
|
39
|
-
Write-Host "=== Cursor Guard Doctor ===" -ForegroundColor Cyan
|
|
40
|
-
Write-Host " Target: $resolved" -ForegroundColor Cyan
|
|
41
|
-
Write-Host ""
|
|
42
|
-
|
|
43
|
-
# ── 1. Git availability ──────────────────────────────────────────
|
|
44
|
-
$hasGit = [bool](Get-Command git -ErrorAction SilentlyContinue)
|
|
45
|
-
if ($hasGit) {
|
|
46
|
-
$gitVer = (git --version 2>$null) -replace 'git version ',''
|
|
47
|
-
Write-Check "Git installed" "PASS" "version $gitVer"
|
|
48
|
-
} else {
|
|
49
|
-
Write-Check "Git installed" "WARN" "git not found in PATH; only shadow strategy available"
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
# ── 2. Git repo status ───────────────────────────────────────────
|
|
53
|
-
$isRepo = $false
|
|
54
|
-
$gitDir = $null
|
|
55
|
-
if ($hasGit) {
|
|
56
|
-
$isRepo = (git rev-parse --is-inside-work-tree 2>$null) -eq "true"
|
|
57
|
-
if ($isRepo) {
|
|
58
|
-
$gitDir = (Resolve-Path (git rev-parse --git-dir 2>$null) -ErrorAction SilentlyContinue).Path
|
|
59
|
-
$isWorktree = (git rev-parse --is-inside-work-tree 2>$null) -eq "true" -and
|
|
60
|
-
(git rev-parse --git-common-dir 2>$null) -ne (git rev-parse --git-dir 2>$null)
|
|
61
|
-
if ($isWorktree) {
|
|
62
|
-
Write-Check "Git repository" "PASS" "worktree detected (git-dir: $gitDir)"
|
|
63
|
-
} else {
|
|
64
|
-
Write-Check "Git repository" "PASS" "standard repo"
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
Write-Check "Git repository" "WARN" "not a Git repo; git/both strategies won't work"
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
# ── 3. .cursor-guard.json ────────────────────────────────────────
|
|
72
|
-
$cfgPath = Join-Path $resolved ".cursor-guard.json"
|
|
73
|
-
$cfg = $null
|
|
74
|
-
if (Test-Path $cfgPath) {
|
|
75
|
-
try {
|
|
76
|
-
$cfg = Get-Content $cfgPath -Raw | ConvertFrom-Json
|
|
77
|
-
Write-Check "Config file" "PASS" ".cursor-guard.json found and valid JSON"
|
|
78
|
-
} catch {
|
|
79
|
-
Write-Check "Config file" "FAIL" "JSON parse error: $_"
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
Write-Check "Config file" "WARN" "no .cursor-guard.json found; using defaults (protect everything)"
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
# ── 4. Strategy vs environment ────────────────────────────────────
|
|
86
|
-
$strategy = "git"
|
|
87
|
-
if ($cfg -and $cfg.backup_strategy) { $strategy = $cfg.backup_strategy }
|
|
88
|
-
if ($strategy -eq "git" -or $strategy -eq "both") {
|
|
89
|
-
if (-not $isRepo) {
|
|
90
|
-
Write-Check "Strategy compatibility" "FAIL" "backup_strategy='$strategy' but directory is not a Git repo"
|
|
91
|
-
} else {
|
|
92
|
-
Write-Check "Strategy compatibility" "PASS" "backup_strategy='$strategy' and Git repo exists"
|
|
93
|
-
}
|
|
94
|
-
} elseif ($strategy -eq "shadow") {
|
|
95
|
-
Write-Check "Strategy compatibility" "PASS" "backup_strategy='shadow' — no Git required"
|
|
96
|
-
} else {
|
|
97
|
-
Write-Check "Strategy compatibility" "FAIL" "unknown backup_strategy='$strategy' (must be git/shadow/both)"
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
# ── 5. Backup branch ─────────────────────────────────────────────
|
|
101
|
-
if ($isRepo) {
|
|
102
|
-
$branchRef = "refs/heads/cursor-guard/auto-backup"
|
|
103
|
-
$branchExists = git rev-parse --verify $branchRef 2>$null
|
|
104
|
-
if ($branchExists) {
|
|
105
|
-
$commitCount = (git rev-list --count $branchRef 2>$null)
|
|
106
|
-
Write-Check "Backup branch" "PASS" "cursor-guard/auto-backup exists ($commitCount commits)"
|
|
107
|
-
} else {
|
|
108
|
-
Write-Check "Backup branch" "WARN" "cursor-guard/auto-backup not created yet (will be created on first backup)"
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
# ── 6. Guard refs ────────────────────────────────────────────────
|
|
113
|
-
if ($isRepo) {
|
|
114
|
-
$guardRefs = git for-each-ref refs/guard/ --format="%(refname)" 2>$null
|
|
115
|
-
if ($guardRefs) {
|
|
116
|
-
$refCount = @($guardRefs).Count
|
|
117
|
-
$preRestoreRefs = @($guardRefs | Where-Object { $_ -match 'pre-restore/' })
|
|
118
|
-
Write-Check "Guard refs" "PASS" "$refCount ref(s) found ($($preRestoreRefs.Count) pre-restore snapshots)"
|
|
119
|
-
} else {
|
|
120
|
-
Write-Check "Guard refs" "WARN" "no guard refs yet (created on first snapshot or restore)"
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
# ── 7. Shadow copy directory ─────────────────────────────────────
|
|
125
|
-
$backupDir = Join-Path $resolved ".cursor-guard-backup"
|
|
126
|
-
if (Test-Path $backupDir) {
|
|
127
|
-
$snapDirs = Get-ChildItem $backupDir -Directory -ErrorAction SilentlyContinue |
|
|
128
|
-
Where-Object { $_.Name -match '^\d{8}_\d{6}$' -or $_.Name -match '^pre-restore-' }
|
|
129
|
-
$snapCount = if ($snapDirs) { @($snapDirs).Count } else { 0 }
|
|
130
|
-
$totalMB = [math]::Round(((Get-ChildItem $backupDir -Recurse -File -ErrorAction SilentlyContinue |
|
|
131
|
-
Measure-Object Length -Sum).Sum / 1MB), 1)
|
|
132
|
-
Write-Check "Shadow copies" "PASS" "$snapCount snapshot(s), ${totalMB} MB total"
|
|
133
|
-
} else {
|
|
134
|
-
Write-Check "Shadow copies" "WARN" ".cursor-guard-backup/ not found (will be created on first shadow backup)"
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
# ── 8. .gitignore / exclude coverage ────────────────────────────
|
|
138
|
-
if ($isRepo) {
|
|
139
|
-
$checkIgnored = git check-ignore ".cursor-guard-backup/test" 2>$null
|
|
140
|
-
if ($checkIgnored) {
|
|
141
|
-
Write-Check "Backup dir ignored" "PASS" ".cursor-guard-backup/ is git-ignored"
|
|
142
|
-
} else {
|
|
143
|
-
Write-Check "Backup dir ignored" "WARN" ".cursor-guard-backup/ may NOT be git-ignored — backup changes could trigger commits"
|
|
144
|
-
}
|
|
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
|
|
145
19
|
}
|
|
146
20
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
$validStrategies = @("git", "shadow", "both")
|
|
150
|
-
if ($cfg.backup_strategy -and $cfg.backup_strategy -notin $validStrategies) {
|
|
151
|
-
Write-Check "Config: backup_strategy" "FAIL" "invalid value '$($cfg.backup_strategy)'"
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
$validPreRestore = @("always", "ask", "never")
|
|
155
|
-
if ($cfg.pre_restore_backup -and $cfg.pre_restore_backup -notin $validPreRestore) {
|
|
156
|
-
Write-Check "Config: pre_restore_backup" "FAIL" "invalid value '$($cfg.pre_restore_backup)'"
|
|
157
|
-
} elseif ($cfg.pre_restore_backup -eq "never") {
|
|
158
|
-
Write-Check "Config: pre_restore_backup" "WARN" "set to 'never' — restores won't auto-preserve current version"
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if ($cfg.auto_backup_interval_seconds -and $cfg.auto_backup_interval_seconds -lt 5) {
|
|
162
|
-
Write-Check "Config: interval" "WARN" "$($cfg.auto_backup_interval_seconds)s is below minimum (5s), will be clamped"
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if ($cfg.retention -and $cfg.retention.mode) {
|
|
166
|
-
$validModes = @("days", "count", "size")
|
|
167
|
-
if ($cfg.retention.mode -notin $validModes) {
|
|
168
|
-
Write-Check "Config: retention.mode" "FAIL" "invalid value '$($cfg.retention.mode)'"
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
# ── 10. Protect / Ignore effectiveness ───────────────────────────
|
|
174
|
-
if ($cfg -and $cfg.protect) {
|
|
175
|
-
$allFiles = Get-ChildItem $resolved -Recurse -File -ErrorAction SilentlyContinue |
|
|
176
|
-
Where-Object { $_.FullName -notmatch '[\\/](\.git|\.cursor-guard-backup|node_modules)[\\/]' }
|
|
177
|
-
$protectedCount = 0
|
|
178
|
-
foreach ($f in $allFiles) {
|
|
179
|
-
$rel = $f.FullName.Substring($resolved.Length + 1) -replace '\\','/'
|
|
180
|
-
foreach ($pat in @($cfg.protect)) {
|
|
181
|
-
$p = $pat -replace '\\','/'
|
|
182
|
-
if ($rel -like $p -or (Split-Path $rel -Leaf) -like $p) { $protectedCount++; break }
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
$totalCount = if ($allFiles) { @($allFiles).Count } else { 0 }
|
|
186
|
-
Write-Check "Protect patterns" "PASS" "$protectedCount / $totalCount files matched by protect patterns"
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
# ── 11. Disk space ────────────────────────────────────────────────
|
|
190
|
-
try {
|
|
191
|
-
$letter = (Split-Path $resolved -Qualifier) -replace ':$',''
|
|
192
|
-
$drv = Get-PSDrive $letter -ErrorAction Stop
|
|
193
|
-
$freeGB = [math]::Round($drv.Free / 1GB, 1)
|
|
194
|
-
if ($freeGB -lt 1) {
|
|
195
|
-
Write-Check "Disk space" "FAIL" "${freeGB} GB free — critically low"
|
|
196
|
-
} elseif ($freeGB -lt 5) {
|
|
197
|
-
Write-Check "Disk space" "WARN" "${freeGB} GB free"
|
|
198
|
-
} else {
|
|
199
|
-
Write-Check "Disk space" "PASS" "${freeGB} GB free"
|
|
200
|
-
}
|
|
201
|
-
} catch {
|
|
202
|
-
Write-Check "Disk space" "WARN" "could not determine free space"
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
# ── 12. Lock file ────────────────────────────────────────────────
|
|
206
|
-
$lockFile = if ($gitDir) { Join-Path $gitDir "cursor-guard.lock" } else { Join-Path $backupDir "cursor-guard.lock" }
|
|
207
|
-
if (Test-Path $lockFile) {
|
|
208
|
-
$lockContent = Get-Content $lockFile -Raw -ErrorAction SilentlyContinue
|
|
209
|
-
Write-Check "Lock file" "WARN" "lock file exists — another instance may be running. Content: $lockContent"
|
|
210
|
-
} else {
|
|
211
|
-
Write-Check "Lock file" "PASS" "no lock file (no running instance)"
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
# ── Summary ──────────────────────────────────────────────────────
|
|
215
|
-
Write-Host ""
|
|
216
|
-
Write-Host "=== Summary ===" -ForegroundColor Cyan
|
|
217
|
-
Write-Host " PASS: $pass | WARN: $warn | FAIL: $fail" -ForegroundColor $(if ($fail -gt 0) { "Red" } elseif ($warn -gt 0) { "Yellow" } else { "Green" })
|
|
218
|
-
Write-Host ""
|
|
219
|
-
if ($fail -gt 0) {
|
|
220
|
-
Write-Host " Fix FAIL items before relying on Cursor Guard." -ForegroundColor Red
|
|
221
|
-
} elseif ($warn -gt 0) {
|
|
222
|
-
Write-Host " Review WARN items to ensure everything works as expected." -ForegroundColor Yellow
|
|
223
|
-
} else {
|
|
224
|
-
Write-Host " All checks passed. Cursor Guard is ready." -ForegroundColor Green
|
|
225
|
-
}
|
|
226
|
-
Write-Host ""
|
|
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"
|