cursor-guard 2.1.0 → 3.0.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 +63 -11
- package/README.zh-CN.md +345 -293
- package/ROADMAP.md +834 -0
- package/SKILL.md +617 -557
- package/package.json +26 -6
- package/references/config-reference.md +175 -175
- package/references/config-reference.zh-CN.md +175 -175
- package/references/cursor-guard.example.json +0 -6
- package/references/lib/auto-backup.js +257 -530
- package/references/lib/core/backups.js +357 -0
- package/references/lib/core/core.test.js +859 -0
- package/references/lib/core/doctor-fix.js +237 -0
- package/references/lib/core/doctor.js +248 -0
- package/references/lib/core/restore.js +305 -0
- package/references/lib/core/snapshot.js +173 -0
- package/references/lib/core/status.js +163 -0
- package/references/lib/guard-doctor.js +46 -238
- package/references/lib/utils.js +371 -371
- package/references/mcp/mcp.test.js +279 -0
- package/references/mcp/server.js +198 -0
- package/references/quickstart.zh-CN.md +342 -0
|
@@ -1,238 +1,46 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function runDoctor(projectDir) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const currentDir = git(['rev-parse', '--git-dir'], { cwd: projectDir, allowFail: true });
|
|
48
|
-
isWorktree = commonDir && currentDir && commonDir !== currentDir;
|
|
49
|
-
} catch { /* ignore */ }
|
|
50
|
-
if (isWorktree) {
|
|
51
|
-
check('Git repository', 'PASS', `worktree detected (git-dir: ${gDir})`);
|
|
52
|
-
} else {
|
|
53
|
-
check('Git repository', 'PASS', 'standard repo');
|
|
54
|
-
}
|
|
55
|
-
} else {
|
|
56
|
-
check('Git repository', 'WARN', 'not a Git repo; git/both strategies won\'t work');
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// 3. Config file
|
|
61
|
-
const { cfg, loaded, error } = loadConfig(projectDir);
|
|
62
|
-
if (loaded) {
|
|
63
|
-
check('Config file', 'PASS', '.cursor-guard.json found and valid JSON');
|
|
64
|
-
} else if (error) {
|
|
65
|
-
check('Config file', 'FAIL', `JSON parse error: ${error}`);
|
|
66
|
-
} else {
|
|
67
|
-
check('Config file', 'WARN', 'no .cursor-guard.json found; using defaults (protect everything)');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// 4. Strategy vs environment
|
|
71
|
-
const strategy = cfg.backup_strategy;
|
|
72
|
-
if (strategy === 'git' || strategy === 'both') {
|
|
73
|
-
if (!repo) {
|
|
74
|
-
check('Strategy compatibility', 'FAIL', `backup_strategy='${strategy}' but directory is not a Git repo`);
|
|
75
|
-
} else {
|
|
76
|
-
check('Strategy compatibility', 'PASS', `backup_strategy='${strategy}' and Git repo exists`);
|
|
77
|
-
}
|
|
78
|
-
} else if (strategy === 'shadow') {
|
|
79
|
-
check('Strategy compatibility', 'PASS', "backup_strategy='shadow' — no Git required");
|
|
80
|
-
} else {
|
|
81
|
-
check('Strategy compatibility', 'FAIL', `unknown backup_strategy='${strategy}' (must be git/shadow/both)`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 5. Backup ref
|
|
85
|
-
if (repo) {
|
|
86
|
-
const guardRef = 'refs/guard/auto-backup';
|
|
87
|
-
const legacyRef = 'refs/heads/cursor-guard/auto-backup';
|
|
88
|
-
const exists = git(['rev-parse', '--verify', guardRef], { cwd: projectDir, allowFail: true });
|
|
89
|
-
const legacyExists = git(['rev-parse', '--verify', legacyRef], { cwd: projectDir, allowFail: true });
|
|
90
|
-
if (exists) {
|
|
91
|
-
const count = git(['rev-list', '--count', guardRef], { cwd: projectDir, allowFail: true }) || '?';
|
|
92
|
-
check('Backup ref', 'PASS', `refs/guard/auto-backup exists (${count} commits)`);
|
|
93
|
-
} else if (legacyExists) {
|
|
94
|
-
const count = git(['rev-list', '--count', legacyRef], { cwd: projectDir, allowFail: true }) || '?';
|
|
95
|
-
check('Backup ref', 'WARN', `legacy refs/heads/cursor-guard/auto-backup found (${count} commits) — run auto-backup once to migrate`);
|
|
96
|
-
} else {
|
|
97
|
-
check('Backup ref', 'WARN', 'refs/guard/auto-backup not created yet (will be created on first backup)');
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 6. Guard refs
|
|
102
|
-
if (repo) {
|
|
103
|
-
const refs = git(['for-each-ref', 'refs/guard/', '--format=%(refname)'], { cwd: projectDir, allowFail: true });
|
|
104
|
-
if (refs) {
|
|
105
|
-
const refList = refs.split('\n').filter(Boolean);
|
|
106
|
-
const preRestoreCount = refList.filter(r => r.includes('pre-restore/')).length;
|
|
107
|
-
check('Guard refs', 'PASS', `${refList.length} ref(s) found (${preRestoreCount} pre-restore snapshots)`);
|
|
108
|
-
} else {
|
|
109
|
-
check('Guard refs', 'WARN', 'no guard refs yet (created on first snapshot or restore)');
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// 7. Shadow copy directory
|
|
114
|
-
const backupDir = path.join(projectDir, '.cursor-guard-backup');
|
|
115
|
-
if (fs.existsSync(backupDir)) {
|
|
116
|
-
let snapCount = 0;
|
|
117
|
-
let totalBytes = 0;
|
|
118
|
-
try {
|
|
119
|
-
const dirs = fs.readdirSync(backupDir, { withFileTypes: true })
|
|
120
|
-
.filter(d => d.isDirectory() && (/^\d{8}_\d{6}$/.test(d.name) || d.name.startsWith('pre-restore-')));
|
|
121
|
-
snapCount = dirs.length;
|
|
122
|
-
} catch { /* ignore */ }
|
|
123
|
-
try {
|
|
124
|
-
const allFiles = walkDir(backupDir, backupDir);
|
|
125
|
-
for (const f of allFiles) {
|
|
126
|
-
try { totalBytes += fs.statSync(f.full).size; } catch { /* skip */ }
|
|
127
|
-
}
|
|
128
|
-
} catch { /* ignore */ }
|
|
129
|
-
const totalMB = (totalBytes / (1024 * 1024)).toFixed(1);
|
|
130
|
-
check('Shadow copies', 'PASS', `${snapCount} snapshot(s), ${totalMB} MB total`);
|
|
131
|
-
} else {
|
|
132
|
-
check('Shadow copies', 'WARN', '.cursor-guard-backup/ not found (will be created on first shadow backup)');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// 8. .gitignore / exclude coverage
|
|
136
|
-
if (repo) {
|
|
137
|
-
const ignored = git(['check-ignore', '.cursor-guard-backup/test'], { cwd: projectDir, allowFail: true });
|
|
138
|
-
if (ignored) {
|
|
139
|
-
check('Backup dir ignored', 'PASS', '.cursor-guard-backup/ is git-ignored');
|
|
140
|
-
} else {
|
|
141
|
-
check('Backup dir ignored', 'WARN', '.cursor-guard-backup/ may NOT be git-ignored — backup changes could trigger commits');
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// 9. Config field validation
|
|
146
|
-
if (loaded) {
|
|
147
|
-
const validStrategies = ['git', 'shadow', 'both'];
|
|
148
|
-
if (cfg.backup_strategy && !validStrategies.includes(cfg.backup_strategy)) {
|
|
149
|
-
check('Config: backup_strategy', 'FAIL', `invalid value '${cfg.backup_strategy}'`);
|
|
150
|
-
}
|
|
151
|
-
const validPreRestore = ['always', 'ask', 'never'];
|
|
152
|
-
if (cfg.pre_restore_backup && !validPreRestore.includes(cfg.pre_restore_backup)) {
|
|
153
|
-
check('Config: pre_restore_backup', 'FAIL', `invalid value '${cfg.pre_restore_backup}'`);
|
|
154
|
-
} else if (cfg.pre_restore_backup === 'never') {
|
|
155
|
-
check('Config: pre_restore_backup', 'WARN', "set to 'never' — restores won't auto-preserve current version");
|
|
156
|
-
}
|
|
157
|
-
if (cfg.auto_backup_interval_seconds && cfg.auto_backup_interval_seconds < 5) {
|
|
158
|
-
check('Config: interval', 'WARN', `${cfg.auto_backup_interval_seconds}s is below minimum (5s), will be clamped`);
|
|
159
|
-
}
|
|
160
|
-
if (cfg.retention && cfg.retention.mode) {
|
|
161
|
-
const validModes = ['days', 'count', 'size'];
|
|
162
|
-
if (!validModes.includes(cfg.retention.mode)) {
|
|
163
|
-
check('Config: retention.mode', 'FAIL', `invalid value '${cfg.retention.mode}'`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
if (cfg.git_retention && cfg.git_retention.mode) {
|
|
167
|
-
const validGitModes = ['days', 'count'];
|
|
168
|
-
if (!validGitModes.includes(cfg.git_retention.mode)) {
|
|
169
|
-
check('Config: git_retention.mode', 'FAIL', `invalid value '${cfg.git_retention.mode}'`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// 10. Protect / Ignore effectiveness
|
|
175
|
-
if (loaded && cfg.protect.length > 0) {
|
|
176
|
-
const allFiles = walkDir(projectDir, projectDir);
|
|
177
|
-
let protectedCount = 0;
|
|
178
|
-
for (const f of allFiles) {
|
|
179
|
-
if (matchesAny(cfg.protect, f.rel)) protectedCount++;
|
|
180
|
-
}
|
|
181
|
-
check('Protect patterns', 'PASS', `${protectedCount} / ${allFiles.length} files matched by protect patterns`);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// 11. Disk space
|
|
185
|
-
const freeGB = diskFreeGB(projectDir);
|
|
186
|
-
if (freeGB !== null) {
|
|
187
|
-
const rounded = freeGB.toFixed(1);
|
|
188
|
-
if (freeGB < 1) {
|
|
189
|
-
check('Disk space', 'FAIL', `${rounded} GB free — critically low`);
|
|
190
|
-
} else if (freeGB < 5) {
|
|
191
|
-
check('Disk space', 'WARN', `${rounded} GB free`);
|
|
192
|
-
} else {
|
|
193
|
-
check('Disk space', 'PASS', `${rounded} GB free`);
|
|
194
|
-
}
|
|
195
|
-
} else {
|
|
196
|
-
check('Disk space', 'WARN', 'could not determine free space');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// 12. Lock file
|
|
200
|
-
const lockFile = gDir
|
|
201
|
-
? path.join(gDir, 'cursor-guard.lock')
|
|
202
|
-
: path.join(backupDir, 'cursor-guard.lock');
|
|
203
|
-
if (fs.existsSync(lockFile)) {
|
|
204
|
-
let content = '';
|
|
205
|
-
try { content = fs.readFileSync(lockFile, 'utf-8').trim(); } catch { /* ignore */ }
|
|
206
|
-
check('Lock file', 'WARN', `lock file exists — another instance may be running. ${content}`);
|
|
207
|
-
} else {
|
|
208
|
-
check('Lock file', 'PASS', 'no lock file (no running instance)');
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// 13. Node.js version
|
|
212
|
-
const nodeVer = process.version;
|
|
213
|
-
const major = parseInt(nodeVer.slice(1), 10);
|
|
214
|
-
if (major >= 18) {
|
|
215
|
-
check('Node.js', 'PASS', `${nodeVer}`);
|
|
216
|
-
} else {
|
|
217
|
-
check('Node.js', 'WARN', `${nodeVer} — recommended >=18`);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Summary
|
|
221
|
-
console.log('');
|
|
222
|
-
console.log(color.cyan('=== Summary ==='));
|
|
223
|
-
const summaryColor = fail > 0 ? 'red' : warn > 0 ? 'yellow' : 'green';
|
|
224
|
-
console.log(color[summaryColor](` PASS: ${pass} | WARN: ${warn} | FAIL: ${fail}`));
|
|
225
|
-
console.log('');
|
|
226
|
-
if (fail > 0) {
|
|
227
|
-
console.log(color.red(' Fix FAIL items before relying on Cursor Guard.'));
|
|
228
|
-
} else if (warn > 0) {
|
|
229
|
-
console.log(color.yellow(' Review WARN items to ensure everything works as expected.'));
|
|
230
|
-
} else {
|
|
231
|
-
console.log(color.green(' All checks passed. Cursor Guard is ready.'));
|
|
232
|
-
}
|
|
233
|
-
console.log('');
|
|
234
|
-
|
|
235
|
-
return fail > 0 ? 1 : 0;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
module.exports = { runDoctor };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { color } = require('./utils');
|
|
4
|
+
const { runDiagnostics } = require('./core/doctor');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Run doctor checks and print formatted output to console.
|
|
8
|
+
* Thin CLI wrapper over core/doctor.js.
|
|
9
|
+
*/
|
|
10
|
+
function runDoctor(projectDir) {
|
|
11
|
+
const { checks, summary } = runDiagnostics(projectDir);
|
|
12
|
+
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log(color.cyan('=== Cursor Guard Doctor ==='));
|
|
15
|
+
console.log(color.cyan(` Target: ${projectDir}`));
|
|
16
|
+
console.log('');
|
|
17
|
+
|
|
18
|
+
for (const c of checks) {
|
|
19
|
+
const tag = `[${c.status}]`;
|
|
20
|
+
const line = c.detail ? ` ${tag} ${c.name} — ${c.detail}` : ` ${tag} ${c.name}`;
|
|
21
|
+
switch (c.status) {
|
|
22
|
+
case 'PASS': console.log(color.green(line)); break;
|
|
23
|
+
case 'WARN': console.log(color.yellow(line)); break;
|
|
24
|
+
case 'FAIL': console.log(color.red(line)); break;
|
|
25
|
+
default: console.log(line);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(color.cyan('=== Summary ==='));
|
|
31
|
+
const summaryColor = summary.fail > 0 ? 'red' : summary.warn > 0 ? 'yellow' : 'green';
|
|
32
|
+
console.log(color[summaryColor](` PASS: ${summary.pass} | WARN: ${summary.warn} | FAIL: ${summary.fail}`));
|
|
33
|
+
console.log('');
|
|
34
|
+
if (summary.fail > 0) {
|
|
35
|
+
console.log(color.red(' Fix FAIL items before relying on Cursor Guard.'));
|
|
36
|
+
} else if (summary.warn > 0) {
|
|
37
|
+
console.log(color.yellow(' Review WARN items to ensure everything works as expected.'));
|
|
38
|
+
} else {
|
|
39
|
+
console.log(color.green(' All checks passed. Cursor Guard is ready.'));
|
|
40
|
+
}
|
|
41
|
+
console.log('');
|
|
42
|
+
|
|
43
|
+
return summary.fail > 0 ? 1 : 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { runDoctor };
|