cursor-guard 4.9.9 → 4.9.15
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 +697 -697
- package/README.zh-CN.md +696 -696
- package/ROADMAP.md +1775 -1720
- package/SKILL.md +631 -629
- package/docs/RELEASE.md +197 -196
- package/docs/SNAPSHOT-BOOKMARK.md +47 -0
- package/package.json +70 -69
- package/references/dashboard/public/app.js +2079 -1832
- package/references/dashboard/public/style.css +1660 -1573
- package/references/dashboard/server.js +197 -4
- package/references/lib/core/backups.js +509 -492
- package/references/lib/core/core.test.js +1761 -1616
- package/references/lib/core/snapshot.js +441 -369
- package/references/mcp/mcp.test.js +381 -362
- package/references/mcp/server.js +404 -347
- package/references/vscode-extension/dist/{cursor-guard-ide-4.9.9.vsix → cursor-guard-ide-4.9.15.vsix} +0 -0
- package/references/vscode-extension/dist/dashboard/public/app.js +2079 -1832
- package/references/vscode-extension/dist/dashboard/public/style.css +1660 -1573
- package/references/vscode-extension/dist/dashboard/server.js +197 -4
- package/references/vscode-extension/dist/extension.js +780 -704
- package/references/vscode-extension/dist/guard-version.json +1 -1
- package/references/vscode-extension/dist/lib/auto-setup.js +201 -192
- package/references/vscode-extension/dist/lib/core/backups.js +509 -492
- package/references/vscode-extension/dist/lib/core/snapshot.js +441 -369
- package/references/vscode-extension/dist/lib/poller.js +161 -21
- package/references/vscode-extension/dist/lib/sidebar-webview.js +22 -0
- package/references/vscode-extension/dist/mcp/server.js +152 -35
- package/references/vscode-extension/dist/package.json +7 -1
- package/references/vscode-extension/dist/skill/ROADMAP.md +1775 -1720
- package/references/vscode-extension/dist/skill/SKILL.md +631 -629
- package/references/vscode-extension/extension.js +780 -704
- package/references/vscode-extension/lib/auto-setup.js +201 -192
- package/references/vscode-extension/lib/poller.js +161 -21
- package/references/vscode-extension/lib/sidebar-webview.js +22 -0
- package/references/vscode-extension/package.json +146 -140
|
@@ -1,192 +1,201 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
function detectIdeDir(vscode) {
|
|
7
|
-
const appName = (vscode.env.appName || '').toLowerCase();
|
|
8
|
-
const home = process.env.USERPROFILE || process.env.HOME || '';
|
|
9
|
-
|
|
10
|
-
const ideDirs = [];
|
|
11
|
-
|
|
12
|
-
if (appName.includes('cursor')) {
|
|
13
|
-
ideDirs.push('.cursor');
|
|
14
|
-
} else if (appName.includes('windsurf')) {
|
|
15
|
-
ideDirs.push('.windsurf');
|
|
16
|
-
} else if (appName.includes('trae')) {
|
|
17
|
-
ideDirs.push('.trae');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
ideDirs.push('.cursor', '.windsurf', '.vscode');
|
|
21
|
-
|
|
22
|
-
const seen = new Set();
|
|
23
|
-
for (const dir of ideDirs) {
|
|
24
|
-
if (seen.has(dir)) continue;
|
|
25
|
-
seen.add(dir);
|
|
26
|
-
const full = path.join(home, dir);
|
|
27
|
-
if (fs.existsSync(full)) return { dirName: dir, homePath: full };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return { dirName: ideDirs[0], homePath: path.join(home, ideDirs[0]) };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function getExtensionRoot(context) {
|
|
34
|
-
return context.extensionPath;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function findBundledSkill(extRoot) {
|
|
38
|
-
const skillDir = path.join(extRoot, 'skill');
|
|
39
|
-
if (fs.existsSync(path.join(skillDir, 'SKILL.md'))) return skillDir;
|
|
40
|
-
const fromRefs = path.resolve(extRoot, '..', '..', 'SKILL.md');
|
|
41
|
-
if (fs.existsSync(fromRefs)) return path.resolve(extRoot, '..', '..');
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function autoSetup(context, vscode) {
|
|
46
|
-
const extRoot = getExtensionRoot(context);
|
|
47
|
-
const { dirName, homePath } = detectIdeDir(vscode);
|
|
48
|
-
const workspaceFolders = vscode.workspace.workspaceFolders;
|
|
49
|
-
const wsRoot = workspaceFolders?.[0]?.uri.fsPath;
|
|
50
|
-
|
|
51
|
-
const actions = [];
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function detectIdeDir(vscode) {
|
|
7
|
+
const appName = (vscode.env.appName || '').toLowerCase();
|
|
8
|
+
const home = process.env.USERPROFILE || process.env.HOME || '';
|
|
9
|
+
|
|
10
|
+
const ideDirs = [];
|
|
11
|
+
|
|
12
|
+
if (appName.includes('cursor')) {
|
|
13
|
+
ideDirs.push('.cursor');
|
|
14
|
+
} else if (appName.includes('windsurf')) {
|
|
15
|
+
ideDirs.push('.windsurf');
|
|
16
|
+
} else if (appName.includes('trae')) {
|
|
17
|
+
ideDirs.push('.trae');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
ideDirs.push('.cursor', '.windsurf', '.vscode');
|
|
21
|
+
|
|
22
|
+
const seen = new Set();
|
|
23
|
+
for (const dir of ideDirs) {
|
|
24
|
+
if (seen.has(dir)) continue;
|
|
25
|
+
seen.add(dir);
|
|
26
|
+
const full = path.join(home, dir);
|
|
27
|
+
if (fs.existsSync(full)) return { dirName: dir, homePath: full };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { dirName: ideDirs[0], homePath: path.join(home, ideDirs[0]) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getExtensionRoot(context) {
|
|
34
|
+
return context.extensionPath;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function findBundledSkill(extRoot) {
|
|
38
|
+
const skillDir = path.join(extRoot, 'skill');
|
|
39
|
+
if (fs.existsSync(path.join(skillDir, 'SKILL.md'))) return skillDir;
|
|
40
|
+
const fromRefs = path.resolve(extRoot, '..', '..', 'SKILL.md');
|
|
41
|
+
if (fs.existsSync(fromRefs)) return path.resolve(extRoot, '..', '..');
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function autoSetup(context, vscode) {
|
|
46
|
+
const extRoot = getExtensionRoot(context);
|
|
47
|
+
const { dirName, homePath } = detectIdeDir(vscode);
|
|
48
|
+
const workspaceFolders = vscode.workspace.workspaceFolders;
|
|
49
|
+
const wsRoot = workspaceFolders?.[0]?.uri.fsPath;
|
|
50
|
+
|
|
51
|
+
const actions = [];
|
|
52
|
+
let skillPath = null;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const skillResult = installAgentSkill(extRoot, homePath, dirName);
|
|
56
|
+
actions.push(...skillResult.actions);
|
|
57
|
+
skillPath = skillResult.skillPath;
|
|
58
|
+
} catch { /* non-critical */ }
|
|
59
|
+
try { actions.push(...autoRegisterMcp(extRoot, homePath, wsRoot)); } catch { /* non-critical */ }
|
|
60
|
+
if (wsRoot) {
|
|
61
|
+
try { actions.push(...autoCreateConfig(extRoot, wsRoot)); } catch { /* non-critical */ }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { actions, skillPath };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Copy / link bundled Agent Skill into ~/.cursor/skills/cursor-guard (or IDE-specific home). */
|
|
68
|
+
function installAgentSkill(extRoot, homePath, dirName) {
|
|
69
|
+
const actions = [];
|
|
70
|
+
const skillSrc = findBundledSkill(extRoot);
|
|
71
|
+
if (!skillSrc) return { actions, skillPath: null };
|
|
72
|
+
|
|
73
|
+
const skillTarget = path.join(homePath, 'skills', 'cursor-guard');
|
|
74
|
+
fs.mkdirSync(skillTarget, { recursive: true });
|
|
75
|
+
|
|
76
|
+
// ── Install/update SKILL.md and ROADMAP.md ──
|
|
77
|
+
const skillMdSrc = path.join(skillSrc, 'SKILL.md');
|
|
78
|
+
const skillMdTarget = path.join(skillTarget, 'SKILL.md');
|
|
79
|
+
if (fs.existsSync(skillMdSrc) && !fs.existsSync(skillMdTarget)) {
|
|
80
|
+
fs.copyFileSync(skillMdSrc, skillMdTarget);
|
|
81
|
+
actions.push('SKILL.md installed');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const roadmapSrc = path.join(skillSrc, 'ROADMAP.md');
|
|
85
|
+
const roadmapDst = path.join(skillTarget, 'ROADMAP.md');
|
|
86
|
+
if (fs.existsSync(roadmapSrc) && !fs.existsSync(roadmapDst)) {
|
|
87
|
+
fs.copyFileSync(roadmapSrc, roadmapDst);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Ensure references/ junction exists (runs even for existing installations) ──
|
|
91
|
+
const refsTarget = path.join(skillTarget, 'references');
|
|
92
|
+
const refsIsJunction = _isSymlinkOrJunction(refsTarget);
|
|
93
|
+
const refsIsPlainDir = !refsIsJunction && fs.existsSync(refsTarget);
|
|
94
|
+
const refsMissingRuntime = refsIsPlainDir && !fs.existsSync(path.join(refsTarget, 'mcp'));
|
|
95
|
+
|
|
96
|
+
if (!fs.existsSync(refsTarget) || refsMissingRuntime) {
|
|
97
|
+
// Remove old plain directory if it only has docs (no runtime)
|
|
98
|
+
if (refsMissingRuntime) {
|
|
99
|
+
try { fs.rmSync(refsTarget, { recursive: true, force: true }); } catch { /* ok */ }
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
fs.symlinkSync(extRoot, refsTarget, 'junction');
|
|
103
|
+
actions.push('references/ linked');
|
|
104
|
+
} catch {
|
|
105
|
+
fs.mkdirSync(refsTarget, { recursive: true });
|
|
106
|
+
_copyDocFiles(skillSrc, refsTarget);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Ensure package.json exists ──
|
|
111
|
+
const pkgDst = path.join(skillTarget, 'package.json');
|
|
112
|
+
if (!fs.existsSync(pkgDst)) {
|
|
113
|
+
const pkgSrc = path.join(extRoot, '..', '..', 'package.json');
|
|
114
|
+
const guardVer = path.join(extRoot, 'guard-version.json');
|
|
115
|
+
if (fs.existsSync(pkgSrc)) {
|
|
116
|
+
fs.copyFileSync(pkgSrc, pkgDst);
|
|
117
|
+
} else if (fs.existsSync(guardVer)) {
|
|
118
|
+
fs.copyFileSync(guardVer, pkgDst);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { actions, skillPath: skillTarget };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function _isSymlinkOrJunction(p) {
|
|
126
|
+
try {
|
|
127
|
+
const stat = fs.lstatSync(p);
|
|
128
|
+
return stat.isSymbolicLink();
|
|
129
|
+
} catch { return false; }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function _copyDocFiles(skillSrc, refsTarget) {
|
|
133
|
+
const docs = [
|
|
134
|
+
'config-reference.md', 'config-reference.zh-CN.md',
|
|
135
|
+
'recovery.md', 'cursor-guard.schema.json', 'cursor-guard.example.json',
|
|
136
|
+
];
|
|
137
|
+
for (const f of docs) {
|
|
138
|
+
const src = path.join(skillSrc, f);
|
|
139
|
+
if (fs.existsSync(src)) fs.copyFileSync(src, path.join(refsTarget, f));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function autoRegisterMcp(extRoot, homePath, wsRoot) {
|
|
144
|
+
const actions = [];
|
|
145
|
+
const mcpServerPath = path.join(extRoot, 'mcp', 'server.js');
|
|
146
|
+
if (!fs.existsSync(mcpServerPath)) return actions;
|
|
147
|
+
|
|
148
|
+
const mcpJsonPaths = [
|
|
149
|
+
wsRoot ? path.join(wsRoot, '.cursor', 'mcp.json') : null,
|
|
150
|
+
wsRoot ? path.join(wsRoot, '.windsurf', 'mcp.json') : null,
|
|
151
|
+
path.join(homePath, 'mcp.json'),
|
|
152
|
+
].filter(Boolean);
|
|
153
|
+
|
|
154
|
+
for (const mcpJsonPath of mcpJsonPaths) {
|
|
155
|
+
const dir = path.dirname(mcpJsonPath);
|
|
156
|
+
if (!fs.existsSync(dir)) continue;
|
|
157
|
+
|
|
158
|
+
let mcpConfig = { mcpServers: {} };
|
|
159
|
+
if (fs.existsSync(mcpJsonPath)) {
|
|
160
|
+
try { mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8')); } catch { continue; }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
164
|
+
if (mcpConfig.mcpServers['cursor-guard']) return actions;
|
|
165
|
+
|
|
166
|
+
mcpConfig.mcpServers['cursor-guard'] = {
|
|
167
|
+
command: 'node',
|
|
168
|
+
args: [mcpServerPath.replace(/\\/g, '/')],
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2));
|
|
172
|
+
actions.push('MCP registered');
|
|
173
|
+
return actions;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return actions;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function autoCreateConfig(extRoot, wsRoot) {
|
|
180
|
+
const actions = [];
|
|
181
|
+
const configTarget = path.join(wsRoot, '.cursor-guard.json');
|
|
182
|
+
if (fs.existsSync(configTarget)) return actions;
|
|
183
|
+
|
|
184
|
+
const exampleSrc = path.join(extRoot, 'skill', 'cursor-guard.example.json');
|
|
185
|
+
const exampleFromRefs = path.join(extRoot, '..', 'cursor-guard.example.json');
|
|
186
|
+
const src = fs.existsSync(exampleSrc) ? exampleSrc : fs.existsSync(exampleFromRefs) ? exampleFromRefs : null;
|
|
187
|
+
|
|
188
|
+
if (src) {
|
|
189
|
+
fs.copyFileSync(src, configTarget);
|
|
190
|
+
actions.push('.cursor-guard.json created');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return actions;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = {
|
|
197
|
+
autoSetup,
|
|
198
|
+
detectIdeDir,
|
|
199
|
+
installAgentSkill,
|
|
200
|
+
getExtensionRoot,
|
|
201
|
+
};
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { guardPath } = require('./paths');
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
/** Safety net if fs.watch misses (platform limits); primary updates are filesystem events. */
|
|
8
|
+
const FALLBACK_POLL_MS = 180000;
|
|
9
|
+
|
|
10
|
+
function tryWatchDirRecursive(dir, cb) {
|
|
11
|
+
try {
|
|
12
|
+
return fs.watch(dir, { recursive: true }, cb);
|
|
13
|
+
} catch {
|
|
14
|
+
try {
|
|
15
|
+
return fs.watch(dir, cb);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
6
21
|
|
|
7
22
|
class Poller {
|
|
8
23
|
constructor(dashMgr) {
|
|
@@ -11,6 +26,12 @@ class Poller {
|
|
|
11
26
|
this._listeners = [];
|
|
12
27
|
this._data = new Map();
|
|
13
28
|
this._pollRunning = false;
|
|
29
|
+
this._pollAgainAfter = false;
|
|
30
|
+
this._pollWaiters = [];
|
|
31
|
+
this._fsWatchers = [];
|
|
32
|
+
this._watchedRegistryKey = '';
|
|
33
|
+
this._fsDebounceTimer = null;
|
|
34
|
+
this._reattachTimer = null;
|
|
14
35
|
}
|
|
15
36
|
|
|
16
37
|
get data() { return this._data; }
|
|
@@ -26,10 +47,107 @@ class Poller {
|
|
|
26
47
|
}
|
|
27
48
|
}
|
|
28
49
|
|
|
50
|
+
_teardownFsWatchers() {
|
|
51
|
+
if (this._reattachTimer) {
|
|
52
|
+
clearTimeout(this._reattachTimer);
|
|
53
|
+
this._reattachTimer = null;
|
|
54
|
+
}
|
|
55
|
+
if (this._fsDebounceTimer) {
|
|
56
|
+
clearTimeout(this._fsDebounceTimer);
|
|
57
|
+
this._fsDebounceTimer = null;
|
|
58
|
+
}
|
|
59
|
+
for (const w of this._fsWatchers) {
|
|
60
|
+
try { w.close(); } catch { /* ignore */ }
|
|
61
|
+
}
|
|
62
|
+
this._fsWatchers = [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_scheduleFsRefresh() {
|
|
66
|
+
if (this._fsDebounceTimer) clearTimeout(this._fsDebounceTimer);
|
|
67
|
+
this._fsDebounceTimer = setTimeout(() => {
|
|
68
|
+
this._fsDebounceTimer = null;
|
|
69
|
+
void this.forceRefresh();
|
|
70
|
+
}, 400);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_syncFileWatchers() {
|
|
74
|
+
const reg = this._dashMgr?.registry;
|
|
75
|
+
if (!reg) return;
|
|
76
|
+
const key = [...reg.entries()].map(([k, v]) => `${k}:${v._path}`).sort().join('|');
|
|
77
|
+
if (key === this._watchedRegistryKey) return;
|
|
78
|
+
this._watchedRegistryKey = key;
|
|
79
|
+
|
|
80
|
+
this._teardownFsWatchers();
|
|
81
|
+
|
|
82
|
+
let utils;
|
|
83
|
+
try {
|
|
84
|
+
utils = require(guardPath('lib', 'utils'));
|
|
85
|
+
} catch {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const { isGitRepo, gitDir: getGitDir } = utils;
|
|
89
|
+
|
|
90
|
+
for (const proj of reg.values()) {
|
|
91
|
+
const pp = proj._path;
|
|
92
|
+
const pid = proj.id;
|
|
93
|
+
|
|
94
|
+
if (isGitRepo(pp)) {
|
|
95
|
+
const gDir = getGitDir(pp);
|
|
96
|
+
if (gDir && fs.existsSync(gDir)) {
|
|
97
|
+
try {
|
|
98
|
+
const w = fs.watch(gDir, (ev, fname) => {
|
|
99
|
+
if (fname === 'cursor-guard-alert.json' || fname === 'cursor-guard.lock') {
|
|
100
|
+
this._scheduleFsRefresh();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
this._fsWatchers.push(w);
|
|
104
|
+
} catch { /* ignore */ }
|
|
105
|
+
|
|
106
|
+
const refsDir = path.join(gDir, 'refs');
|
|
107
|
+
if (fs.existsSync(refsDir)) {
|
|
108
|
+
try {
|
|
109
|
+
const w = fs.watch(refsDir, (ev, fname) => {
|
|
110
|
+
if (!fname) return;
|
|
111
|
+
if (fname === 'guard' || fname.startsWith('guard')) {
|
|
112
|
+
this._scheduleFsRefresh();
|
|
113
|
+
if (!this._reattachTimer) {
|
|
114
|
+
this._reattachTimer = setTimeout(() => {
|
|
115
|
+
this._reattachTimer = null;
|
|
116
|
+
this._watchedRegistryKey = '';
|
|
117
|
+
this._syncFileWatchers();
|
|
118
|
+
}, 600);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
this._fsWatchers.push(w);
|
|
123
|
+
} catch { /* ignore */ }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const guardDir = path.join(gDir, 'refs', 'guard');
|
|
127
|
+
if (fs.existsSync(guardDir)) {
|
|
128
|
+
const w = tryWatchDirRecursive(guardDir, () => this._scheduleFsRefresh());
|
|
129
|
+
if (w) this._fsWatchers.push(w);
|
|
130
|
+
const preRestoreDir = path.join(guardDir, 'pre-restore');
|
|
131
|
+
if (fs.existsSync(preRestoreDir)) {
|
|
132
|
+
const w2 = tryWatchDirRecursive(preRestoreDir, () => this._scheduleFsRefresh());
|
|
133
|
+
if (w2) this._fsWatchers.push(w2);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const backupDir = path.join(pp, '.cursor-guard-backup');
|
|
140
|
+
if (fs.existsSync(backupDir)) {
|
|
141
|
+
const w = tryWatchDirRecursive(backupDir, () => this._scheduleFsRefresh());
|
|
142
|
+
if (w) this._fsWatchers.push(w);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
29
147
|
start() {
|
|
30
148
|
if (this._timer) return;
|
|
31
|
-
this._poll();
|
|
32
|
-
this._timer = setInterval(() => this._poll(),
|
|
149
|
+
void this._poll();
|
|
150
|
+
this._timer = setInterval(() => void this._poll(), FALLBACK_POLL_MS);
|
|
33
151
|
}
|
|
34
152
|
|
|
35
153
|
stop() {
|
|
@@ -37,33 +155,55 @@ class Poller {
|
|
|
37
155
|
}
|
|
38
156
|
|
|
39
157
|
async _poll() {
|
|
40
|
-
if (this._pollRunning)
|
|
158
|
+
if (this._pollRunning) {
|
|
159
|
+
this._pollAgainAfter = true;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
41
162
|
this._pollRunning = true;
|
|
42
163
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
164
|
+
do {
|
|
165
|
+
this._pollAgainAfter = false;
|
|
166
|
+
if (!this._dashMgr.running) break;
|
|
167
|
+
const projects = await this._dashMgr.getProjects();
|
|
168
|
+
if (!Array.isArray(projects)) break;
|
|
169
|
+
for (const p of projects) {
|
|
170
|
+
const fullData = await this._dashMgr.getFullPageData(p.id);
|
|
171
|
+
this._data.set(p.id, {
|
|
172
|
+
...p,
|
|
173
|
+
dashboard: fullData?.dashboard || null,
|
|
174
|
+
backups: fullData?.backups || [],
|
|
175
|
+
scope: fullData?.scope || null,
|
|
176
|
+
doctor: fullData?.doctor || null,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
this._emit();
|
|
180
|
+
} while (this._pollAgainAfter);
|
|
57
181
|
} catch { /* non-critical */ }
|
|
58
|
-
finally {
|
|
182
|
+
finally {
|
|
183
|
+
this._pollRunning = false;
|
|
184
|
+
const waiters = this._pollWaiters.splice(0);
|
|
185
|
+
for (const fn of waiters) {
|
|
186
|
+
try { fn(); } catch { /* ignore */ }
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
this._syncFileWatchers();
|
|
190
|
+
} catch { /* ignore */ }
|
|
191
|
+
}
|
|
59
192
|
}
|
|
60
193
|
|
|
61
194
|
async forceRefresh() {
|
|
62
|
-
|
|
195
|
+
return new Promise(resolve => {
|
|
196
|
+
this._pollWaiters.push(resolve);
|
|
197
|
+
this._pollAgainAfter = true;
|
|
198
|
+
if (!this._pollRunning) {
|
|
199
|
+
void this._poll();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
63
202
|
}
|
|
64
203
|
|
|
65
204
|
dispose() {
|
|
66
205
|
this.stop();
|
|
206
|
+
this._teardownFsWatchers();
|
|
67
207
|
this._listeners = [];
|
|
68
208
|
}
|
|
69
209
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const vscode = require('vscode');
|
|
4
4
|
const { getLocale, setLocale } = require('./locale');
|
|
5
|
+
const { guardPath } = require('./paths');
|
|
5
6
|
|
|
6
7
|
class SidebarDashboardProvider {
|
|
7
8
|
constructor(poller, context) {
|
|
@@ -41,6 +42,19 @@ class SidebarDashboardProvider {
|
|
|
41
42
|
this._postLocale();
|
|
42
43
|
}
|
|
43
44
|
if (msg.cmd === 'exec') vscode.commands.executeCommand(msg.command);
|
|
45
|
+
if (msg.cmd === 'clearAlert' && msg.projectId) {
|
|
46
|
+
(async () => {
|
|
47
|
+
try {
|
|
48
|
+
const reg = this._poller._dashMgr?.registry;
|
|
49
|
+
const entry = reg?.get(msg.projectId);
|
|
50
|
+
if (entry?._path) {
|
|
51
|
+
const { clearAlert } = require(guardPath('lib', 'core', 'anomaly'));
|
|
52
|
+
clearAlert(entry._path);
|
|
53
|
+
await this._poller.forceRefresh();
|
|
54
|
+
}
|
|
55
|
+
} catch { /* ignore */ }
|
|
56
|
+
})();
|
|
57
|
+
}
|
|
44
58
|
});
|
|
45
59
|
|
|
46
60
|
webviewView.onDidChangeVisibility(() => {
|
|
@@ -1058,6 +1072,7 @@ const I18N = {
|
|
|
1058
1072
|
'actions.openDashboard': '打开看板',
|
|
1059
1073
|
'actions.restore': '恢复',
|
|
1060
1074
|
'actions.viewDetails': '查看详情',
|
|
1075
|
+
'actions.dismissAlert': '忽略告警',
|
|
1061
1076
|
'actions.snapshot': '立即快照',
|
|
1062
1077
|
'actions.watcherOn': '停止 Watcher',
|
|
1063
1078
|
'actions.watcherOff': '启动 Watcher',
|
|
@@ -1290,6 +1305,13 @@ function render(projects) {
|
|
|
1290
1305
|
vscode.postMessage({ cmd: 'exec', command: btn.dataset.cmd });
|
|
1291
1306
|
});
|
|
1292
1307
|
});
|
|
1308
|
+
|
|
1309
|
+
root.querySelectorAll('[data-dismiss-alert]').forEach(btn => {
|
|
1310
|
+
btn.addEventListener('click', () => {
|
|
1311
|
+
const pid = btn.getAttribute('data-dismiss-alert');
|
|
1312
|
+
if (pid) vscode.postMessage({ cmd: 'clearAlert', projectId: pid });
|
|
1313
|
+
});
|
|
1314
|
+
});
|
|
1293
1315
|
}
|
|
1294
1316
|
|
|
1295
1317
|
function renderProject(dashboard, projectId) {
|