cursor-guard 4.9.6 → 4.9.9
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 +124 -61
- package/README.zh-CN.md +121 -58
- package/ROADMAP.md +48 -20
- package/SKILL.md +1 -1
- package/docs/RELEASE.md +196 -0
- package/package.json +4 -2
- package/references/dashboard/public/app.js +79 -79
- package/references/dashboard/public/style.css +264 -159
- package/references/lib/core/core.test.js +139 -101
- package/references/lib/core/snapshot.js +8 -4
- package/references/mcp/server.js +73 -72
- package/references/vscode-extension/build-vsix.js +7 -5
- package/references/vscode-extension/dist/{cursor-guard-ide-4.9.6.vsix → cursor-guard-ide-4.9.9.vsix} +0 -0
- package/references/vscode-extension/dist/dashboard/public/app.js +79 -79
- package/references/vscode-extension/dist/dashboard/public/style.css +264 -159
- package/references/vscode-extension/dist/extension.js +704 -498
- package/references/vscode-extension/dist/guard-version.json +1 -1
- package/references/vscode-extension/dist/lib/core/snapshot.js +8 -4
- package/references/vscode-extension/dist/lib/dashboard-manager.js +70 -13
- package/references/vscode-extension/dist/lib/locale.js +36 -0
- package/references/vscode-extension/dist/lib/sidebar-webview.js +1484 -502
- package/references/vscode-extension/dist/mcp/server.js +11 -7
- package/references/vscode-extension/dist/media/brand-placeholder.png +0 -0
- package/references/vscode-extension/dist/package.json +1 -1
- package/references/vscode-extension/dist/skill/ROADMAP.md +48 -20
- package/references/vscode-extension/dist/skill/SKILL.md +1 -1
- package/references/vscode-extension/extension.js +704 -498
- package/references/vscode-extension/lib/dashboard-manager.js +70 -13
- package/references/vscode-extension/lib/locale.js +36 -0
- package/references/vscode-extension/lib/sidebar-webview.js +1484 -502
- package/references/vscode-extension/media/brand-placeholder.png +0 -0
- package/references/vscode-extension/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"4.9.
|
|
1
|
+
{"version":"4.9.9"}
|
|
@@ -91,6 +91,8 @@ function buildCommitMessage(ts, opts) {
|
|
|
91
91
|
* @param {string} [opts.context.intent] - Why this snapshot was created (e.g. "refactoring auth middleware")
|
|
92
92
|
* @param {string} [opts.context.agent] - AI model identifier (e.g. "claude-4-opus")
|
|
93
93
|
* @param {string} [opts.context.session] - Conversation/session ID
|
|
94
|
+
* @param {boolean} [opts.allowEmptyTree] - If true, still create a commit when the snapshot tree equals the previous ref (empty / bookmark commit). Auto-backup should omit this; explicit manual snapshots should set it.
|
|
95
|
+
* @param {boolean} [opts.fullWorkspaceSnapshot] - If true, ignore `cfg.protect` when building the snapshot tree (still apply `ignore` / secrets). Use for IDE/MCP "snapshot everything" so edits outside protect patterns are not invisible to the snapshot.
|
|
94
96
|
* @returns {{ status: 'created'|'skipped'|'error', commitHash?: string, shortHash?: string, fileCount?: number, reason?: string, error?: string, secretsExcluded?: string[] }}
|
|
95
97
|
*/
|
|
96
98
|
function createGitSnapshot(projectDir, cfg, opts = {}) {
|
|
@@ -99,6 +101,8 @@ function createGitSnapshot(projectDir, cfg, opts = {}) {
|
|
|
99
101
|
const gDir = getGitDir(projectDir);
|
|
100
102
|
if (!gDir) return { status: 'error', error: 'not a git repository' };
|
|
101
103
|
|
|
104
|
+
const narrowProtect = cfg.protect.length > 0 && !opts.fullWorkspaceSnapshot;
|
|
105
|
+
|
|
102
106
|
const guardIndex = path.join(gDir, 'cursor-guard-index');
|
|
103
107
|
const guardIndexLock = guardIndex + '.lock';
|
|
104
108
|
const env = { ...process.env, GIT_INDEX_FILE: guardIndex };
|
|
@@ -109,7 +113,7 @@ function createGitSnapshot(projectDir, cfg, opts = {}) {
|
|
|
109
113
|
try {
|
|
110
114
|
const parentHash = git(['rev-parse', '--verify', branchRef], { cwd, allowFail: true });
|
|
111
115
|
|
|
112
|
-
if (
|
|
116
|
+
if (narrowProtect) {
|
|
113
117
|
// protect uses strict matching (full path only, no basename fallback)
|
|
114
118
|
// so *.js only matches root-level js files, not nested ones
|
|
115
119
|
execFileSync('git', ['add', '-A'], { cwd, env, stdio: 'pipe' });
|
|
@@ -132,7 +136,7 @@ function createGitSnapshot(projectDir, cfg, opts = {}) {
|
|
|
132
136
|
? git(['rev-parse', `${branchRef}^{tree}`], { cwd, allowFail: true })
|
|
133
137
|
: null;
|
|
134
138
|
|
|
135
|
-
if (newTree === parentTree) {
|
|
139
|
+
if (newTree === parentTree && !opts.allowEmptyTree) {
|
|
136
140
|
return { status: 'skipped', reason: 'tree unchanged' };
|
|
137
141
|
}
|
|
138
142
|
|
|
@@ -156,7 +160,7 @@ function createGitSnapshot(projectDir, cfg, opts = {}) {
|
|
|
156
160
|
: 'M';
|
|
157
161
|
const fileName = filePart.split('\t').pop();
|
|
158
162
|
if (matchesAny(cfg.ignore, fileName) || matchesAny(cfg.ignore, path.basename(fileName))) continue;
|
|
159
|
-
if (
|
|
163
|
+
if (narrowProtect && !matchesAny(cfg.protect, fileName, { strict: true })) continue;
|
|
160
164
|
groups[key].push(fileName);
|
|
161
165
|
}
|
|
162
166
|
changedCount = Object.values(groups).reduce((sum, arr) => sum + arr.length, 0);
|
|
@@ -202,7 +206,7 @@ function createGitSnapshot(projectDir, cfg, opts = {}) {
|
|
|
202
206
|
if (lsInitial) {
|
|
203
207
|
const files = lsInitial.split('\n').filter(Boolean)
|
|
204
208
|
.filter(f => !matchesAny(cfg.ignore, f) && !matchesAny(cfg.ignore, path.basename(f)))
|
|
205
|
-
.filter(f =>
|
|
209
|
+
.filter(f => !narrowProtect || matchesAny(cfg.protect, f, { strict: true }));
|
|
206
210
|
changedCount = files.length;
|
|
207
211
|
const sample = files.slice(0, 5).join(', ');
|
|
208
212
|
|
|
@@ -7,11 +7,13 @@ const { spawn } = require('child_process');
|
|
|
7
7
|
const { guardPath } = require('./paths');
|
|
8
8
|
|
|
9
9
|
const CONFIG_FILE = '.cursor-guard.json';
|
|
10
|
+
const WATCHER_START_GRACE_MS = 8000;
|
|
10
11
|
|
|
11
12
|
class DashboardManager {
|
|
12
13
|
constructor() {
|
|
13
14
|
this._instance = null;
|
|
14
15
|
this._serverModule = null;
|
|
16
|
+
this._startingWatchers = new Map();
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
get running() { return !!this._instance; }
|
|
@@ -89,12 +91,51 @@ class DashboardManager {
|
|
|
89
91
|
const { createGitSnapshot } = require(guardPath('lib', 'core', 'snapshot'));
|
|
90
92
|
const { loadConfig } = require(guardPath('lib', 'utils'));
|
|
91
93
|
const { cfg } = loadConfig(projectPath);
|
|
92
|
-
return createGitSnapshot(projectPath, cfg, {
|
|
94
|
+
return createGitSnapshot(projectPath, cfg, {
|
|
95
|
+
branchRef: 'refs/guard/snapshot',
|
|
96
|
+
message: `guard: manual snapshot via IDE (${new Date().toISOString()})`,
|
|
97
|
+
context: { trigger: 'manual' },
|
|
98
|
+
allowEmptyTree: true,
|
|
99
|
+
// Match user expectation: "Snapshot now" captures the whole repo (except ignore/secrets), not only protect globs
|
|
100
|
+
fullWorkspaceSnapshot: true,
|
|
101
|
+
});
|
|
93
102
|
} catch (e) {
|
|
94
103
|
return { status: 'error', error: e.message };
|
|
95
104
|
}
|
|
96
105
|
}
|
|
97
106
|
|
|
107
|
+
_getWatcherLockPath(projectPath) {
|
|
108
|
+
try {
|
|
109
|
+
const { gitAvailable, isGitRepo, gitDir: getGitDir } = require(guardPath('lib', 'utils'));
|
|
110
|
+
const repo = gitAvailable() && isGitRepo(projectPath);
|
|
111
|
+
if (repo) {
|
|
112
|
+
const gDir = getGitDir(projectPath);
|
|
113
|
+
if (gDir) return path.join(gDir, 'cursor-guard.lock');
|
|
114
|
+
}
|
|
115
|
+
} catch { /* ignore */ }
|
|
116
|
+
return path.join(projectPath, '.cursor-guard-backup', 'cursor-guard.lock');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_getPendingWatcherPid(projectPath) {
|
|
120
|
+
const pending = this._startingWatchers.get(projectPath);
|
|
121
|
+
if (!pending) return null;
|
|
122
|
+
try {
|
|
123
|
+
process.kill(pending.pid, 0);
|
|
124
|
+
return pending.pid;
|
|
125
|
+
} catch {
|
|
126
|
+
this._startingWatchers.delete(projectPath);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
_clearPendingWatcher(projectPath, pid) {
|
|
132
|
+
const pending = this._startingWatchers.get(projectPath);
|
|
133
|
+
if (!pending) return;
|
|
134
|
+
if (pid == null || pending.pid === pid) {
|
|
135
|
+
this._startingWatchers.delete(projectPath);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
98
139
|
startWatcher(projectPath) {
|
|
99
140
|
if (!projectPath) return null;
|
|
100
141
|
const existingPid = this.getWatcherPid(projectPath);
|
|
@@ -106,33 +147,49 @@ class DashboardManager {
|
|
|
106
147
|
detached: true,
|
|
107
148
|
env: { ...process.env, GUARD_SPAWNED_BY_EXT: '1' },
|
|
108
149
|
});
|
|
150
|
+
this._startingWatchers.set(projectPath, { pid: child.pid, startedAt: Date.now() });
|
|
151
|
+
const clearPending = () => this._clearPendingWatcher(projectPath, child.pid);
|
|
152
|
+
child.once('exit', clearPending);
|
|
153
|
+
child.once('error', clearPending);
|
|
154
|
+
setTimeout(clearPending, WATCHER_START_GRACE_MS);
|
|
109
155
|
child.unref();
|
|
110
156
|
return child.pid;
|
|
111
157
|
}
|
|
112
158
|
|
|
113
159
|
stopWatcher(projectPath) {
|
|
114
160
|
if (!projectPath) return false;
|
|
161
|
+
const pendingPid = this._getPendingWatcherPid(projectPath);
|
|
162
|
+
if (pendingPid) {
|
|
163
|
+
try { process.kill(pendingPid, 'SIGTERM'); } catch { /* ignore */ }
|
|
164
|
+
this._clearPendingWatcher(projectPath, pendingPid);
|
|
165
|
+
}
|
|
115
166
|
try {
|
|
116
|
-
const lockPath =
|
|
167
|
+
const lockPath = this._getWatcherLockPath(projectPath);
|
|
117
168
|
if (!fs.existsSync(lockPath)) return false;
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return true;
|
|
169
|
+
const content = fs.readFileSync(lockPath, 'utf-8');
|
|
170
|
+
const pidMatch = content.match(/pid=(\d+)/);
|
|
171
|
+
if (pidMatch) {
|
|
172
|
+
process.kill(parseInt(pidMatch[1], 10), 'SIGTERM');
|
|
123
173
|
}
|
|
174
|
+
try { fs.unlinkSync(lockPath); } catch { /* ok */ }
|
|
175
|
+
return true;
|
|
124
176
|
} catch { /* ok */ }
|
|
125
|
-
return
|
|
177
|
+
return !!pendingPid;
|
|
126
178
|
}
|
|
127
179
|
|
|
128
180
|
getWatcherPid(projectPath) {
|
|
181
|
+
const pendingPid = this._getPendingWatcherPid(projectPath);
|
|
182
|
+
if (pendingPid) return pendingPid;
|
|
129
183
|
try {
|
|
130
|
-
const lockPath =
|
|
184
|
+
const lockPath = this._getWatcherLockPath(projectPath);
|
|
131
185
|
if (!fs.existsSync(lockPath)) return null;
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
186
|
+
const content = fs.readFileSync(lockPath, 'utf-8');
|
|
187
|
+
const pidMatch = content.match(/pid=(\d+)/);
|
|
188
|
+
if (pidMatch) {
|
|
189
|
+
const pid = parseInt(pidMatch[1], 10);
|
|
190
|
+
process.kill(pid, 0);
|
|
191
|
+
this._clearPendingWatcher(projectPath, pid);
|
|
192
|
+
return pid;
|
|
136
193
|
}
|
|
137
194
|
} catch { /* not running */ }
|
|
138
195
|
return null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const vscode = require('vscode');
|
|
4
|
+
|
|
5
|
+
const EXTENSION_LOCALE_KEY = 'cursorGuard.locale';
|
|
6
|
+
|
|
7
|
+
function normalizeLocale(locale) {
|
|
8
|
+
return locale === 'zh-CN' ? 'zh-CN' : 'en-US';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function detectLocale() {
|
|
12
|
+
return normalizeLocale(
|
|
13
|
+
(vscode.env.language || '').toLowerCase().startsWith('zh') ? 'zh-CN' : 'en-US'
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getLocale(storage) {
|
|
18
|
+
if (!storage || typeof storage.get !== 'function') return detectLocale();
|
|
19
|
+
return normalizeLocale(storage.get(EXTENSION_LOCALE_KEY) || detectLocale());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function setLocale(storage, locale) {
|
|
23
|
+
const normalized = normalizeLocale(locale);
|
|
24
|
+
if (storage && typeof storage.update === 'function') {
|
|
25
|
+
await storage.update(EXTENSION_LOCALE_KEY, normalized);
|
|
26
|
+
}
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = {
|
|
31
|
+
EXTENSION_LOCALE_KEY,
|
|
32
|
+
normalizeLocale,
|
|
33
|
+
detectLocale,
|
|
34
|
+
getLocale,
|
|
35
|
+
setLocale,
|
|
36
|
+
};
|