cursor-guard 4.5.6 → 4.5.7
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/ROADMAP.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
> 本文档描述 cursor-guard 从 V2 到 V7 的长期演进方向。
|
|
4
4
|
> 每一代向下兼容,低版本功能永远不废弃。
|
|
5
5
|
>
|
|
6
|
-
> **当前版本**:`V4.5.
|
|
7
|
-
> **文档状态**:`V2` ~ `V4.5.
|
|
6
|
+
> **当前版本**:`V4.5.7`(V4 最终版)
|
|
7
|
+
> **文档状态**:`V2` ~ `V4.5.7` 已完成交付(含 V5 intent/audit 基础),`V5` 主体规划中
|
|
8
8
|
|
|
9
9
|
## 阅读导航
|
|
10
10
|
|
|
@@ -463,6 +463,7 @@ V4 经过 4 轮系统性代码审查,修复了以下关键问题:
|
|
|
463
463
|
| V4.5.3 | **告警历史 UX 优化 + 备份结构化文件表格**:见下方详细说明 | ✅ |
|
|
464
464
|
| V4.5.4 | **Shadow 硬链接增量优化 + always_watch 强保护模式**:见下方详细说明 | ✅ |
|
|
465
465
|
| V4.5.6 | **Bug 修复 + 告警 UX + init 优化**:见下方详细说明 | ✅ |
|
|
466
|
+
| V4.5.7 | **文件详情 Modal 修复 + Dashboard 端口复用**:见下方详细说明 | ✅ |
|
|
466
467
|
|
|
467
468
|
#### V4.4.1 详细内容
|
|
468
469
|
|
|
@@ -652,6 +653,24 @@ V4 经过 4 轮系统性代码审查,修复了以下关键问题:
|
|
|
652
653
|
| `cursor-guard-init` 自动创建配置 | init 流程新增 Step 4/5:若 `.cursor-guard.json` 不存在,自动从 `cursor-guard.example.json` 复制为项目根默认配置。升级场景下保留现有配置 |
|
|
653
654
|
| `backup_interval_seconds` 兼容别名 | `loadConfig` 支持 `backup_interval_seconds` 作为 `auto_backup_interval_seconds` 的别名(带 deprecation 警告) |
|
|
654
655
|
|
|
656
|
+
#### V4.5.7 详细内容
|
|
657
|
+
|
|
658
|
+
**Bug 修复**:
|
|
659
|
+
|
|
660
|
+
| 问题 | 根因 | 修复 |
|
|
661
|
+
|------|------|------|
|
|
662
|
+
| 告警"查看文件详情"Modal 点不开 | 事件处理中 `state.pageData?.alerts` 路径错误,告警数据实际存储在 `state.pageData.dashboard.alerts` | 修正为 `state.pageData?.dashboard?.alerts`,同时修正 projectPath 取值路径 |
|
|
663
|
+
|
|
664
|
+
**Dashboard 服务端口复用(单例模式)**:
|
|
665
|
+
|
|
666
|
+
| 改进 | 说明 |
|
|
667
|
+
|------|------|
|
|
668
|
+
| 模块级单例 `_instance` | `startDashboardServer` 首次调用时创建 HTTP 服务并缓存到 `_instance`(含 server/port/registry/token) |
|
|
669
|
+
| 热加载新项目 | 后续调用检测到 `_instance` 已存在时,不创建新服务,而是调用 `_mergeProjects` 将新路径合并到已有 registry 中 |
|
|
670
|
+
| 去重机制 | `_mergeProjects` 按 `_path.toLowerCase()` 去重,相同项目不重复注册 |
|
|
671
|
+
| 透明生效 | registry 是引用传递,已运行的 HTTP handler 闭包自动读取最新 registry,无需重启服务 |
|
|
672
|
+
| 导出 `getInstance()` | 外部可通过 `getInstance()` 获取当前运行实例的 server/port/registry 信息 |
|
|
673
|
+
|
|
655
674
|
#### V4.5.x 新增配置参考
|
|
656
675
|
|
|
657
676
|
| 字段 | 类型 | 默认值 | 引入版本 | 说明 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-guard",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.7",
|
|
4
4
|
"description": "Protects code from accidental AI overwrite or deletion in Cursor IDE — mandatory pre-write snapshots, review-before-apply, local Git safety net, and deterministic recovery. | 保护代码免受 Cursor AI 代理意外覆写或删除——强制写前快照、预览再执行、本地 Git 安全网、确定性恢复。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cursor",
|
|
@@ -1540,10 +1540,10 @@ function setupEvents() {
|
|
|
1540
1540
|
}
|
|
1541
1541
|
const modalBtn = e.target.closest('[data-alert-files-modal]');
|
|
1542
1542
|
if (modalBtn) {
|
|
1543
|
-
const alerts = state.pageData?.alerts;
|
|
1543
|
+
const alerts = state.pageData?.dashboard?.alerts;
|
|
1544
1544
|
const files = alerts?.latest?.files || [];
|
|
1545
1545
|
if (files.length > 0) {
|
|
1546
|
-
const proj = state.pageData?.
|
|
1546
|
+
const proj = state.pageData?.dashboard?.watcher?.path || '';
|
|
1547
1547
|
openFileModal(t('modal.alertFiles'), files, proj, '');
|
|
1548
1548
|
}
|
|
1549
1549
|
return;
|
|
@@ -186,19 +186,47 @@ function handleApi(pathname, query, registry, res) {
|
|
|
186
186
|
return notFound(res);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
/* ── Server
|
|
189
|
+
/* ── Server (singleton) ─────────────────────────────────────── */
|
|
190
|
+
|
|
191
|
+
let _instance = null;
|
|
192
|
+
|
|
193
|
+
function _mergeProjects(registry, paths) {
|
|
194
|
+
const seen = new Set([...registry.values()].map(p => p._path.toLowerCase()));
|
|
195
|
+
let maxIdx = registry.size;
|
|
196
|
+
let added = 0;
|
|
197
|
+
for (const raw of paths) {
|
|
198
|
+
const resolved = path.resolve(raw);
|
|
199
|
+
if (seen.has(resolved.toLowerCase())) continue;
|
|
200
|
+
seen.add(resolved.toLowerCase());
|
|
201
|
+
const id = `p${maxIdx++}`;
|
|
202
|
+
const name = path.basename(resolved) || resolved;
|
|
203
|
+
const label = resolved.length > 50 ? '...' + resolved.slice(-47) : resolved;
|
|
204
|
+
registry.set(id, { id, name, pathLabel: label, _path: resolved });
|
|
205
|
+
added++;
|
|
206
|
+
}
|
|
207
|
+
return added;
|
|
208
|
+
}
|
|
190
209
|
|
|
191
210
|
/**
|
|
192
|
-
* Start the dashboard HTTP server.
|
|
193
|
-
*
|
|
211
|
+
* Start the dashboard HTTP server, or hot-add projects to an existing instance.
|
|
212
|
+
* Uses a module-level singleton: subsequent calls reuse the same port and server,
|
|
213
|
+
* merging new project paths into the live registry.
|
|
194
214
|
*
|
|
195
215
|
* @param {string[]} paths - Project directories to serve
|
|
196
216
|
* @param {object} [opts]
|
|
197
|
-
* @param {number} [opts.port=3120] - Starting port
|
|
217
|
+
* @param {number} [opts.port=3120] - Starting port (ignored if server already running)
|
|
198
218
|
* @param {boolean} [opts.silent=false] - Suppress banner output
|
|
199
219
|
* @returns {Promise<{server: http.Server, port: number, registry: Map}>}
|
|
200
220
|
*/
|
|
201
221
|
function startDashboardServer(paths, opts = {}) {
|
|
222
|
+
if (_instance) {
|
|
223
|
+
const added = _mergeProjects(_instance.registry, paths);
|
|
224
|
+
if (added > 0 && !opts.silent) {
|
|
225
|
+
console.log(` [dashboard] Hot-added ${added} project(s) — total: ${_instance.registry.size} on port ${_instance.port}`);
|
|
226
|
+
}
|
|
227
|
+
return Promise.resolve(_instance);
|
|
228
|
+
}
|
|
229
|
+
|
|
202
230
|
const port = opts.port || DEFAULT_PORT;
|
|
203
231
|
const silent = opts.silent || false;
|
|
204
232
|
const registry = buildRegistry(paths);
|
|
@@ -209,7 +237,6 @@ function startDashboardServer(paths, opts = {}) {
|
|
|
209
237
|
let retries = 0;
|
|
210
238
|
|
|
211
239
|
const server = http.createServer((req, res) => {
|
|
212
|
-
// DNS rebinding protection: reject unexpected Host headers
|
|
213
240
|
const host = req.headers.host || '';
|
|
214
241
|
if (!ALLOWED_HOSTS.test(host)) {
|
|
215
242
|
res.writeHead(403);
|
|
@@ -224,7 +251,6 @@ function startDashboardServer(paths, opts = {}) {
|
|
|
224
251
|
try { parsed = new URL(req.url, `http://${host}`); }
|
|
225
252
|
catch { return notFound(res); }
|
|
226
253
|
|
|
227
|
-
// API endpoints require per-process token
|
|
228
254
|
if (parsed.pathname.startsWith('/api/')) {
|
|
229
255
|
const reqToken = parsed.searchParams.get('token');
|
|
230
256
|
if (reqToken !== token) {
|
|
@@ -249,6 +275,7 @@ function startDashboardServer(paths, opts = {}) {
|
|
|
249
275
|
|
|
250
276
|
server.on('listening', () => {
|
|
251
277
|
const addr = server.address();
|
|
278
|
+
_instance = { server, port: addr.port, registry, token };
|
|
252
279
|
if (!silent) {
|
|
253
280
|
console.log('');
|
|
254
281
|
console.log(' Cursor Guard Dashboard');
|
|
@@ -260,7 +287,7 @@ function startDashboardServer(paths, opts = {}) {
|
|
|
260
287
|
}
|
|
261
288
|
console.log('');
|
|
262
289
|
}
|
|
263
|
-
resolve(
|
|
290
|
+
resolve(_instance);
|
|
264
291
|
});
|
|
265
292
|
|
|
266
293
|
server.listen(currentPort, '127.0.0.1');
|
|
@@ -277,4 +304,4 @@ if (require.main === module) {
|
|
|
277
304
|
});
|
|
278
305
|
}
|
|
279
306
|
|
|
280
|
-
module.exports = { startDashboardServer };
|
|
307
|
+
module.exports = { startDashboardServer, getInstance: () => _instance };
|