cursor-guard 4.3.2 → 4.3.3

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 CHANGED
@@ -377,6 +377,13 @@ The skill activates on these signals:
377
377
 
378
378
  ## Changelog
379
379
 
380
+ ### v4.3.3
381
+
382
+ - **Feature**: Intent context for snapshots — `snapshot_now` now accepts `intent`, `agent`, and `session` parameters, stored as Git commit trailers to form an audit trail per operation
383
+ - **Feature**: Dashboard displays intent badge in backup table and full intent/agent/session fields in restore-point drawer
384
+ - **Improve**: `parseCommitTrailers` refactored to a data-driven map, supporting all 6 trailer fields (Files-Changed, Summary, Trigger, Intent, Agent, Session)
385
+ - **Improve**: SKILL.md updated to guide AI agents to pass `intent` describing the operation about to happen
386
+
380
387
  ### v4.3.2
381
388
 
382
389
  - **Fix**: `cursor-guard-init` now adds `node_modules/` (root-level) to `.gitignore` — prevents `git add -A` from scanning thousands of npm dependency files after `npm install cursor-guard --save-dev`
package/README.zh-CN.md CHANGED
@@ -377,6 +377,13 @@ node references\dashboard\server.js --path "D:\MyProject"
377
377
 
378
378
  ## 更新日志
379
379
 
380
+ ### v4.3.3
381
+
382
+ - **功能**:快照意图上下文——`snapshot_now` 新增 `intent`(操作意图)、`agent`(AI 模型)、`session`(会话 ID)参数,作为 Git commit trailer 存储,形成按操作事件的审计链
383
+ - **功能**:仪表盘备份表格显示意图徽章,恢复点抽屉完整展示 intent/agent/session 字段
384
+ - **改进**:`parseCommitTrailers` 重构为数据驱动映射表,支持全部 6 个 trailer 字段
385
+ - **改进**:SKILL.md 更新指引,要求 AI agent 在调用 `snapshot_now` 时传入 `intent` 描述即将执行的操作
386
+
380
387
  ### v4.3.2
381
388
 
382
389
  - **修复**:`cursor-guard-init` 现在会将根目录 `node_modules/` 加入 `.gitignore`——防止 `npm install cursor-guard --save-dev` 后 `git add -A` 扫描数千个依赖文件导致极度缓慢
package/SKILL.md CHANGED
@@ -147,14 +147,18 @@ When the target file of an edit **falls outside the protected scope**, the agent
147
147
 
148
148
  > **MCP shortcut**: if `snapshot_now` tool is available, call it with `{ "path": "<project>", "strategy": "git" }` instead of the shell commands below. The tool handles temp index, secrets exclusion, and ref creation internally, and returns `{ "git": { "status": "created", "commitHash": "...", "shortHash": "..." } }`. Report the `shortHash` to the user and proceed.
149
149
  >
150
- > **Best practice — descriptive messages**: Always provide a meaningful `message` parameter that describes *why* this snapshot is being created and *what* changes are at risk. This message appears in the dashboard restore-point list, helping users identify which snapshot to restore from. Example:
150
+ > **Best practice — intent context**: Always provide `intent` to describe *what operation you are about to perform and why*. This creates an audit trail so the user can later understand "what was the AI doing when this backup was made". Also pass `message` for the commit subject. Example:
151
151
  > ```json
152
152
  > {
153
153
  > "path": "/project",
154
154
  > "strategy": "git",
155
- > "message": "guard: before refactoring auth middleware — moving session logic from app.js to middleware/auth.js"
155
+ > "message": "guard: before refactoring auth middleware",
156
+ > "intent": "用户要求将 app.js 中的 session 逻辑拆分到 middleware/auth.js,涉及 3 个函数重写",
157
+ > "agent": "claude-4-opus",
158
+ > "session": "6290c87f"
156
159
  > }
157
160
  > ```
161
+ > The `intent`, `agent`, and `session` fields are stored as Git commit trailers and displayed in the dashboard restore-point list and detail drawer, forming a complete audit trail per operation.
158
162
 
159
163
  Use a **temporary index and dedicated ref** so the user's staged/unstaged state is never touched:
160
164
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-guard",
3
- "version": "4.3.2",
3
+ "version": "4.3.3",
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",
@@ -100,6 +100,9 @@ const I18N = {
100
100
  'summary.deleted': 'Deleted',
101
101
  'summary.renamed': 'Renamed',
102
102
  'summary.files': 'files',
103
+ 'drawer.field.intent': 'Intent',
104
+ 'drawer.field.agent': 'Agent',
105
+ 'drawer.field.session': 'Session',
103
106
 
104
107
  'error.fetchFailed': 'Failed to fetch data',
105
108
  'error.sectionFailed': 'This section failed to load',
@@ -278,6 +281,9 @@ const I18N = {
278
281
  'summary.deleted': '删除',
279
282
  'summary.renamed': '重命名',
280
283
  'summary.files': '个文件',
284
+ 'drawer.field.intent': '操作意图',
285
+ 'drawer.field.agent': 'AI 模型',
286
+ 'drawer.field.session': '会话 ID',
281
287
 
282
288
  'error.fetchFailed': '数据拉取失败',
283
289
  'error.sectionFailed': '此区块加载失败',
@@ -821,7 +827,10 @@ function formatSummaryCell(b) {
821
827
  const short = translated.length > 80 ? translated.substring(0, 77) + '...' : translated;
822
828
  parts.push(`<span class="text-muted text-sm">${esc(short)}</span>`);
823
829
  }
824
- if (b.message && !b.message.startsWith('guard:')) {
830
+ if (b.intent) {
831
+ const intentShort = b.intent.length > 60 ? b.intent.substring(0, 57) + '...' : b.intent;
832
+ parts.push(`<span class="badge badge-intent">${esc(intentShort)}</span>`);
833
+ } else if (b.message && !b.message.startsWith('guard:')) {
825
834
  const msgShort = b.message.length > 50 ? b.message.substring(0, 47) + '...' : b.message;
826
835
  parts.push(`<span class="text-muted text-sm">${esc(msgShort)}</span>`);
827
836
  }
@@ -952,6 +961,9 @@ function openRestoreDrawer(backup) {
952
961
  if (backup.ref) fields.push({ key: 'drawer.field.ref', val: backup.ref });
953
962
  if (backup.commitHash) fields.push({ key: 'drawer.field.hash', val: backup.commitHash });
954
963
  if (backup.path) fields.push({ key: 'drawer.field.path', val: backup.path });
964
+ if (backup.intent) fields.push({ key: 'drawer.field.intent', val: backup.intent });
965
+ if (backup.agent) fields.push({ key: 'drawer.field.agent', val: backup.agent });
966
+ if (backup.session) fields.push({ key: 'drawer.field.session', val: backup.session });
955
967
  if (backup.message) fields.push({ key: 'drawer.field.message', val: backup.message });
956
968
  if (backup.summary) fields.push({ key: 'drawer.field.summary', val: translateSummary(backup.summary) });
957
969
 
@@ -248,9 +248,11 @@ main {
248
248
  .badge-shadow { background: var(--amber-bg); color: var(--amber); }
249
249
  .badge-pre { background: var(--purple-bg); color: var(--purple); }
250
250
  .badge-trigger { background: var(--bg-tertiary); color: var(--text-secondary); font-size: 0.7rem; }
251
+ .badge-intent { background: var(--blue-bg); color: var(--blue); font-size: 0.7rem; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle; }
251
252
 
252
- .backup-summary-cell { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
253
+ .backup-summary-cell { max-width: 340px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
253
254
  .backup-summary-cell .badge-trigger { margin-right: 4px; }
255
+ .backup-summary-cell .badge-intent { margin-left: 4px; }
254
256
  .backup-summary-cell .text-sm { font-size: 0.75rem; }
255
257
 
256
258
  /* ── Stats Row ────────────────────────────────────────────── */
@@ -41,15 +41,24 @@ function entryToMs(entry) {
41
41
  return d ? d.getTime() : 0;
42
42
  }
43
43
 
44
+ const TRAILER_MAP = {
45
+ 'Files-Changed': { key: 'filesChanged', parse: v => parseInt(v, 10) },
46
+ 'Summary': { key: 'summary' },
47
+ 'Trigger': { key: 'trigger' },
48
+ 'Intent': { key: 'intent' },
49
+ 'Agent': { key: 'agent' },
50
+ 'Session': { key: 'session' },
51
+ };
52
+
44
53
  function parseCommitTrailers(body) {
45
54
  if (!body) return {};
46
55
  const result = {};
56
+ const pattern = new RegExp(`^(${Object.keys(TRAILER_MAP).join('|')}):\\s*(.+)$`);
47
57
  for (const line of body.split('\n')) {
48
- const m = line.match(/^(Files-Changed|Summary|Trigger):\s*(.+)$/);
58
+ const m = line.match(pattern);
49
59
  if (m) {
50
- const key = m[1] === 'Files-Changed' ? 'filesChanged'
51
- : m[1] === 'Summary' ? 'summary' : 'trigger';
52
- result[key] = key === 'filesChanged' ? parseInt(m[2], 10) : m[2];
60
+ const def = TRAILER_MAP[m[1]];
61
+ result[def.key] = def.parse ? def.parse(m[2]) : m[2];
53
62
  }
54
63
  }
55
64
  return result;
@@ -65,6 +65,9 @@ function buildCommitMessage(ts, opts) {
65
65
  if (ctx.changedFileCount != null) trailers.push(`Files-Changed: ${ctx.changedFileCount}`);
66
66
  if (ctx.summary) trailers.push(`Summary: ${ctx.summary}`);
67
67
  if (ctx.trigger) trailers.push(`Trigger: ${ctx.trigger}`);
68
+ if (ctx.intent) trailers.push(`Intent: ${ctx.intent}`);
69
+ if (ctx.agent) trailers.push(`Agent: ${ctx.agent}`);
70
+ if (ctx.session) trailers.push(`Session: ${ctx.session}`);
68
71
 
69
72
  if (trailers.length === 0) return subject;
70
73
  return subject + '\n\n' + trailers.join('\n');
@@ -84,7 +87,10 @@ function buildCommitMessage(ts, opts) {
84
87
  * @param {object} [opts.context] - Backup context metadata
85
88
  * @param {string} [opts.context.trigger] - 'auto' | 'manual' | 'pre-restore'
86
89
  * @param {number} [opts.context.changedFileCount] - Number of changed files
87
- * @param {string} [opts.context.summary] - Short change summary (e.g. "M src/app.js, A new.ts")
90
+ * @param {string} [opts.context.summary] - Short change summary (e.g. "Modified 3: a.js, b.js; Added 1: c.js")
91
+ * @param {string} [opts.context.intent] - Why this snapshot was created (e.g. "refactoring auth middleware")
92
+ * @param {string} [opts.context.agent] - AI model identifier (e.g. "claude-4-opus")
93
+ * @param {string} [opts.context.session] - Conversation/session ID
88
94
  * @returns {{ status: 'created'|'skipped'|'error', commitHash?: string, shortHash?: string, fileCount?: number, reason?: string, error?: string, secretsExcluded?: string[] }}
89
95
  */
90
96
  function createGitSnapshot(projectDir, cfg, opts = {}) {
@@ -92,8 +92,11 @@ server.tool(
92
92
  strategy: z.enum(['git', 'shadow', 'both']).optional().describe('Backup strategy (default: from config, or "git")'),
93
93
  message: z.string().optional().describe('Custom commit message for git snapshot'),
94
94
  scope: z.enum(['protected', 'all']).optional().describe('Snapshot scope: "protected" = only files matching protect patterns (default when protect is configured); "all" = all files regardless of protect config'),
95
+ intent: z.string().optional().describe('Why this snapshot is being created — describe the operation about to happen (e.g. "refactoring auth middleware to use JWT")'),
96
+ agent: z.string().optional().describe('AI model identifier (e.g. "claude-4-opus")'),
97
+ session: z.string().optional().describe('Conversation or session ID for audit trail'),
95
98
  },
96
- async ({ path: projectPath, strategy, message, scope }) => {
99
+ async ({ path: projectPath, strategy, message, scope, intent, agent, session }) => {
97
100
  const resolved = path.resolve(projectPath);
98
101
  const { loadConfig } = require('../lib/utils');
99
102
  const { cfg } = loadConfig(resolved);
@@ -109,10 +112,14 @@ server.tool(
109
112
  const results = {};
110
113
 
111
114
  if (effectiveStrategy === 'git' || effectiveStrategy === 'both') {
115
+ const context = { trigger: 'manual' };
116
+ if (intent) context.intent = intent;
117
+ if (agent) context.agent = agent;
118
+ if (session) context.session = session;
112
119
  results.git = createGitSnapshot(resolved, cfg, {
113
120
  branchRef: 'refs/guard/snapshot',
114
121
  message: message || `guard: manual snapshot ${new Date().toISOString()}`,
115
- context: { trigger: 'manual' },
122
+ context,
116
123
  });
117
124
  }
118
125