cursor-guard 4.6.0 → 4.6.1
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 +14 -2
- package/package.json +1 -1
- package/references/dashboard/public/app.js +54 -19
package/ROADMAP.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
> 本文档描述 cursor-guard 从 V2 到 V7 的长期演进方向。
|
|
4
4
|
> 每一代向下兼容,低版本功能永远不废弃。
|
|
5
5
|
>
|
|
6
|
-
> **当前版本**:`V4.6.
|
|
7
|
-
> **文档状态**:`V2` ~ `V4.6.
|
|
6
|
+
> **当前版本**:`V4.6.1`
|
|
7
|
+
> **文档状态**:`V2` ~ `V4.6.1` 已完成交付(含 V5 intent/audit 基础),`V5` 主体规划中
|
|
8
8
|
|
|
9
9
|
## 阅读导航
|
|
10
10
|
|
|
@@ -466,6 +466,7 @@ V4 经过 4 轮系统性代码审查,修复了以下关键问题:
|
|
|
466
466
|
| V4.5.7 | **文件详情 Modal 修复 + Dashboard 端口复用**:见下方详细说明 | ✅ |
|
|
467
467
|
| V4.5.8 | **Dashboard 版本更新检测 + 一键重启**:见下方详细说明 | ✅ |
|
|
468
468
|
| V4.6.0 | **告警 UX 全面升级**:见下方详细说明 | ✅ |
|
|
469
|
+
| V4.6.1 | **告警历史弹窗化**:见下方详细说明 | ✅ |
|
|
469
470
|
|
|
470
471
|
#### V4.4.1 详细内容
|
|
471
472
|
|
|
@@ -698,6 +699,17 @@ V4 经过 4 轮系统性代码审查,修复了以下关键问题:
|
|
|
698
699
|
| 告警历史常驻入口 | `buildAlertHistoryHtml()` 提取为独立函数,在活跃告警和无告警两种状态下都渲染"历史 N 条"折叠按钮,告警激活时也能查看历史 |
|
|
699
700
|
| 告警历史 localStorage 持久化 | 新增 `loadAlertHistory()` / `saveAlertHistory()`,使用 `localStorage` key `cursorGuard_alertHistory` 持久化最近 20 条告警历史,刷新页面不丢失 |
|
|
700
701
|
|
|
702
|
+
#### V4.6.1 详细内容
|
|
703
|
+
|
|
704
|
+
**告警历史弹窗化**:
|
|
705
|
+
|
|
706
|
+
| 改动 | 说明 |
|
|
707
|
+
|------|------|
|
|
708
|
+
| 历史按钮改为弹窗触发 | "历史 N 条"按钮点击后打开全屏 Modal(复用 `file-modal`),不再内联折叠展开 |
|
|
709
|
+
| 历史表格化展示 | Modal 内以表格呈现所有历史告警:触发时间、详情、文件类型分布、文件列表按钮 |
|
|
710
|
+
| 嵌套文件详情 | 每条历史告警的"查看文件"按钮可再打开文件详情 Modal,查看具体变更文件列表 |
|
|
711
|
+
| 新增 i18n keys | `alert.col.detail` / `alert.col.breakdown` / `alert.col.files`(中英双语) |
|
|
712
|
+
|
|
701
713
|
#### V4.5.x 新增配置参考
|
|
702
714
|
|
|
703
715
|
| 字段 | 类型 | 默认值 | 引入版本 | 说明 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-guard",
|
|
3
|
-
"version": "4.6.
|
|
3
|
+
"version": "4.6.1",
|
|
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",
|
|
@@ -52,6 +52,9 @@ const I18N = {
|
|
|
52
52
|
'alert.col.file': 'File',
|
|
53
53
|
'alert.col.action': 'Action',
|
|
54
54
|
'alert.col.changes':'Changes',
|
|
55
|
+
'alert.col.detail': 'Detail',
|
|
56
|
+
'alert.col.breakdown': 'Breakdown',
|
|
57
|
+
'alert.col.files': 'Files',
|
|
55
58
|
'alert.action.modified': 'Modified',
|
|
56
59
|
'alert.action.added': 'Added',
|
|
57
60
|
'alert.action.deleted': 'Deleted',
|
|
@@ -276,6 +279,9 @@ const I18N = {
|
|
|
276
279
|
'alert.col.file': '文件',
|
|
277
280
|
'alert.col.action': '操作',
|
|
278
281
|
'alert.col.changes':'变化量',
|
|
282
|
+
'alert.col.detail': '详情',
|
|
283
|
+
'alert.col.breakdown': '文件类型',
|
|
284
|
+
'alert.col.files': '文件列表',
|
|
279
285
|
'alert.action.modified': '修改',
|
|
280
286
|
'alert.action.added': '新增',
|
|
281
287
|
'alert.action.deleted': '删除',
|
|
@@ -913,24 +919,55 @@ function alertFileBreakdown(files) {
|
|
|
913
919
|
function buildAlertHistoryHtml() {
|
|
914
920
|
if (state.alertHistory.length === 0) return '';
|
|
915
921
|
const count = state.alertHistory.length;
|
|
916
|
-
const rows = state.alertHistory.slice(-5).reverse().map(h => {
|
|
917
|
-
const breakdown = alertFileBreakdown(h.files);
|
|
918
|
-
return `<div class="alert-history-row text-sm text-muted">
|
|
919
|
-
<span class="alert-history-time">${esc(formatTime(h.timestamp))}</span>
|
|
920
|
-
<span>${t('alert.detail', { count: h.fileCount, window: h.windowSeconds, threshold: h.threshold })}</span>
|
|
921
|
-
${breakdown ? `<span class="alert-history-breakdown">${esc(breakdown)}</span>` : ''}
|
|
922
|
-
<span class="badge badge-expired">${t('alert.expired')}</span>
|
|
923
|
-
</div>`;
|
|
924
|
-
}).join('');
|
|
925
922
|
return `
|
|
926
923
|
<div class="alert-history-toggle-wrap">
|
|
927
|
-
<button class="alert-history-toggle-btn text-sm text-muted" data-alert-history-
|
|
928
|
-
</div>
|
|
929
|
-
<div class="alert-history alert-history-collapsed">
|
|
930
|
-
<div class="alert-history-label text-sm">${t('alert.history')}</div>${rows}
|
|
924
|
+
<button class="alert-history-toggle-btn text-sm text-muted" data-alert-history-modal>${t('alert.historyCount', { n: count })}</button>
|
|
931
925
|
</div>`;
|
|
932
926
|
}
|
|
933
927
|
|
|
928
|
+
function openAlertHistoryModal() {
|
|
929
|
+
const list = [...state.alertHistory].reverse();
|
|
930
|
+
if (list.length === 0) return;
|
|
931
|
+
$('#file-modal-title').textContent = t('alert.history');
|
|
932
|
+
const body = $('#file-modal-body');
|
|
933
|
+
|
|
934
|
+
const rows = list.map((h, i) => {
|
|
935
|
+
const breakdown = alertFileBreakdown(h.files);
|
|
936
|
+
const fileCount = Array.isArray(h.files) ? h.files.length : 0;
|
|
937
|
+
return `<tr>
|
|
938
|
+
<td class="text-mono">${esc(formatTime(h.timestamp))}</td>
|
|
939
|
+
<td>${t('alert.detail', { count: h.fileCount, window: h.windowSeconds, threshold: h.threshold })}</td>
|
|
940
|
+
<td>${breakdown ? esc(breakdown) : '-'}</td>
|
|
941
|
+
<td>${fileCount > 0 ? `<button class="modal-restore-btn" data-history-files="${i}">${t('alert.viewFiles', { n: fileCount })}</button>` : '-'}</td>
|
|
942
|
+
</tr>`;
|
|
943
|
+
}).join('');
|
|
944
|
+
|
|
945
|
+
body.innerHTML = `<table>
|
|
946
|
+
<thead><tr>
|
|
947
|
+
<th>${t('alert.triggered')}</th>
|
|
948
|
+
<th>${t('alert.col.detail')}</th>
|
|
949
|
+
<th>${t('alert.col.breakdown')}</th>
|
|
950
|
+
<th>${t('alert.col.files')}</th>
|
|
951
|
+
</tr></thead>
|
|
952
|
+
<tbody>${rows}</tbody>
|
|
953
|
+
</table>`;
|
|
954
|
+
|
|
955
|
+
body.addEventListener('click', (e) => {
|
|
956
|
+
const btn = e.target.closest('[data-history-files]');
|
|
957
|
+
if (btn) {
|
|
958
|
+
const idx = parseInt(btn.dataset.historyFiles);
|
|
959
|
+
const h = list[idx];
|
|
960
|
+
if (h?.files?.length > 0) {
|
|
961
|
+
const proj = state.pageData?.dashboard?.watcher?.path || '';
|
|
962
|
+
openFileModal(t('modal.alertFiles'), h.files, proj, '');
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
$('#file-modal-overlay').classList.add('active');
|
|
968
|
+
document.body.style.overflow = 'hidden';
|
|
969
|
+
}
|
|
970
|
+
|
|
934
971
|
function renderAlertCard(alerts) {
|
|
935
972
|
const el = $('#card-alert');
|
|
936
973
|
if (!alerts?.active) {
|
|
@@ -1573,13 +1610,11 @@ function setupEvents() {
|
|
|
1573
1610
|
if (backup) openRestoreDrawer(backup);
|
|
1574
1611
|
});
|
|
1575
1612
|
|
|
1576
|
-
// Alert history
|
|
1613
|
+
// Alert history modal + file modal (event delegation)
|
|
1577
1614
|
$('#card-alert').addEventListener('click', (e) => {
|
|
1578
|
-
const
|
|
1579
|
-
if (
|
|
1580
|
-
|
|
1581
|
-
const history = card?.querySelector('.alert-history');
|
|
1582
|
-
if (history) history.classList.toggle('alert-history-collapsed');
|
|
1615
|
+
const historyBtn = e.target.closest('[data-alert-history-modal]');
|
|
1616
|
+
if (historyBtn) {
|
|
1617
|
+
openAlertHistoryModal();
|
|
1583
1618
|
return;
|
|
1584
1619
|
}
|
|
1585
1620
|
const modalBtn = e.target.closest('[data-alert-files-modal]');
|