review-changes 1.0.0

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/SKILL.md ADDED
@@ -0,0 +1,178 @@
1
+ ---
2
+ name: review-changes
3
+ description: |
4
+ 审查 AI 修改的代码,检查修改是否符合用户意图。
5
+ 生成文字报告和 HTML 报告,帮助开发者发现错误修改。
6
+ HTML 报告保存在当前项目目录的 .claude/review-changes/ 下,本地服务器 serving,访问地址:http://localhost:8080/review-report.html
7
+ triggers:
8
+ - review changes
9
+ - check changes
10
+ - 审查修改
11
+ - 检查修改
12
+ allowed-tools:
13
+ - Bash
14
+ - Read
15
+ - Write
16
+ - Edit
17
+ - Glob
18
+ - Grep
19
+ - Agent
20
+ ---
21
+
22
+ # /review-changes
23
+
24
+ 当开发者使用 AI 修改项目代码后,准备提交时,调用此 skill 审查修改内容。
25
+
26
+ **核心价值**:不是简单的 diff 展示,而是"上下文对齐检查"——检查 AI 的修改是否符合用户的原始意图。
27
+
28
+ ---
29
+
30
+ ## Phase 1: 读取对话历史,提取用户意图
31
+
32
+ 从当前 Claude Code 会话的对话记录中,提取用户的核心意图。
33
+
34
+ **方法**:
35
+ 1. 读取对话历史(最近的对话记录)
36
+ 2. 找到用户描述的问题或需求
37
+ 3. 提取核心意图(用户想解决什么问题)
38
+
39
+ **输出**:用户意图的简洁描述(1-2 句话)
40
+
41
+ ---
42
+
43
+ ## Phase 2: 解析 git diff,获取所有修改
44
+
45
+ 执行 `git diff` 获取所有未提交的修改。
46
+
47
+ **步骤**:
48
+ 1. 检查是否在 git 仓库中
49
+ 2. 执行 `git diff` 获取所有修改
50
+ 3. 解析每个文件的修改内容
51
+
52
+ **输出**:
53
+ - 修改的文件列表
54
+ - 每个文件的具体修改内容(新增/删除的行)
55
+
56
+ ---
57
+
58
+ ## Phase 3: 智能分析——用户意图 vs 实际修改
59
+
60
+ 对比用户意图和实际修改,标记不相干的修改。
61
+
62
+ **分析逻辑**:
63
+ 1. 对于每个修改的文件,检查修改内容是否与用户意图相关
64
+ 2. 如果修改内容与意图不符,标记为"不相干"
65
+ 3. 如果修改内容明显错误(如改了不该改的逻辑),标记为"明显问题"
66
+
67
+ **标记分类**:
68
+ - ✅ **符合意图** — 修改内容与用户意图一致
69
+ - ⚠️ **需要检查** — 修改内容可能与意图不符,需要人工确认
70
+ - ❌ **明显问题** — 修改内容明显错误或与意图无关
71
+
72
+ ---
73
+
74
+ ## Phase 4: 生成文字报告
75
+
76
+ 在终端直接显示文字报告。
77
+
78
+ **报告格式**:
79
+ ```
80
+ 📋 代码修改审查报告
81
+
82
+ 用户意图:[用户想解决什么问题]
83
+
84
+ 修改文件:N 个
85
+ ✅ src/file1.ts — [简短描述](符合意图)
86
+ ⚠️ src/file2.ts — [简短描述](需要检查)
87
+ ❌ src/file3.ts — [简短描述](明显问题)
88
+
89
+ 问题:
90
+ - file2.ts: [具体问题描述]
91
+ - file3.ts: [具体问题描述]
92
+
93
+ HTML 报告已生成:.claude/review-changes/review-report.html
94
+ 本地服务器已启动:http://localhost:8080/review-report.html
95
+ ```
96
+
97
+ **注意**:服务器地址应该可以直接在终端中点击打开。在 Claude Code 中,URL 会自动变成可点击的链接,用户点击后可以直接在浏览器中打开。
98
+
99
+ ---
100
+
101
+ ## Phase 5: 生成 HTML 报告
102
+
103
+ 生成详细的 HTML 报告,供浏览器打开查看。
104
+
105
+ **HTML 文件位置**:在当前项目目录下创建 `.claude/review-changes/` 目录,生成 `review-report.html` 文件。
106
+
107
+ **本地服务器**:使用 Python 启动一个简单的 HTTP 服务器来 serving HTML 报告,输出服务器地址(如 `http://localhost:8080`),用户可以直接点击访问。
108
+
109
+ **HTML 报告功能**:
110
+
111
+ ### 1. 统计摘要(顶部)
112
+ - 总修改文件数
113
+ - 总修改行数
114
+ - 问题文件数
115
+ - 风险评分(低/中/高)
116
+
117
+ ### 2. 文件分组(左侧导航)
118
+ - ✅ 符合意图(绿色)
119
+ - ⚠️ 需要检查(黄色)
120
+ - ❌ 明显问题(红色)
121
+
122
+ ### 3. 快速导航(左侧文件树)
123
+ - 显示所有修改的文件
124
+ - 点击跳转到对应文件
125
+ - 显示每个文件的修改行数
126
+
127
+ ### 4. 修改详情(右侧主区域)
128
+ - 文件名(点击复制文件路径)
129
+ - 用户原始要求(上下文关联)
130
+ - 修改内容(代码高亮 + 行号)
131
+ - 分析结果(为什么符合/不符合意图)
132
+
133
+ ### 5. 问题定位
134
+ - 点击"不相干修改"跳转到对应代码行
135
+ - 高亮显示具体哪一行有问题
136
+ - 显示这行代码的上下文(前后几行)
137
+
138
+ ### 6. 暗色主题
139
+ - 支持暗色/亮色切换
140
+ - 默认跟随系统设置
141
+
142
+ ### 7. 上下文关联
143
+ - 每个文件显示用户原始要求
144
+ - 显示"为什么这个修改与意图不符"的解释
145
+ - 显示建议的修改方向
146
+
147
+ **配色方案**:
148
+ - 主色调:中性灰(暗色模式)/ 浅灰(亮色模式)
149
+ - 状态色:
150
+ - ✅ 符合意图:绿色 (#10B981)
151
+ - ⚠️ 需要检查:黄色 (#F59E0B)
152
+ - ❌ 明显问题:红色 (#EF4444)
153
+ - 代码高亮:使用 VS Code 的配色方案
154
+
155
+ **HTML 文件位置**:`.claude/skills/review-changes/html/review-report.html`
156
+
157
+ ---
158
+
159
+ ## Phase 6: 完成
160
+
161
+ 告诉用户:
162
+ 1. 文字报告已在终端显示
163
+ 2. HTML 报告已生成在 `.claude/review-changes/review-report.html`
164
+ 3. 本地服务器已启动,访问地址:`http://localhost:8080/review-report.html`
165
+ 4. 如果需要回撤某个文件,可以让 AI 执行 `git checkout HEAD -- <文件路径>`
166
+
167
+ **重要**:输出服务器地址时,确保地址是可点击的。在 Claude Code 中,URL 会自动变成可点击的链接,用户点击后可以直接在浏览器中打开。
168
+
169
+ ---
170
+
171
+ ## 重要规则
172
+
173
+ - **只分析未提交的修改** — 使用 `git diff`(不是 `git diff --cached`)
174
+ - **从对话历史提取意图** — 不需要用户手动输入
175
+ - **标记要准确** — 不要误标,也不要漏标
176
+ - **文字报告要简洁** — 快速查看关键信息
177
+ - **HTML 报告要详细** — 完整展示所有修改和分析
178
+ - **UI 要清晰** — 布局合理、配色和谐、易于阅读
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "review-changes",
3
+ "version": "1.0.0",
4
+ "description": "审查 AI 修改的代码,检查修改是否符合用户意图。生成文字报告和 HTML 报告。",
5
+ "main": "SKILL.md",
6
+ "scripts": {
7
+ "test": "echo \"No tests yet\""
8
+ },
9
+ "keywords": [
10
+ "claude-code",
11
+ "skill",
12
+ "ai",
13
+ "code-review",
14
+ "git-diff"
15
+ ],
16
+ "author": "yuxi-ovo",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/yuxi-ovo/claude-code-skills.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/yuxi-ovo/claude-code-skills/issues"
24
+ },
25
+ "homepage": "https://github.com/yuxi-ovo/claude-code-skills#readme"
26
+ }
@@ -0,0 +1,614 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>代码修改审查报告</title>
7
+ <style>
8
+ :root {
9
+ --bg-primary: #ffffff;
10
+ --bg-secondary: #f8f9fa;
11
+ --bg-tertiary: #e9ecef;
12
+ --text-primary: #212529;
13
+ --text-secondary: #6c757d;
14
+ --border-color: #dee2e6;
15
+ --success-color: #10B981;
16
+ --warning-color: #F59E0B;
17
+ --error-color: #EF4444;
18
+ --code-bg: #f6f8fa;
19
+ --line-number-color: #6c757d;
20
+ --added-bg: #dafbe1;
21
+ --removed-bg: #ffebe9;
22
+ }
23
+
24
+ [data-theme="dark"] {
25
+ --bg-primary: #1a1a1a;
26
+ --bg-secondary: #2d2d2d;
27
+ --bg-tertiary: #3d3d3d;
28
+ --text-primary: #e0e0e0;
29
+ --text-secondary: #a0a0a0;
30
+ --border-color: #404040;
31
+ --code-bg: #2d2d2d;
32
+ --line-number-color: #a0a0a0;
33
+ --added-bg: #1a3a1a;
34
+ --removed-bg: #3a1a1a;
35
+ }
36
+
37
+ * {
38
+ margin: 0;
39
+ padding: 0;
40
+ box-sizing: border-box;
41
+ }
42
+
43
+ body {
44
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
45
+ background-color: var(--bg-primary);
46
+ color: var(--text-primary);
47
+ line-height: 1.6;
48
+ }
49
+
50
+ .container {
51
+ display: flex;
52
+ min-height: 100vh;
53
+ }
54
+
55
+ /* 左侧导航 */
56
+ .sidebar {
57
+ width: 280px;
58
+ background-color: var(--bg-secondary);
59
+ border-right: 1px solid var(--border-color);
60
+ padding: 20px;
61
+ overflow-y: auto;
62
+ position: fixed;
63
+ height: 100vh;
64
+ }
65
+
66
+ .sidebar h3 {
67
+ font-size: 14px;
68
+ color: var(--text-secondary);
69
+ text-transform: uppercase;
70
+ margin-bottom: 15px;
71
+ letter-spacing: 0.5px;
72
+ }
73
+
74
+ .file-tree {
75
+ list-style: none;
76
+ }
77
+
78
+ .file-tree li {
79
+ margin-bottom: 8px;
80
+ }
81
+
82
+ .file-tree a {
83
+ color: var(--text-primary);
84
+ text-decoration: none;
85
+ display: flex;
86
+ align-items: center;
87
+ padding: 8px 12px;
88
+ border-radius: 6px;
89
+ transition: background-color 0.2s;
90
+ font-size: 14px;
91
+ }
92
+
93
+ .file-tree a:hover {
94
+ background-color: var(--bg-tertiary);
95
+ }
96
+
97
+ .file-tree .file-icon {
98
+ margin-right: 8px;
99
+ font-size: 16px;
100
+ }
101
+
102
+ .file-tree .file-changes {
103
+ margin-left: auto;
104
+ font-size: 12px;
105
+ color: var(--text-secondary);
106
+ background-color: var(--bg-tertiary);
107
+ padding: 2px 6px;
108
+ border-radius: 10px;
109
+ }
110
+
111
+ /* 状态分组 */
112
+ .status-group {
113
+ margin-bottom: 20px;
114
+ }
115
+
116
+ .status-group-header {
117
+ display: flex;
118
+ align-items: center;
119
+ font-size: 13px;
120
+ font-weight: 600;
121
+ margin-bottom: 10px;
122
+ padding: 6px 10px;
123
+ border-radius: 6px;
124
+ }
125
+
126
+ .status-group-header.success {
127
+ background-color: rgba(16, 185, 129, 0.1);
128
+ color: var(--success-color);
129
+ }
130
+
131
+ .status-group-header.warning {
132
+ background-color: rgba(245, 158, 11, 0.1);
133
+ color: var(--warning-color);
134
+ }
135
+
136
+ .status-group-header.error {
137
+ background-color: rgba(239, 68, 68, 0.1);
138
+ color: var(--error-color);
139
+ }
140
+
141
+ .status-icon {
142
+ margin-right: 8px;
143
+ }
144
+
145
+ /* 主内容区域 */
146
+ .main-content {
147
+ flex: 1;
148
+ margin-left: 280px;
149
+ padding: 30px;
150
+ }
151
+
152
+ /* 统计摘要 */
153
+ .summary {
154
+ display: grid;
155
+ grid-template-columns: repeat(4, 1fr);
156
+ gap: 20px;
157
+ margin-bottom: 30px;
158
+ }
159
+
160
+ .summary-card {
161
+ background-color: var(--bg-secondary);
162
+ border: 1px solid var(--border-color);
163
+ border-radius: 8px;
164
+ padding: 20px;
165
+ text-align: center;
166
+ }
167
+
168
+ .summary-card .number {
169
+ font-size: 32px;
170
+ font-weight: 700;
171
+ margin-bottom: 5px;
172
+ }
173
+
174
+ .summary-card .label {
175
+ font-size: 14px;
176
+ color: var(--text-secondary);
177
+ }
178
+
179
+ .summary-card.success .number { color: var(--success-color); }
180
+ .summary-card.warning .number { color: var(--warning-color); }
181
+ .summary-card.error .number { color: var(--error-color); }
182
+
183
+ /* 文件详情 */
184
+ .file-detail {
185
+ background-color: var(--bg-secondary);
186
+ border: 1px solid var(--border-color);
187
+ border-radius: 8px;
188
+ margin-bottom: 20px;
189
+ overflow: hidden;
190
+ }
191
+
192
+ .file-header {
193
+ display: flex;
194
+ align-items: center;
195
+ justify-content: space-between;
196
+ padding: 15px 20px;
197
+ background-color: var(--bg-tertiary);
198
+ border-bottom: 1px solid var(--border-color);
199
+ cursor: pointer;
200
+ }
201
+
202
+ .file-header:hover {
203
+ background-color: var(--bg-secondary);
204
+ }
205
+
206
+ .file-name {
207
+ font-weight: 600;
208
+ font-size: 15px;
209
+ display: flex;
210
+ align-items: center;
211
+ }
212
+
213
+ .file-name .copy-btn {
214
+ margin-left: 10px;
215
+ padding: 4px 8px;
216
+ font-size: 12px;
217
+ background-color: var(--bg-primary);
218
+ border: 1px solid var(--border-color);
219
+ border-radius: 4px;
220
+ cursor: pointer;
221
+ color: var(--text-secondary);
222
+ opacity: 0;
223
+ transition: opacity 0.2s;
224
+ }
225
+
226
+ .file-header:hover .copy-btn {
227
+ opacity: 1;
228
+ }
229
+
230
+ .file-name .copy-btn:hover {
231
+ background-color: var(--bg-tertiary);
232
+ }
233
+
234
+ .file-status {
235
+ display: flex;
236
+ align-items: center;
237
+ font-size: 13px;
238
+ padding: 4px 10px;
239
+ border-radius: 12px;
240
+ }
241
+
242
+ .file-status.success {
243
+ background-color: rgba(16, 185, 129, 0.1);
244
+ color: var(--success-color);
245
+ }
246
+
247
+ .file-status.warning {
248
+ background-color: rgba(245, 158, 11, 0.1);
249
+ color: var(--warning-color);
250
+ }
251
+
252
+ .file-status.error {
253
+ background-color: rgba(239, 68, 68, 0.1);
254
+ color: var(--error-color);
255
+ }
256
+
257
+ /* 上下文关联 */
258
+ .context-section {
259
+ padding: 15px 20px;
260
+ border-bottom: 1px solid var(--border-color);
261
+ background-color: var(--bg-primary);
262
+ }
263
+
264
+ .context-label {
265
+ font-size: 12px;
266
+ color: var(--text-secondary);
267
+ text-transform: uppercase;
268
+ margin-bottom: 8px;
269
+ letter-spacing: 0.5px;
270
+ }
271
+
272
+ .context-text {
273
+ font-size: 14px;
274
+ color: var(--text-primary);
275
+ line-height: 1.5;
276
+ }
277
+
278
+ .context-explanation {
279
+ margin-top: 10px;
280
+ padding: 10px 12px;
281
+ background-color: rgba(245, 158, 11, 0.1);
282
+ border-left: 3px solid var(--warning-color);
283
+ border-radius: 4px;
284
+ font-size: 13px;
285
+ color: var(--text-secondary);
286
+ }
287
+
288
+ .context-explanation.error {
289
+ background-color: rgba(239, 68, 68, 0.1);
290
+ border-left-color: var(--error-color);
291
+ }
292
+
293
+ /* 代码差异 */
294
+ .diff-container {
295
+ overflow-x: auto;
296
+ }
297
+
298
+ .diff-table {
299
+ width: 100%;
300
+ border-collapse: collapse;
301
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
302
+ font-size: 13px;
303
+ line-height: 1.5;
304
+ }
305
+
306
+ .diff-table td {
307
+ padding: 0 10px;
308
+ white-space: pre;
309
+ vertical-align: top;
310
+ }
311
+
312
+ .diff-line-number {
313
+ width: 50px;
314
+ min-width: 50px;
315
+ text-align: right;
316
+ color: var(--line-number-color);
317
+ background-color: var(--bg-secondary);
318
+ user-select: none;
319
+ padding-right: 15px;
320
+ border-right: 1px solid var(--border-color);
321
+ }
322
+
323
+ .diff-line-content {
324
+ padding-left: 15px;
325
+ }
326
+
327
+ .diff-line.added {
328
+ background-color: var(--added-bg);
329
+ }
330
+
331
+ .diff-line.removed {
332
+ background-color: var(--removed-bg);
333
+ }
334
+
335
+ .diff-line.added .diff-line-content::before {
336
+ content: '+';
337
+ color: var(--success-color);
338
+ margin-right: 5px;
339
+ }
340
+
341
+ .diff-line.removed .diff-line-content::before {
342
+ content: '-';
343
+ color: var(--error-color);
344
+ margin-right: 5px;
345
+ }
346
+
347
+ .diff-line.context {
348
+ background-color: var(--bg-primary);
349
+ }
350
+
351
+ /* 主题切换 */
352
+ .theme-toggle {
353
+ position: fixed;
354
+ top: 20px;
355
+ right: 20px;
356
+ padding: 8px 16px;
357
+ background-color: var(--bg-secondary);
358
+ border: 1px solid var(--border-color);
359
+ border-radius: 6px;
360
+ cursor: pointer;
361
+ color: var(--text-primary);
362
+ font-size: 14px;
363
+ z-index: 1000;
364
+ }
365
+
366
+ .theme-toggle:hover {
367
+ background-color: var(--bg-tertiary);
368
+ }
369
+
370
+ /* 响应式设计 */
371
+ @media (max-width: 768px) {
372
+ .sidebar {
373
+ display: none;
374
+ }
375
+
376
+ .main-content {
377
+ margin-left: 0;
378
+ }
379
+
380
+ .summary {
381
+ grid-template-columns: repeat(2, 1fr);
382
+ }
383
+ }
384
+ </style>
385
+ </head>
386
+ <body>
387
+ <button class="theme-toggle" onclick="toggleTheme()">🌓 切换主题</button>
388
+
389
+ <div class="container">
390
+ <!-- 左侧导航 -->
391
+ <nav class="sidebar">
392
+ <h3>修改文件</h3>
393
+ <div class="status-group">
394
+ <div class="status-group-header success">
395
+ <span class="status-icon">✅</span>
396
+ 符合意图
397
+ </div>
398
+ <ul class="file-tree" id="success-files">
399
+ <!-- 动态生成 -->
400
+ </ul>
401
+ </div>
402
+
403
+ <div class="status-group">
404
+ <div class="status-group-header warning">
405
+ <span class="status-icon">⚠️</span>
406
+ 需要检查
407
+ </div>
408
+ <ul class="file-tree" id="warning-files">
409
+ <!-- 动态生成 -->
410
+ </ul>
411
+ </div>
412
+
413
+ <div class="status-group">
414
+ <div class="status-group-header error">
415
+ <span class="status-icon">❌</span>
416
+ 明显问题
417
+ </div>
418
+ <ul class="file-tree" id="error-files">
419
+ <!-- 动态生成 -->
420
+ </ul>
421
+ </div>
422
+ </nav>
423
+
424
+ <!-- 主内容区域 -->
425
+ <main class="main-content">
426
+ <!-- 统计摘要 -->
427
+ <div class="summary">
428
+ <div class="summary-card">
429
+ <div class="number" id="total-files">0</div>
430
+ <div class="label">修改文件</div>
431
+ </div>
432
+ <div class="summary-card">
433
+ <div class="number" id="total-changes">0</div>
434
+ <div class="label">修改行数</div>
435
+ </div>
436
+ <div class="summary-card warning">
437
+ <div class="number" id="warning-count">0</div>
438
+ <div class="label">需要检查</div>
439
+ </div>
440
+ <div class="summary-card error">
441
+ <div class="number" id="error-count">0</div>
442
+ <div class="label">明显问题</div>
443
+ </div>
444
+ </div>
445
+
446
+ <!-- 文件详情列表 -->
447
+ <div id="file-details">
448
+ <!-- 动态生成 -->
449
+ </div>
450
+ </main>
451
+ </div>
452
+
453
+ <script>
454
+ // 报告数据(由 AI 动态生成)
455
+ const reportData = {
456
+ userIntent: "{{USER_INTENT}}",
457
+ files: [
458
+ // 示例数据结构
459
+ // {
460
+ // path: "src/example.ts",
461
+ // status: "success", // success, warning, error
462
+ // changes: 10,
463
+ // userRequest: "修复路径问题",
464
+ // explanation: null, // 如果有问题,解释为什么
465
+ // diff: [
466
+ // { type: "context", line: 1, content: "..." },
467
+ // { type: "added", line: 2, content: "..." },
468
+ // { type: "removed", line: 3, content: "..." }
469
+ // ]
470
+ // }
471
+ ]
472
+ };
473
+
474
+ // 初始化报告
475
+ function initReport() {
476
+ document.getElementById('total-files').textContent = reportData.files.length;
477
+
478
+ let totalChanges = 0;
479
+ let warningCount = 0;
480
+ let errorCount = 0;
481
+
482
+ const successFiles = document.getElementById('success-files');
483
+ const warningFiles = document.getElementById('warning-files');
484
+ const errorFiles = document.getElementById('error-files');
485
+ const fileDetails = document.getElementById('file-details');
486
+
487
+ reportData.files.forEach((file, index) => {
488
+ totalChanges += file.changes;
489
+
490
+ // 添加到侧边栏
491
+ const li = document.createElement('li');
492
+ li.innerHTML = `
493
+ <a href="#file-${index}">
494
+ <span class="file-icon">📄</span>
495
+ ${file.path.split('/').pop()}
496
+ <span class="file-changes">+${file.changes}</span>
497
+ </a>
498
+ `;
499
+
500
+ if (file.status === 'success') {
501
+ successFiles.appendChild(li);
502
+ } else if (file.status === 'warning') {
503
+ warningFiles.appendChild(li);
504
+ warningCount++;
505
+ } else {
506
+ errorFiles.appendChild(li);
507
+ errorCount++;
508
+ }
509
+
510
+ // 添加文件详情
511
+ const fileDetail = document.createElement('div');
512
+ fileDetail.className = 'file-detail';
513
+ fileDetail.id = `file-${index}`;
514
+
515
+ let contextHTML = '';
516
+ if (file.explanation) {
517
+ contextHTML = `
518
+ <div class="context-section">
519
+ <div class="context-label">用户要求</div>
520
+ <div class="context-text">${file.userRequest}</div>
521
+ <div class="context-explanation ${file.status === 'error' ? 'error' : ''}">
522
+ ${file.explanation}
523
+ </div>
524
+ </div>
525
+ `;
526
+ } else {
527
+ contextHTML = `
528
+ <div class="context-section">
529
+ <div class="context-label">用户要求</div>
530
+ <div class="context-text">${file.userRequest}</div>
531
+ </div>
532
+ `;
533
+ }
534
+
535
+ let diffHTML = '';
536
+ if (file.diff && file.diff.length > 0) {
537
+ diffHTML = `
538
+ <div class="diff-container">
539
+ <table class="diff-table">
540
+ ${file.diff.map(line => `
541
+ <tr class="diff-line ${line.type}">
542
+ <td class="diff-line-number">${line.line}</td>
543
+ <td class="diff-line-content">${escapeHtml(line.content)}</td>
544
+ </tr>
545
+ `).join('')}
546
+ </table>
547
+ </div>
548
+ `;
549
+ }
550
+
551
+ fileDetail.innerHTML = `
552
+ <div class="file-header" onclick="toggleFile(${index})">
553
+ <div class="file-name">
554
+ 📄 ${file.path}
555
+ <button class="copy-btn" onclick="event.stopPropagation(); copyPath('${file.path}')">
556
+ 复制路径
557
+ </button>
558
+ </div>
559
+ <div class="file-status ${file.status}">
560
+ ${file.status === 'success' ? '✅ 符合意图' :
561
+ file.status === 'warning' ? '⚠️ 需要检查' :
562
+ '❌ 明显问题'}
563
+ </div>
564
+ </div>
565
+ ${contextHTML}
566
+ ${diffHTML}
567
+ `;
568
+
569
+ fileDetails.appendChild(fileDetail);
570
+ });
571
+
572
+ document.getElementById('total-changes').textContent = totalChanges;
573
+ document.getElementById('warning-count').textContent = warningCount;
574
+ document.getElementById('error-count').textContent = errorCount;
575
+ }
576
+
577
+ // 切换文件展开/折叠
578
+ function toggleFile(index) {
579
+ const fileDetail = document.getElementById(`file-${index}`);
580
+ const diffContainer = fileDetail.querySelector('.diff-container');
581
+ if (diffContainer) {
582
+ diffContainer.style.display = diffContainer.style.display === 'none' ? 'block' : 'none';
583
+ }
584
+ }
585
+
586
+ // 复制文件路径
587
+ function copyPath(path) {
588
+ navigator.clipboard.writeText(path).then(() => {
589
+ alert('已复制: ' + path);
590
+ });
591
+ }
592
+
593
+ // 切换主题
594
+ function toggleTheme() {
595
+ const body = document.body;
596
+ if (body.getAttribute('data-theme') === 'dark') {
597
+ body.removeAttribute('data-theme');
598
+ } else {
599
+ body.setAttribute('data-theme', 'dark');
600
+ }
601
+ }
602
+
603
+ // HTML 转义
604
+ function escapeHtml(text) {
605
+ const div = document.createElement('div');
606
+ div.textContent = text;
607
+ return div.innerHTML;
608
+ }
609
+
610
+ // 初始化
611
+ initReport();
612
+ </script>
613
+ </body>
614
+ </html>