╭─── Claude Code v2.1.19 ───────────────────────────────────────────────────────────────────────────────────────────────────────╮
│                                   │ Tips for getting started                                                                  │
│           Welcome back!           │ Run /init to create a CLAUDE.md file with instructions for Claude                         │
│                                   │                                                                                           │
│                                   │ ─────────────────────────────────────────────────────────────────                         │
│             ▗ ▗   ▖ ▖             │ Recent activity                                                                           │
│                                   │ No recent activity                                                                        │
│               ▘▘ ▝▝               │                                                                                           │
│   glm-4.7 · API Usage Billing     │                                                                                           │
│   ~/output/github/human-review    │                                                                                           │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
                                                                                                                                 
  /model to try Opus 4.5                                                                                                         
                                                                                                                                 
❯ 我需要这样一个 cli 工具。用法 hrev commit-hash / hrev plan.md / hrev diff                                                      
  ，启动之后，会打开一个浏览器页面，并且在浏览器里面，显示 commit-hash的diff ，显示 plan.md 文档，或者其他的 diff                
  ，同时用户可以在显示的 html 页面上进行评论。评论完成后，统一提交，作为 hrev 工具的输出，这样 huaman-review 可以让claude        
  code codex gemini 等等知道人工 revieew 的结果，特别是plan                                                                      
  或者一其他方案的审核，人工只需要点评（comment）即可，不需要手动编辑，也不需要打开 IDE查看了。只需要 在 open                    
  出来的页面上面做 评论即可。                                                                                                    
                                                                                                                                 
⏺ 我来帮你创建这个 hrev CLI 工具。首先让我了解一下当前项目的结构。                                                               
                                                                                                                                 
⏺ Search(pattern: "**/*.{py,js,ts,json,toml}")                                                                                   
  ⎿  Found 0 files                                                                                                               
  ⎿  Running PostToolUse hooks… (0/2 done)                                                                                       
                                                                                                                                 
⏺ 这是一个新项目。让我为你创建 hrev CLI 工具。我会使用 Python + FastAPI 来实现，包含 CLI 入口、Web 服务器和前端页面。            
                                                                                                                                 
⏺ Write(pyproject.toml)                                                                                                          
  ⎿  User rejected write to pyproject.toml                                                                                       
       1 [project]                                                                                                               
       2 name = "hrev"                                                                                                           
       3 version = "0.1.0"                                                                                                       
       4 description = "Human review CLI tool - Open browser for code review with comments"                                      
       5 authors = [{name = "User", email = "user@example.com"}]                                                                 
       6 readme = "README.md"                                                                                                    
       7 requires-python = ">=3.10"                                                                                              
       8 dependencies = [                                                                                                        
       9     "fastapi>=0.115.0",                                                                                                 
      10     "uvicorn>=0.32.0",                                                                                                  
     … +16 lines                                                                                                                 
                                                                                                                                 
❯ 先plan 一下                                                                                                                    
                                                                                                                                 
⏺ 好的，让我先创建计划文件，然后启动 Plan agent 来设计实现方案。                                                                 
                                                                                                                                 
⏺ Updated plan                                                                                                                   
  ⎿  Wrote 47 lines to ../../../.claude/plans/zippy-plotting-ladybug.md                                                          
       1 # hrev CLI 工具实现计划                                                                                                 
       2                                                                                                                         
       3 ## 需求概述                                                                                                             
       4                                                                                                                         
       5 创建一个 CLI 工具 `hrev`，用于人工代码审查。                                                                            
       6                                                                                                                         
       7 ### 三种用法                                                                                                            
       8 1. `hrev commit-hash` - 显示某个 commit 的 diff                                                                         
       9 2. `hrev plan.md` - 显示 plan.md 文档内容                                                                               
      10 3. `hrev diff` - 显示当前 git diff                                                                                      
     … +37 lines (ctrl+o to expand)                                                                                              
  ⎿  Running PostToolUse hook…                                                                                                   
                                                                                                                                 
❯ 我希望用 rust 来做这样的事情。好处 1. 性能好  2. 二进制打包 3. 简单易用                                                        
  同时我还希望能够提供 skills or command 来做这样的文档，1引导 AI 在做完修改，或者方案后，让人来 review                          
  对应的变更。这样可以在 git 提交之前，可以在提交 pr 之前人工进行 review                                                         
                                                                                                                                 
⏺ 好的！用 Rust 实现确实更好 - 性能优秀、单一二进制文件、无依赖。让我更新计划并设计 Rust 版本。                                  
                                                                                                                                 
⏺ Updated plan                                                                                                                   
  ⎿  /plan to preview                                                                                                            
       5  创建一个 CLI 工具 `hrev`，用于人工代码审查。                                                                           
       6                                                                                                                         
       7  ### 三种用法                                                                                                           
       8 -1. `hrev commit-hash` - 显示某个 commit 的 diff                                                                        
       9 -2. `hrev plan.md` - 显示 plan.md 文档内容                                                                              
       8 +1. `hrev <commit-hash>` - 显示某个 commit 的 diff                                                                      
       9 +2. `hrev <file.md>` - 显示文档内容（如 plan.md）                                                                       
      10  3. `hrev diff` - 显示当前 git diff                                                                                     
      11                                                                                                                         
      12  ### 功能流程                                                                                                           
      13  1. 启动后打开浏览器页面                                                                                                
      14 -2. 显示对应内容（diff/文档）                                                                                           
      15 -3. 用户在 HTML 页面上添加评论                                                                                          
      14 +2. 显示对应内容（diff/文档），支持行号                                                                                 
      15 +3. 用户在 HTML 页面上添加评论（支持行级评论）                                                                          
      16  4. 评论完成后提交，作为工具输出                                                                                        
      17                                                                                                                         
      18 +### 额外需求                                                                                                           
      19 +- 提供 Claude Code skill/command，引导 AI 在修改后让人 review                                                          
      20 +- 在 git commit 前、PR 前进行人工 review                                                                               
      21 +                                                                                                                       
      22  ## 技术选型                                                                                                            
      23                                                                                                                         
      20 -- 语言: Python（易于脚本工具开发）                                                                                     
      21 -- Web框架: FastAPI（轻量、异步）                                                                                       
      22 -- 模板: Jinja2                                                                                                         
      23 -- CLI: Click                                                                                                           
      24 +- 语言: **Rust**（性能、单一二进制、无依赖）                                                                           
      25 +- Web框架: axum（异步、基于 tokio）                                                                                    
      26 +- 模板: askama（编译时模板检查）                                                                                       
      27 +- CLI: clap（derive API）                                                                                              
      28 +- Git: git2（Rust git 绑定）                                                                                           
      29                                                                                                                         
      30  ## 项目结构                                                                                                            
      31                                                                                                                         
      32  ```                                                                                                                    
      33  hrev/                                                                                                                  
      29 -├── hrev/                                                                                                              
      30 -│   ├── __init__.py                                                                                                    
      31 -│   ├── cli.py          # CLI 入口                                                                                     
      32 -│   ├── server.py       # FastAPI 服务器                                                                               
      33 -│   ├── git_ops.py      # Git 操作封装                                                                                 
      34 -│   ├── models.py       # 数据模型                                                                                     
      34 +├── Cargo.toml                                                                                                         
      35 +├── Cargo.lock                                                                                                         
      36 +├── src/                                                                                                               
      37 +│   ├── main.rs           # CLI 入口                                                                                   
      38 +│   ├── cli.rs            # 命令行参数解析                                                                             
      39 +│   ├── server.rs         # Axum 服务器                                                                                
      40 +│   ├── git_ops.rs        # Git 操作封装                                                                               
      41 +│   ├── models.rs         # 数据模型                                                                                   
      42  │   └── templates/                                                                                                     
      36 -│       └── review.html # 前端页面                                                                                     
      37 -│   └── static/                                                                                                        
      38 -│       ├── style.css                                                                                                  
      39 -│       └── app.js                                                                                                     
      40 -├── pyproject.toml                                                                                                     
      43 +│       └── review.html   # 前端页面（askama）                                                                         
      44 +├── skills/                                                                                                            
      45 +│   └── review-changes.md # Claude Code skill 定义                                                                     
      46  └── README.md                                                                                                          
      47  ```                                                                                                                    
      48                                                                                                                         
  ⎿  Running PostToolUse hook…                                                                                                   
                                                                                                                                 
⏺ Plan(Design Rust-based hrev CLI tool)                                                                                          
  ⎿  Done (16 tool uses · 0 tokens · 6m 24s)                                                                                     
  ⎿  Running PostToolUse hook…                                                                                                   
                                                                                                                                 
⏺ 好的，这些问题很关键！让我更新计划文件，详细解答这些技术细节。                                                                 
                                                                                                                                 
⏺ Reading Plan(~/.claude/plans/zippy-plotting-ladybug.md)                                                                        
  ⎿  Read 372 lines                                                                                                              
  ⎿  Running PostToolUse hooks… (0/2 done)                                                                                       
                                                                                                                                 
⏺ 现在让我更新计划，详细解答这四个关键技术问题：                                                                                 
                                                                                                                                 
⏺ Updated plan                                                                                                                   
  ⎿  /plan to preview                                                                                                            
      358  }                                                                                                                     
      359  ```                                                                                                                   
      360                                                                                                                        
      361 -### 9. 验证计划                                                                                                       
      361 +### 9. 四个关键技术问题的解决方案                                                                                     
      362                                                                                                                        
      363 +#### 问题 1: 用户评论怎么收集？                                                                                       
      364 +                                                                                                                      
      365 +**前端收集流程：**                                                                                                    
      366 +                                                                                                                      
      367 +```javascript                                                                                                         
      368 +// static/app.js - 评论收集逻辑                                                                                       
      369 +class ReviewApp {                                                                                                     
      370 +    constructor() {                                                                                                   
      371 +        this.comments = [];                                                                                           
      372 +        this.currentFile = null;                                                                                      
      373 +    }                                                                                                                 
      374 +                                                                                                                      
      375 +    // 点击行时触发                                                                                                   
      376 +    onLineClick(lineElement) {                                                                                        
      377 +        const file = this.currentFile;                                                                                
      378 +        const line = parseInt(lineElement.dataset.line);                                                              
      379 +        this.openCommentModal(file, line);                                                                            
      380 +    }                                                                                                                 
      381 +                                                                                                                      
      382 +    // 打开评论输入弹窗                                                                                               
      383 +    openCommentModal(file, line) {                                                                                    
      384 +        const modal = document.getElementById('comment-modal');                                                       
      385 +        const textarea = document.getElementById('comment-text');                                                     
      386 +                                                                                                                      
      387 +        modal.dataset.file = file;                                                                                    
      388 +        modal.dataset.line = line;                                                                                    
      389 +        textarea.value = '';                                                                                          
      390 +        modal.classList.remove('hidden');                                                                             
      391 +    }                                                                                                                 
      392 +                                                                                                                      
      393 +    // 提交评论                                                                                                       
      394 +    async submitComment() {                                                                                           
      395 +        const modal = document.getElementById('comment-modal');                                                       
      396 +        const comment = {                                                                                             
      397 +            file: modal.dataset.file,                                                                                 
      398 +            line: parseInt(modal.dataset.line),                                                                       
      399 +            text: document.getElementById('comment-text').value,                                                      
      400 +            timestamp: new Date().toISOString()                                                                       
      401 +        };                                                                                                            
      402 +                                                                                                                      
      403 +        // 发送到后端                                                                                                 
      404 +        const response = await fetch('/api/comments', {                                                               
      405 +            method: 'POST',                                                                                           
      406 +            headers: { 'Content-Type': 'application/json' },                                                          
      407 +            body: JSON.stringify(comment)                                                                             
      408 +        });                                                                                                           
      409 +                                                                                                                      
      410 +        const saved = await response.json();                                                                          
      411 +        this.comments.push(saved);                                                                                    
      412 +        this.renderComments();                                                                                        
      413 +        modal.classList.add('hidden');                                                                                
      414 +    }                                                                                                                 
      415 +}                                                                                                                     
      416 +```                                                                                                                   
      417 +                                                                                                                      
      418 +**后端存储流程：**                                                                                                    
      419 +                                                                                                                      
      420 +```rust                                                                                                               
      421 +// src/storage.rs - 评论持久化                                                                                        
      422 +use serde_json;                                                                                                       
      423 +use std::fs;                                                                                                          
      424 +use std::path::PathBuf;                                                                                               
      425 +                                                                                                                      
      426 +pub struct CommentStorage {                                                                                           
      427 +    session_dir: PathBuf,                                                                                             
      428 +}                                                                                                                     
      429 +                                                                                                                      
      430 +impl CommentStorage {                                                                                                 
      431 +    pub fn new(session_id: &str) -> Result<Self> {                                                                    
      432 +        let dir = dirs::home_dir()                                                                                    
      433 +            .unwrap()                                                                                                 
      434 +            .join(".hrev")                                                                                            
      435 +            .join("sessions")                                                                                         
      436 +            .join(session_id);                                                                                        
      437 +        fs::create_dir_all(&dir)?;                                                                                    
      438 +        Ok(Self { session_dir: dir })                                                                                 
      439 +    }                                                                                                                 
      440 +                                                                                                                      
      441 +    // 添加评论                                                                                                       
      442 +    pub fn add_comment(&self, comment: &Comment) -> Result<()> {                                                      
      443 +        let mut comments = self.load_comments()?;                                                                     
      444 +        comments.push(comment.clone());                                                                               
      445 +        self.save_comments(&comments)?;                                                                               
      446 +        Ok(())                                                                                                        
      447 +    }                                                                                                                 
      448 +                                                                                                                      
      449 +    // 保存到 JSON                                                                                                    
      450 +    fn save_comments(&self, comments: &[Comment]) -> Result<()> {                                                     
      451 +        let path = self.session_dir.join("comments.json");                                                            
      452 +        let json = serde_json::to_string_pretty(comments)?;                                                           
      453 +        fs::write(path, json)?;                                                                                       
      454 +        Ok(())                                                                                                        
      455 +    }                                                                                                                 
      456 +}                                                                                                                     
      457 +```                                                                                                                   
      458 +                                                                                                                      
      459 +#### 问题 2: JS 渲染 diff 用哪一套？                                                                                  
      460 +                                                                                                                      
      461 +**方案：自己实现简单 diff 渲染，不依赖外部库**                                                                        
      462 +                                                                                                                      
      463 +```javascript                                                                                                         
      464 +// static/app.js - 简单 diff 渲染                                                                                     
      465 +class DiffRenderer {                                                                                                  
      466 +    // 渲染单个 diff hunk                                                                                             
      467 +    renderHunk(hunk) {                                                                                                
      468 +        return hunk.lines.map(line => {                                                                               
      469 +            const className = line.type === 'added' ? 'added' :                                                       
      470 +                            line.type === 'removed' ? 'removed' : 'context';                                          
      471 +            const prefix = line.type === 'added' ? '+' :                                                              
      472 +                          line.type === 'removed' ? '-' : ' ';                                                        
      473 +            return `<div class="diff-line ${className}"                                                               
      474 +                        data-file="${hunk.file}"                                                                      
      475 +                        data-line="${line.number}"                                                                    
      476 +                        onclick="app.onLineClick(this)">                                                              
      477 +                        <span class="line-prefix">${prefix}</span>                                                    
      478 +                        <span class="line-content">${this.escapeHtml(line.content)}</span>                            
      479 +                    </div>`;                                                                                          
      480 +        }).join('');                                                                                                  
      481 +    }                                                                                                                 
      482 +                                                                                                                      
      483 +    // 渲染文件 diff                                                                                                  
      484 +    renderFileDiff(fileDiff) {                                                                                        
      485 +        return `                                                                                                      
      486 +            <div class="file-diff" data-file="${fileDiff.path}">                                                      
      487 +                <div class="file-header">                                                                             
      488 +                    <span class="file-status ${fileDiff.status}">${fileDiff.status}</span>                            
      489 +                    <span class="file-path">${fileDiff.path}</span>                                                   
      490 +                </div>                                                                                                
      491 +                ${fileDiff.hunks.map(h => this.renderHunk(h)).join('')}                                               
      492 +            </div>                                                                                                    
      493 +        `;                                                                                                            
      494 +    }                                                                                                                 
      495 +                                                                                                                      
      496 +    escapeHtml(text) {                                                                                                
      497 +        const div = document.createElement('div');                                                                    
      498 +        div.textContent = text;                                                                                       
      499 +        return div.innerHTML;                                                                                         
      500 +    }                                                                                                                 
      501 +}                                                                                                                     
      502 +```                                                                                                                   
      503 +                                                                                                                      
      504 +**CSS 样式：**                                                                                                        
      505 +                                                                                                                      
      506 +```css                                                                                                                
      507 +/* static/app.css - Diff 样式 */                                                                                      
      508 +.diff-line {                                                                                                          
      509 +    font-family: 'Monaco', 'Menlo', monospace;                                                                        
      510 +    font-size: 13px;                                                                                                  
      511 +    line-height: 20px;                                                                                                
      512 +    padding: 0 10px;                                                                                                  
      513 +    cursor: pointer;                                                                                                  
      514 +    transition: background 0.15s;                                                                                     
      515 +}                                                                                                                     
      516 +                                                                                                                      
      517 +.diff-line:hover {                                                                                                    
      518 +    background: rgba(255, 255, 255, 0.05);                                                                            
      519 +}                                                                                                                     
      520 +                                                                                                                      
      521 +.diff-line .line-prefix {                                                                                             
      522 +    color: #8b949e;                                                                                                   
      523 +    margin-right: 10px;                                                                                               
      524 +    user-select: none;                                                                                                
      525 +}                                                                                                                     
      526 +                                                                                                                      
      527 +.diff-line.added {                                                                                                    
      528 +    background: rgba(46, 160, 67, 0.15);                                                                              
      529 +}                                                                                                                     
      530 +                                                                                                                      
      531 +.diff-line.added .line-prefix {                                                                                       
      532 +    color: #3fb950;                                                                                                   
      533 +}                                                                                                                     
      534 +                                                                                                                      
      535 +.diff-line.removed {                                                                                                  
      536 +    background: rgba(248, 81, 73, 0.15);                                                                              
      537 +}                                                                                                                     
      538 +                                                                                                                      
      539 +.diff-line.removed .line-prefix {                                                                                     
      540 +    color: #f85149;                                                                                                   
      541 +}                                                                                                                     
      542 +```                                                                                                                   
      543 +                                                                                                                      
      544 +#### 问题 3: 怎么打包到一个 binary 里面？                                                                             
      545 +                                                                                                                      
      546 +**方案：使用 `rust-embed` 嵌入静态资源**                                                                              
      547 +                                                                                                                      
      548 +```toml                                                                                                               
      549 +# Cargo.toml 添加依赖                                                                                                 
      550 +[dependencies]                                                                                                        
      551 +rust-embed = "8.0"                                                                                                    
      552 +mime_guess = "2.0"                                                                                                    
      553 +```                                                                                                                   
      554 +                                                                                                                      
      555 +```rust                                                                                                               
      556 +// src/static_assets.rs - 嵌入静态资源                                                                                
      557 +use rust_embed::RustEmbed;                                                                                            
      558 +                                                                                                                      
      559 +#[derive(RustEmbed)]                                                                                                  
      560 +#[folder = "static/"]                                                                                                 
      561 +struct Assets;                                                                                                        
      562 +                                                                                                                      
      563 +#[derive(RustEmbed)]                                                                                                  
      564 +#[folder = "templates/"]                                                                                              
      565 +struct Templates;                                                                                                     
      566 +                                                                                                                      
      567 +pub fn get_asset(path: &str) -> Option<(Vec<u8>, &'static str)> {                                                     
      568 +    let asset = Assets::get(path.trim_start_matches('/'))?;                                                           
      569 +    let mime = mime_guess::from_path(path)                                                                            
      570 +        .first_or_octet_stream()                                                                                      
      571 +        .to_string();                                                                                                 
      572 +    // leak 转换为 &'static str，生命周期与程序相同                                                                   
      573 +    let mime_static: &'static str = Box::leak(mime.into_boxed_str());                                                 
      574 +    Some((asset.data.to_vec(), mime_static))                                                                          
      575 +}                                                                                                                     
      576 +                                                                                                                      
      577 +pub fn get_template(name: &str) -> Option<String> {                                                                   
      578 +    let asset = Templates::get(name)?;                                                                                
      579 +    Some(String::from_utf8(asset.data.to_vec()).ok()?)                                                                
      580 +}                                                                                                                     
      581 +```                                                                                                                   
      582 +                                                                                                                      
      583 +**Axum 路由使用嵌入资源：**                                                                                           
      584 +                                                                                                                      
      585 +```rust                                                                                                               
      586 +// src/routes.rs                                                                                                      
      587 +use axum::{response::Response, body::Body, http::{StatusCode, header}};                                               
      588 +use crate::static_assets::get_asset;                                                                                  
      589 +                                                                                                                      
      590 +pub async fn serve_static(path: String) -> Response {                                                                 
      591 +    match get_asset(&path) {                                                                                          
      592 +        Some((data, mime)) => {                                                                                       
      593 +            let mut response = Response::new(Body::from(data));                                                       
      594 +            response.headers_mut().insert(                                                                            
      595 +                header::CONTENT_TYPE,                                                                                 
      596 +                header::HeaderValue::from_static(mime)                                                                
      597 +            );                                                                                                        
      598 +            response                                                                                                  
      599 +        }                                                                                                             
      600 +        None => {                                                                                                     
      601 +            let mut response = Response::new(Body::empty());                                                          
      602 +            *response.status_mut() = StatusCode::NOT_FOUND;                                                           
      603 +            response                                                                                                  
      604 +        }                                                                                                             
      605 +    }                                                                                                                 
      606 +}                                                                                                                     
      607 +                                                                                                                      
      608 +pub async fn serve_index() -> Response {                                                                              
      609 +    match get_template("review.html") {                                                                               
      610 +        Some(html) => {                                                                                               
      611 +            let mut response = Response::new(Body::from(html));                                                       
      612 +            response.headers_mut().insert(                                                                            
      613 +                header::CONTENT_TYPE,                                                                                 
      614 +                header::HeaderValue::from_static("text/html; charset=utf-8")                                          
      615 +            );                                                                                                        
      616 +            response                                                                                                  
      617 +        }                                                                                                             
      618 +        None => {                                                                                                     
      619 +            let mut response = Response::new(Body::from("Template not found"));                                       
      620 +            *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;                                               
      621 +            response                                                                                                  
      622 +        }                                                                                                             
      623 +    }                                                                                                                 
      624 +}                                                                                                                     
      625 +```                                                                                                                   
      626 +                                                                                                                      
      627 +**项目结构调整：**                                                                                                    
      628 +                                                                                                                      
      629 +```                                                                                                                   
      630 +hrev/                                                                                                                 
      631 +├── Cargo.toml                                                                                                        
      632 +├── src/                                                                                                              
      633 +│   ├── main.rs                                                                                                       
      634 +│   ├── static_assets.rs    # 嵌入资源                                                                                
      635 +│   └── ...                                                                                                           
      636 +├── static/                 # 开发时存在，编译后嵌入二进制                                                            
      637 +│   ├── app.js                                                                                                        
      638 +│   └── app.css                                                                                                       
      639 +└── templates/                                                                                                        
      640 +    └── review.html                                                                                                   
      641 +```                                                                                                                   
      642 +                                                                                                                      
      643 +**编译后：**                                                                                                          
      644 +- `cargo build --release` 生成单一二进制 `target/release/hrev`                                                        
      645 +- 无需携带 static/ 目录                                                                                               
      646 +- 可直接复制到任何地方运行                                                                                            
      647 +                                                                                                                      
      648 +#### 问题 4: 如何将用户的评论与文件、行号、上下文整理为合适的输出？                                                   
      649 +                                                                                                                      
      650 +**输出格式设计：**                                                                                                    
      651 +                                                                                                                      
      652 +```rust                                                                                                               
      653 +// src/output.rs - 评论输出格式                                                                                       
      654 +use serde::{Deserialize, Serialize};                                                                                  
      655 +                                                                                                                      
      656 +#[derive(Debug, Serialize, Deserialize)]                                                                              
      657 +pub struct ReviewOutput {                                                                                             
      658 +    pub session_id: String,                                                                                           
      659 +    pub review_type: String,                                                                                          
      660 +    pub target: String,                                                                                               
      661 +    pub created_at: String,                                                                                           
      662 +    pub completed_at: String,                                                                                         
      663 +    pub summary: ReviewSummary,                                                                                       
      664 +    pub comments: Vec<GroupedComment>,                                                                                
      665 +}                                                                                                                     
      666 +                                                                                                                      
      667 +#[derive(Debug, Serialize, Deserialize)]                                                                              
      668 +pub struct ReviewSummary {                                                                                            
      669 +    pub total_comments: usize,                                                                                        
      670 +    pub total_files: usize,                                                                                           
      671 +    pub files_with_comments: Vec<String>,                                                                             
      672 +    pub comment_by_type: CommentTypeStats,                                                                            
      673 +}                                                                                                                     
      674 +                                                                                                                      
      675 +#[derive(Debug, Serialize, Deserialize)]                                                                              
      676 +pub struct CommentTypeStats {                                                                                         
      677 +    pub suggestion: usize,                                                                                            
      678 +    pub question: usize,                                                                                              
      679 +    pub issue: usize,                                                                                                 
      680 +    pub approval: usize,                                                                                              
      681 +}                                                                                                                     
      682 +                                                                                                                      
      683 +#[derive(Debug, Serialize, Deserialize)]                                                                              
      684 +pub struct GroupedComment {                                                                                           
      685 +    pub file: String,                                                                                                 
      686 +    pub comments: Vec<FileComment>,                                                                                   
      687 +}                                                                                                                     
      688 +                                                                                                                      
      689 +#[derive(Debug, Serialize, Deserialize)]                                                                              
      690 +pub struct FileComment {                                                                                              
      691 +    pub line: usize,                                                                                                  
      692 +    pub text: String,                                                                                                 
      693 +    pub context_before: Vec<String>,  // 上文 2 行                                                                    
      694 +    pub context_after: Vec<String>,   // 下文 2 行                                                                    
      695 +    pub created_at: String,                                                                                           
      696 +}                                                                                                                     
      697 +                                                                                                                      
      698 +// 输出为 Markdown 格式（供 AI 阅读）                                                                                 
      699 +pub fn output_markdown(output: &ReviewOutput) -> String {                                                             
      700 +    let mut md = String::new();                                                                                       
      701 +                                                                                                                      
      702 +    md.push_str(&format!("# Code Review Results\n\n"));                                                               
      703 +    md.push_str(&format!("**Target:** {} ({})\n", output.target, output.review_type));                                
      704 +    md.push_str(&format!("**Comments:** {} across {} file(s)\n\n",                                                    
      705 +        output.summary.total_comments,                                                                                
      706 +        output.summary.total_files));                                                                                 
      707 +                                                                                                                      
      708 +    md.push_str("## Comments by File\n\n");                                                                           
      709 +                                                                                                                      
      710 +    for group in &output.comments {                                                                                   
      711 +        md.push_str(&format!("### `{}`\n\n", group.file));                                                            
      712 +                                                                                                                      
      713 +        for comment in &group.comments {                                                                              
      714 +            md.push_str(&format!("#### Line {}\n\n", comment.line));                                                  
      715 +                                                                                                                      
      716 +            // 显示上下文                                                                                             
      717 +            md.push_str("**Context:**\n```\n");                                                                       
      718 +            for (i, line) in comment.context_before.iter().enumerate() {                                              
      719 +                md.push_str(&format!("{}  {}\n",                                                                      
      720 +                    comment.line as isize - comment.context_before.len() as isize + 1 + i as isize,                   
      721 +                    line));                                                                                           
      722 +            }                                                                                                         
      723 +            md.push_str(&format!("{}> {}\n", comment.line, ">".repeat(comment.text.chars().count().min(40))));        
      724 +            for (i, line) in comment.context_after.iter().enumerate() {                                               
      725 +                md.push_str(&format!("{}  {}\n", comment.line + 1 + i, line));                                        
      726 +            }                                                                                                         
      727 +            md.push_str("```\n\n");                                                                                   
      728 +                                                                                                                      
      729 +            md.push_str(&format!("**Comment:** {}\n\n", comment.text));                                               
      730 +        }                                                                                                             
      731 +    }                                                                                                                 
      732 +                                                                                                                      
      733 +    md                                                                                                                
      734 +}                                                                                                                     
      735 +                                                                                                                      
      736 +// 输出为 JSON 格式（供程序解析）                                                                                     
      737 +pub fn output_json(output: &ReviewOutput) -> String {                                                                 
      738 +    serde_json::to_string_pretty(output).unwrap()                                                                     
      739 +}                                                                                                                     
      740 +                                                                                                                      
      741 +// 终端输出（人类可读）                                                                                               
      742 +pub fn print_terminal(output: &ReviewOutput) {                                                                        
      743 +    println!("╔════════════════════════════════════════════════════════════╗");                                       
      744 +    println!("║                    Code Review Complete                    ║");                                       
      745 +    println!("╚════════════════════════════════════════════════════════════╝");                                       
      746 +    println!();                                                                                                       
      747 +    println!("  Target: {} ({})", output.target, output.review_type);                                                 
      748 +    println!("  Comments: {} across {} file(s)",                                                                      
      749 +        output.summary.total_colors,                                                                                  
      750 +        output.summary.total_files);                                                                                  
      751 +    println!();                                                                                                       
      752 +                                                                                                                      
      753 +    for group in &output.comments {                                                                                   
      754 +        println!("  📄 {}", group.file);                                                                              
      755 +        for comment in &group.comments {                                                                              
      756 +            println!("    [L{}] {}", comment.line,                                                                    
      757 +                comment.text.chars().take(50).collect::<String>());                                                   
      758 +        }                                                                                                             
      759 +        println!();                                                                                                   
      760 +    }                                                                                                                 
      761 +                                                                                                                      
      762 +    println!("  💾 Full results saved to:");                                                                          
      763 +    println!("     .hrev/sessions/{}/review.json", output.session_id);                                                
      764 +    println!("     .hrev/sessions/{}/review.md", output.session_id);                                                  
      765 +}                                                                                                                     
      766 +```                                                                                                                   
      767 +                                                                                                                      
      768 +**带上下文的评论收集：**                                                                                              
      769 +                                                                                                                      
      770 +```rust                                                                                                               
      771 +// src/git_ops.rs - 获取上下文                                                                                        
      772 +impl GitOps {                                                                                                         
      773 +    /// 获取指定行的上下文                                                                                            
      774 +    pub fn get_line_context(                                                                                          
      775 +        &self,                                                                                                        
      776 +        file_path: &str,                                                                                              
      777 +        line: usize,                                                                                                  
      778 +        before: usize,                                                                                                
      779 +        after: usize,                                                                                                 
      780 +    ) -> Result<(Vec<String>, Vec<String>)> {                                                                         
      781 +        let content = self.get_file_content(file_path, None)?;                                                        
      782 +        let lines: Vec<&str> = content.lines().collect();                                                             
      783 +                                                                                                                      
      784 +        let start = if line > before { line - before - 1 } else { 0 };                                                
      785 +        let end = (line + after).min(lines.len());                                                                    
      786 +                                                                                                                      
      787 +        let context_before = lines[start..line].to_vec();                                                             
      788 +        let context_after = lines[line..end].to_vec();                                                                
      789 +                                                                                                                      
      790 +        Ok((                                                                                                          
      791 +            context_before.into_iter().map(|s| s.to_string()).collect(),                                              
      792 +            context_after.into_iter().map(|s| s.to_string()).collect(),                                               
      793 +        ))                                                                                                            
      794 +    }                                                                                                                 
      795 +}                                                                                                                     
      796 +```                                                                                                                   
      797 +                                                                                                                      
      798 +### 10. 验证计划                                                                                                      
      799 +                                                                                                                      
      800  完成后的测试流程：                                                                                                    
      801  1. `cargo build --release` - 编译二进制                                                                               
      802  2. `./target/release/hrev diff` - 测试 diff 模式                                                                      
      803  3. `./target/release/hrev plan.md` - 测试文件模式                                                                     
      804  4. `./target/release/hrev abc1234` - 测试 commit 模式                                                                 
      805  5. 在浏览器中添加评论，验证保存和导出                                                                                 
      369 -6. 测试 Claude Code skill 集成                                                                                        
      806 +6. 检查 `.hrev/sessions/` 目录查看输出文件                                                                            
      807 +7. 测试 Claude Code skill 集成                                                                                        
      808                                                                                                                        
      809  ---                                                                                                                   
  ⎿  Running PostToolUse hook…                                                                                                   
                                                                                                                                 
⏺ 计划已更新，包含四个关键技术问题的详细解决方案：                                                                               
                                                                                                                                 
  问题 1: 用户评论收集 - 前端 JS 点击行 → 弹窗输入 → POST 到后端 API → 保存到 JSON                                               
                                                                                                                                 
  问题 2: JS diff 渲染 - 自己实现简单的 diff 渲染器，不依赖外部库，用 CSS 实现添加/删除行的样式                                  
                                                                                                                                 
  问题 3: 单一二进制打包 - 使用 rust-embed 在编译时将 static/ 和 templates/ 目录嵌入二进制                                       
                                                                                                                                 
  问题 4: 评论输出格式 - 三种格式：                                                                                              
  - 终端输出（人类可读摘要）                                                                                                     
  - Markdown（供 AI 阅读详细评论+上下文）                                                                                        
  - JSON（供程序解析）                                                                                                           
  ⎿  Tool use rejected with user message: 继续                                                                                   
                                                                                                                                 
⏺ 计划已经比较完整了。让我再次请求批准以开始实现：                                                                               
  ⎿  Tool use rejected with user message: 继续调整方案。我还需要review                                                           
                                                                                                                                 
⏺ 明白，让我问一些问题来更好地理解你的需求：                                                                                     
                                                                                                                                 
⏺ User answered Claude's questions:                                                                                              
  ⎿  · 评论输出格式主要给谁看？这会影响输出设计。 → 两者兼顾                                                                     
     · 评论是否需要支持分类标签？方便 AI 理解评论性质。 → 简单即可                                                               
     · 审查完成后希望如何与 AI 交互？ → 自动处理                                                                                 
  ⎿  Running PostToolUse hook…                                                                                                   
                                                                                                                                 
⏺ 明白了！根据你的回答，让我调整方案：                                                                                           
  - 输出格式：两者兼顾（人类可读 + AI 可解析）                                                                                   
  - 评论：简单纯文本即可                                                                                                         
  - AI 交互：自动处理（skill 读取评论后自动修改代码）                                                                            
                                                                                                                                 
⏺ Updated plan                                                                                                                   
  ⎿  /plan to preview                                                                                                            
      647                                                                                                                        
      648  #### 问题 4: 如何将用户的评论与文件、行号、上下文整理为合适的输出？                                                   
      649                                                                                                                        
      650 -**输出格式设计：**                                                                                                    
      650 +**输出格式设计（两者兼顾：人类可读 + AI 可解析）：**                                                                  
      651                                                                                                                        
      652  ```rust                                                                                                               
      653  // src/output.rs - 评论输出格式                                                                                       
     ...                                                                                                                         
      669      pub total_comments: usize,                                                                                        
      670      pub total_files: usize,                                                                                           
      671      pub files_with_comments: Vec<String>,                                                                             
      672 -    pub comment_by_type: CommentTypeStats,                                                                            
      672  }                                                                                                                     
      673                                                                                                                        
      674  #[derive(Debug, Serialize, Deserialize)]                                                                              
      676 -pub struct CommentTypeStats {                                                                                         
      677 -    pub suggestion: usize,                                                                                            
      678 -    pub question: usize,                                                                                              
      679 -    pub issue: usize,                                                                                                 
      680 -    pub approval: usize,                                                                                              
      681 -}                                                                                                                     
      682 -                                                                                                                      
      683 -#[derive(Debug, Serialize, Deserialize)]                                                                              
      675  pub struct GroupedComment {                                                                                           
      676      pub file: String,                                                                                                 
      677      pub comments: Vec<FileComment>,                                                                                   
  ⎿  Running PostToolUse hook…                                                                                                   
                                                                                                                                 
⏺ Updated plan                                                                                                                   
  ⎿  Error editing file                                                                                                          
                                                                                                                                 
⏺ Updated plan                                                                                                                   
  ⎿  /plan to preview                                                                                                            
      686      pub created_at: String,                                                                                           
      687  }                                                                                                                     
      688                                                                                                                        
      689 -// 输出为 Markdown 格式（供 AI 阅读）                                                                                 
      689 +// 输出为 Markdown 格式（AI 友好 + 人类可读）                                                                         
      690  pub fn output_markdown(output: &ReviewOutput) -> String {                                                             
      691      let mut md = String::new();                                                                                       
      692                                                                                                                        
      693 -    md.push_str(&format!("# Code Review Results\n\n"));                                                               
      694 -    md.push_str(&format!("**Target:** {} ({})\n", output.target, output.review_type));                                
      695 -    md.push_str(&format!("**Comments:** {} across {} file(s)\n\n",                                                    
      696 -        output.summary.total_comments,                                                                                
      697 -        output.summary.total_files));                                                                                 
      693 +    // 顶部摘要（人类快速查看）                                                                                       
      694 +    md.push_str("╔════════════════════════════════════════════════════════════╗\n");                                  
      695 +    md.push_str(&format!("║  Review Results: {} | {} comment(s) ║\n",                                                 
      696 +        output.target, output.summary.total_comments));                                                               
      697 +    md.push_str("╚════════════════════════════════════════════════════════════╝\n\n");                                
      698                                                                                                                        
      699 -    md.push_str("## Comments by File\n\n");                                                                           
      699 +    // AI 结构化区域（用特殊标记）                                                                                    
      700 +    md.push_str("<!-- HREV_AI_START -->\n");                                                                          
      701 +    md.push_str(&format!("## Review: {} ({})\n\n", output.target, output.review_type));                               
      702 +    md.push_str(&format!("**Files:** {}\n", output.summary.files_with_comments.join(", ")));                          
      703 +    md.push_str(&format!("**Comments:** {} total\n\n", output.summary.total_comments));                               
      704                                                                                                                        
      705      for group in &output.comments {                                                                                   
      702 -        md.push_str(&format!("### `{}`\n\n", group.file));                                                            
      706 +        md.push_str(&format!("### File: {}\n\n", group.file));                                                        
      707                                                                                                                        
      708          for comment in &group.comments {                                                                              
      705 -            md.push_str(&format!("#### Line {}\n\n", comment.line));                                                  
      709 +            md.push_str(&format!("#### Line {}: {}\n\n",                                                              
      710 +                comment.line, comment.text));                                                                         
      711                                                                                                                        
      707 -            // 显示上下文                                                                                             
      708 -            md.push_str("**Context:**\n```\n");                                                                       
      712 +            // 上下文代码块                                                                                           
      713 +            md.push_str("```context\n");                                                                              
      714              for (i, line) in comment.context_before.iter().enumerate() {                                              
      710 -                md.push_str(&format!("{}  {}\n",                                                                      
      711 -                    comment.line as isize - comment.context_before.len() as isize + 1 + i as isize,                   
      712 -                    line));                                                                                           
      715 +                let line_num = comment.line as isize - comment.context_before.len() as isize + 1 + i as isize;        
      716 +                md.push_str(&format!("{:>4} | {}\n", line_num, line));                                                
      717              }                                                                                                         
      714 -            md.push_str(&format!("{}> {}\n", comment.line, ">".repeat(comment.text.chars().count().min(40))));        
      718 +            md.push_str(&format!("{:>4} | >>> {}\n", comment.line, comment.text));                                    
      719              for (i, line) in comment.context_after.iter().enumerate() {                                               
      716 -                md.push_str(&format!("{}  {}\n", comment.line + 1 + i, line));                                        
      720 +                md.push_str(&format!("{:>4} | {}\n", comment.line + 1 + i, line));                                    
      721              }                                                                                                         
      722              md.push_str("```\n\n");                                                                                   
      719 -                                                                                                                      
      720 -            md.push_str(&format!("**Comment:** {}\n\n", comment.text));                                               
      723          }                                                                                                             
      724      }                                                                                                                 
      725 +    md.push_str("<!-- HREV_AI_END -->\n\n");                                                                          
      726                                                                                                                        
      727      md                                                                                                                
      728  }                                                                                                                     
  ⎿  Running PostToolUse hook…                                                                                                   
                                                                                                                                 
⏺ Hook 提示了 XSS 安全问题，我会在实现时注意使用 DOMPurify 或 textContent。继续补充 Rust 代码实现部分：                          
                                                                                                                                 
⏺ Updated plan                                                                                                                   
  ⎿  /plan to preview                                                                                                            
       224  }                                                                                                                    
       225  ```                                                                                                                  
       226                                                                                                                       
       227 -### 5. Git 操作封装                                                                                                  
       227 +### 5. Rust 代码实现（详细）                                                                                         
       228                                                                                                                       
       229 +#### 5.1 Cargo.toml                                                                                                  
       230 +                                                                                                                     
       231 +```toml                                                                                                              
       232 +[package]                                                                                                            
       233 +name = "hrev"                                                                                                        
       234 +version = "0.1.0"                                                                                                    
       235 +edition = "2021"                                                                                                     
       236 +description = "Human review CLI tool"                                                                                
       237 +license = "MIT"                                                                                                      
       238 +                                                                                                                     
       239 +[dependencies]                                                                                                       
       240 +# CLI                                                                                                                
       241 +clap = { version = "4.5", features = ["derive"] }                                                                    
       242 +                                                                                                                     
       243 +# Async runtime                                                                                                      
       244 +tokio = { version = "1.40", features = ["full"] }                                                                    
       245 +                                                                                                                     
       246 +# Web framework                                                                                                      
       247 +axum = { version = "0.7", features = ["ws"] }                                                                        
       248 +tower = "0.5"                                                                                                        
       249 +tower-http = { version = "0.5", features = ["fs", "cors"] }                                                          
       250 +                                                                                                                     
       251 +# Templating                                                                                                         
       252 +askama = { version = "0.12", features = ["with-axum"] }                                                              
       253 +                                                                                                                     
       254 +# Serialization                                                                                                      
       255 +serde = { version = "1.0", features = ["derive"] }                                                                   
       256 +serde_json = "1.0"                                                                                                   
       257 +                                                                                                                     
       258 +# Git operations                                                                                                     
       259 +git2 = "0.19"                                                                                                        
       260 +                                                                                                                     
       261 +# Date/time                                                                                                          
       262 +chrono = { version = "0.4", features = ["serde"] }                                                                   
       263 +                                                                                                                     
       264 +# Hashing                                                                                                            
       265 +sha2 = "0.10"                                                                                                        
       266 +                                                                                                                     
       267 +# UUID                                                                                                               
       268 +uuid = { version = "1.10", features = ["v4", "serde"] }                                                              
       269 +                                                                                                                     
       270 +# Error handling                                                                                                     
       271 +anyhow = "1.0"                                                                                                       
       272 +thiserror = "1.0"                                                                                                    
       273 +                                                                                                                     
       274 +# Logging                                                                                                            
       275 +tracing = "0.1"                                                                                                      
       276 +tracing-subscriber = "0.3"                                                                                           
       277 +                                                                                                                     
       278 +# Browser open                                                                                                       
       279 +open = "5.3"                                                                                                         
       280 +                                                                                                                     
       281 +# Terminal colors                                                                                                    
       282 +colored = "2.1"                                                                                                      
       283 +```                                                                                                                  
       284 +                                                                                                                     
       285 +#### 5.2 src/main.rs                                                                                                 
       286 +                                                                                                                     
       287  ```rust                                                                                                              
       288 +use anyhow::Result;                                                                                                  
       289 +use clap::Parser;                                                                                                    
       290 +use tracing::{info, warn};                                                                                           
       291 +                                                                                                                     
       292 +mod cli;                                                                                                             
       293 +mod server;                                                                                                          
       294 +mod git_ops;                                                                                                         
       295 +mod models;                                                                                                          
       296 +mod storage;                                                                                                         
       297 +mod routes;                                                                                                          
       298 +mod output;                                                                                                          
       299 +                                                                                                                     
       300 +use cli::Args;                                                                                                       
       301 +use git_ops::parse_input;                                                                                            
       302 +use storage::ReviewStorage;                                                                                          
       303 +                                                                                                                     
       304 +#[tokio::main]                                                                                                       
       305 +async fn main() -> Result<()> {                                                                                      
       306 +    // 初始化日志                                                                                                    
       307 +    tracing_subscriber::fmt::init();                                                                                 
       308 +                                                                                                                     
       309 +    let args = Args::parse();                                                                                        
       310 +                                                                                                                     
       311 +    // 解析输入                                                                                                      
       312 +    let input = parse_input(&args.input)?;                                                                           
       313 +    info!("Parsed input: {:?}", input);                                                                              
       314 +                                                                                                                     
       315 +    // 创建存储                                                                                                      
       316 +    let storage = ReviewStorage::new(&input)?;                                                                       
       317 +    let data = storage.load_or_create()?;                                                                            
       318 +                                                                                                                     
       319 +    // 启动服务器                                                                                                    
       320 +    let port = server::run(args.port, data.clone()).await?;                                                          
       321 +                                                                                                                     
       322 +    // 打开浏览器                                                                                                    
       323 +    if !args.no_browser {                                                                                            
       324 +        if let Err(e) = open::that(format!("http://localhost:{}", port)) {                                           
       325 +            warn!("Failed to open browser: {}", e);                                                                  
       326 +            eprintln!("Please open http://localhost:{} in your browser", port);                                      
       327 +        }                                                                                                            
       328 +    }                                                                                                                
       329 +                                                                                                                     
       330 +    // 等待服务器完成                                                                                                
       331 +    server::wait_for_completion().await?;                                                                            
       332 +                                                                                                                     
       333 +    // 输出评论摘要                                                                                                  
       334 +    let final_data = storage.load()?;                                                                                
       335 +    output::print_summary(&final_data);                                                                              
       336 +                                                                                                                     
       337 +    Ok(())                                                                                                           
       338 +}                                                                                                                    
       339 +```                                                                                                                  
       340 +                                                                                                                     
       341 +#### 5.3 src/cli.rs                                                                                                  
       342 +                                                                                                                     
       343 +```rust                                                                                                              
       344 +use clap::Parser;                                                                                                    
       345 +                                                                                                                     
       346 +#[derive(Parser, Debug)]                                                                                             
       347 +#[command(name = "hrev")]                                                                                            
       348 +#[command(author = "hrev")]                                                                                          
       349 +#[command(version = "0.1.0")]                                                                                        
       350 +#[command(about = "Human review CLI tool", long_about = None)]                                                       
       351 +pub struct Args {                                                                                                    
       352 +    /// Input: commit hash, file path, or "diff"                                                                     
       353 +    #[arg(value_name = "INPUT")]                                                                                     
       354 +    pub input: String,                                                                                               
       355 +                                                                                                                     
       356 +    /// Port for web server (default: random available port)                                                         
       357 +    #[arg(short, long, default_value = "0")]                                                                         
       358 +    pub port: u16,                                                                                                   
       359 +                                                                                                                     
       360 +    /// Don't open browser automatically                                                                             
       361 +    #[arg(long, default_value = "false")]                                                                            
       362 +    pub no_browser: bool,                                                                                            
       363 +}                                                                                                                    
       364 +```                                                                                                                  
       365 +                                                                                                                     
       366 +#### 5.4 src/models.rs                                                                                               
       367 +                                                                                                                     
       368 +```rust                                                                                                              
       369 +use serde::{Deserialize, Serialize};                                                                                 
       370 +use chrono::{DateTime, Utc};                                                                                         
       371 +use uuid::Uuid;                                                                                                      
       372 +                                                                                                                     
       373 +/// 审查数据                                                                                                         
       374 +#[derive(Debug, Clone, Serialize, Deserialize)]                                                                      
       375 +pub struct ReviewData {                                                                                              
       376 +    /// 输入类型                                                                                                     
       377 +    pub input_type: InputType,                                                                                       
       378 +    /// 原始输入                                                                                                     
       379 +    pub input: String,                                                                                               
       380 +    /// 评论列表                                                                                                     
       381 +    pub comments: Vec<Comment>,                                                                                      
       382 +    /// 创建时间                                                                                                     
       383 +    pub created_at: DateTime<Utc>,                                                                                   
       384 +    /// 状态                                                                                                         
       385 +    pub status: ReviewStatus,                                                                                        
       386 +}                                                                                                                    
       387 +                                                                                                                     
       388 +/// 输入类型                                                                                                         
       389 +#[derive(Debug, Clone, Serialize, Deserialize)]                                                                      
       390 +#[serde(tag = "type")]                                                                                               
       391 +pub enum InputType {                                                                                                 
       392 +    #[serde(rename = "commit_diff")]                                                                                 
       393 +    CommitDiff { commit: String },                                                                                   
       394 +    #[serde(rename = "file_content")]                                                                                
       395 +    FileContent { path: String },                                                                                    
       396 +    #[serde(rename = "working_tree_diff")]                                                                           
       397 +    WorkingTreeDiff,                                                                                                 
       398 +}                                                                                                                    
       399 +                                                                                                                     
       400 +/// 评论                                                                                                             
       401 +#[derive(Debug, Clone, Serialize, Deserialize)]                                                                      
       402 +pub struct Comment {                                                                                                 
       403 +    /// 唯一 ID                                                                                                      
       404 +    pub id: String,                                                                                                  
       405 +    /// 文件路径（diff 模式下）                                                                                      
       406 +    #[serde(skip_serializing_if = "Option::is_none")]                                                                
       407 +    pub file: Option<String>,                                                                                        
       408 +    /// 行号                                                                                                         
       409 +    #[serde(skip_serializing_if = "Option::is_none")]                                                                
       410 +    pub line: Option<u32>,                                                                                           
       411 +    /// 评论内容                                                                                                     
       412 +    pub text: String,                                                                                                
       413 +    /// 创建时间                                                                                                     
       414 +    pub created_at: DateTime<Utc>,                                                                                   
       415 +    /// 已解决标记                                                                                                   
       416 +    pub resolved: bool,                                                                                              
       417 +    /// 父评论 ID（用于回复）                                                                                        
       418 +    #[serde(skip_serializing_if = "Option::is_none")]                                                                
       419 +    pub parent_id: Option<String>,                                                                                   
       420 +}                                                                                                                    
       421 +                                                                                                                     
       422 +impl Comment {                                                                                                       
       423 +    pub fn new(file: Option<String>, line: Option<u32>, text: String) -> Self {                                      
       424 +        Self {                                                                                                       
       425 +            id: Uuid::new_v4().to_string(),                                                                          
       426 +            file,                                                                                                    
       427 +            line,                                                                                                    
       428 +            text,                                                                                                    
       429 +            created_at: Utc::now(),                                                                                  
       430 +            resolved: false,                                                                                         
       431 +            parent_id: None,                                                                                         
       432 +        }                                                                                                            
       433 +    }                                                                                                                
       434 +}                                                                                                                    
       435 +                                                                                                                     
       436 +/// 审查状态                                                                                                         
       437 +#[derive(Debug, Clone, Serialize, Deserialize)]                                                                      
       438 +pub enum ReviewStatus {                                                                                              
       439 +    #[serde(rename = "in_progress")]                                                                                 
       440 +    InProgress,                                                                                                      
       441 +    #[serde(rename = "completed")]                                                                                   
       442 +    Completed,                                                                                                       
       443 +}                                                                                                                    
       444 +                                                                                                                     
       445 +/// API 响应 - 初始数据                                                                                              
       446 +#[derive(Debug, Serialize)]                                                                                          
       447 +pub struct DataResponse {                                                                                            
       448 +    pub type_: String,                                                                                               
       449 +    pub title: String,                                                                                               
       450 +    pub files: Vec<FileData>,                                                                                        
       451 +    pub comments: Vec<Comment>,                                                                                      
       452 +}                                                                                                                    
       453 +                                                                                                                     
       454 +/// 文件数据（用于前端渲染）                                                                                         
       455 +#[derive(Debug, Serialize)]                                                                                          
       456 +pub struct FileData {                                                                                                
       457 +    pub path: String,                                                                                                
       458 +    pub status: String,                                                                                              
       459 +    pub lines: Vec<LineData>,                                                                                        
       460 +}                                                                                                                    
       461 +                                                                                                                     
       462 +/// 行数据                                                                                                           
       463 +#[derive(Debug, Serialize)]                                                                                          
       464 +pub struct LineData {                                                                                                
       465 +    pub number: u32,                                                                                                 
       466 +    pub content: String,                                                                                             
       467 +    #[serde(skip_serializing_if = "Option::is_none")]                                                                
       468 +    pub type_: Option<String>, // "added", "removed", or null                                                        
       469 +}                                                                                                                    
       470 +                                                                                                                     
       471 +/// API 请求 - 添加评论                                                                                              
       472 +#[derive(Debug, Deserialize)]                                                                                        
       473 +pub struct AddCommentRequest {                                                                                       
       474 +    #[serde(rename = "file")]                                                                                        
       475 +    pub file: Option<String>,                                                                                        
       476 +    pub line: Option<u32>,                                                                                           
       477 +    pub text: String,                                                                                                
       478 +}                                                                                                                    
       479 +```                                                                                                                  
       480 +                                                                                                                     
       481 +#### 5.5 src/git_ops.rs                                                                                              
       482 +                                                                                                                     
       483 +```rust                                                                                                              
       484 +use anyhow::{Context, Result};                                                                                       
       485 +use git2::{Repository, Diff, Delta};                                                                                 
       486 +use std::path::{Path, PathBuf};                                                                                      
       487 +use std::fs;                                                                                                         
       488 +                                                                                                                     
       489 +use crate::models::{InputType, FileData, LineData, ReviewData, Comment, ReviewStatus};                               
       490 +use crate::storage::ReviewStorage;                                                                                   
       491 +                                                                                                                     
       492 +/// 解析输入                                                                                                         
       493 +pub fn parse_input(input: &str) -> Result<InputType> {                                                               
       494 +    // 检查是否是 "diff" 关键字                                                                                      
       495 +    if input == "diff" {                                                                                             
       496 +        return Ok(InputType::WorkingTreeDiff);                                                                       
       497 +    }                                                                                                                
       498 +                                                                                                                     
       499 +    // 检查是否是文件路径                                                                                            
       500 +    if Path::new(input).exists() {                                                                                   
       501 +        return Ok(InputType::FileContent {                                                                           
       502 +            path: input.to_string(),                                                                                 
       503 +        });                                                                                                          
       504 +    }                                                                                                                
       505 +                                                                                                                     
       506 +    // 尝试解析为 commit hash                                                                                        
       507 +    if let Ok(repo) = Repository::discover(".") {                                                                    
       508 +        if let Ok(_) = repo.revparse_single(input) {                                                                 
       509 +            return Ok(InputType::CommitDiff {                                                                        
       510 +                commit: input.to_string(),                                                                           
       511 +            });                                                                                                      
       512 +        }                                                                                                            
       513 +    }                                                                                                                
       514 +                                                                                                                     
       515 +    Err(anyhow::anyhow!("无法解析输入: {}. 请提供: commit hash, 文件路径, 或 'diff'", input))                        
       516 +}                                                                                                                    
       517 +                                                                                                                     
       518 +/// 获取工作区 diff                                                                                                  
       519 +pub fn get_working_tree_diff() -> Result<Vec<FileData>> {                                                            
       520 +    let repo = Repository::discover(".")?;                                                                           
       521 +    let head = repo.head()?;                                                                                         
       522 +    let head_tree = head.peel_to_tree()?;                                                                            
       523 +                                                                                                                     
       524 +    let mut diff_opts = git2::DiffOptions::new();                                                                    
       525 +    diff_opts.include_unmodified(false);                                                                             
       526 +    diff_opts.recurse_untracked_dirs(true);                                                                          
       527 +                                                                                                                     
       528 +    let diff = repo.diff_tree_to_workdir_with_index(Some(&head_tree), Some(&mut diff_opts))?;                        
       529 +                                                                                                                     
       530 +    diff_to_file_data(&diff, &repo)                                                                                  
       531 +}                                                                                                                    
       532 +                                                                                                                     
       533 +/// 获取 commit diff                                                                                                 
       534 +pub fn get_commit_diff(commit_hash: &str) -> Result<Vec<FileData>> {                                                 
       535 +    let repo = Repository::discover(".")?;                                                                           
       536 +    let obj = repo.revparse_single(commit_hash)?;                                                                    
       537 +    let commit = obj.peel_to_commit()?;                                                                              
       538 +                                                                                                                     
       539 +    let parent = commit.parent(0)?;                                                                                  
       540 +    let parent_tree = parent.tree()?;                                                                                
       541 +    let commit_tree = commit.tree()?;                                                                                
       542 +                                                                                                                     
       543 +    let diff = repo.diff_tree_to_tree(                                                                               
       544 +        Some(&parent_tree),                                                                                          
       545 +        Some(&commit_tree),                                                                                          
       546 +        None,                                                                                                        
       547 +    )?;                                                                                                              
       548 +                                                                                                                     
       549 +    diff_to_file_data(&diff, &repo)                                                                                  
       550 +}                                                                                                                    
       551 +                                                                                                                     
       552 +/// 获取文件内容                                                                                                     
       553 +pub fn get_file_content(path: &str) -> Result<Vec<FileData>> {                                                       
       554 +    let content = fs::read_to_string(path)?;                                                                         
       555 +    let lines: Vec<LineData> = content                                                                               
       556 +        .lines()                                                                                                     
       557 +        .enumerate()                                                                                                 
       558 +        .map(|(i, line)| LineData {                                                                                  
       559 +            number: (i + 1) as u32,                                                                                  
       560 +            content: line.to_string(),                                                                               
       561 +            type_: None,                                                                                             
       562 +        })                                                                                                           
       563 +        .collect();                                                                                                  
       564 +                                                                                                                     
       565 +    Ok(vec![FileData {                                                                                               
       566 +        path: path.to_string(),                                                                                      
       567 +        status: "view".to_string(),                                                                                  
       568 +        lines,                                                                                                       
       569 +    }])                                                                                                              
       570 +}                                                                                                                    
       571 +                                                                                                                     
       572 +/// 将 git2 Diff 转换为 FileData                                                                                     
       573 +fn diff_to_file_data(diff: &Diff, repo: &Repository) -> Result<Vec<FileData>> {                                      
       574 +    let mut files = Vec::new();                                                                                      
       575 +                                                                                                                     
       576 +    diff.foreach(                                                                                                    
       577 +        &mut |delta, _progress| {                                                                                    
       578 +            let path = delta.new_file().path().and_then(|p| p.to_str()).unwrap_or("unknown");                        
       579 +            let status = match delta.status() {                                                                      
       580 +                Delta::Added => "added",                                                                             
       581 +                Delta::Deleted => "deleted",                                                                         
       582 +                Delta::Modified => "modified",                                                                       
       583 +                Delta::Renamed => "renamed",                                                                         
       584 +                Delta::Copied => "copied",                                                                           
       585 +                _ => "modified",                                                                                     
       586 +            };                                                                                                       
       587 +                                                                                                                     
       588 +            files.push(FileData {                                                                                    
       589 +                path: path.to_string(),                                                                              
       590 +                status: status.to_string(),                                                                          
       591 +                lines: Vec::new(),                                                                                   
       592 +            });                                                                                                      
       593 +            true                                                                                                     
       594 +        },                                                                                                           
       595 +        None,                                                                                                        
       596 +        Some(|delta, hunk| {                                                                                         
       597 +            // 处理每个 hunk                                                                                         
       598 +            true                                                                                                     
       599 +        }),                                                                                                          
       600 +        Some(|delta, hunk, line| {                                                                                   
       601 +            // 处理每一行                                                                                            
       602 +            let path = delta.new_file().path().and_then(|p| p.to_str()).unwrap_or("unknown");                        
       603 +            if let Some(file) = files.iter_mut().find(|f| f.path == path) {                                          
       604 +                let line_num = line.new_lineno().unwrap_or(0);                                                       
       605 +                let content = std::str::from_utf8(line.content()).unwrap_or("").trim_end().to_string();              
       606 +                let line_type = match line.origin() {                                                                
       607 +                    '+' | '>' => Some("added".to_string()),                                                          
       608 +                    '-' | '<' => Some("removed".to_string()),                                                        
       609 +                    _ => None,                                                                                       
       610 +                };                                                                                                   
       611 +                                                                                                                     
       612 +                file.lines.push(LineData {                                                                           
       613 +                    number: line_num,                                                                                
       614 +                    content,                                                                                         
       615 +                    type_: line_type,                                                                                
       616 +                });                                                                                                  
       617 +            }                                                                                                        
       618 +            true                                                                                                     
       619 +        }),                                                                                                          
       620 +    )?;                                                                                                              
       621 +                                                                                                                     
       622 +    Ok(files)                                                                                                        
       623 +}                                                                                                                    
       624 +```                                                                                                                  
       625 +                                                                                                                     
       626 +#### 5.6 src/storage.rs                                                                                              
       627 +                                                                                                                     
       628 +```rust                                                                                                              
       629 +use anyhow::{Context, Result};                                                                                       
       630 +use std::fs;                                                                                                         
       631 +use std::path::PathBuf;                                                                                              
       632 +use sha2::{Sha256, Digest};                                                                                          
       633 +use chrono::Utc;                                                                                                     
       634 +                                                                                                                     
       635 +use crate::models::{ReviewData, ReviewStatus, InputType, Comment};                                                   
       636 +use crate::git_ops;                                                                                                  
       637 +                                                                                                                     
       638 +/// 审查存储管理                                                                                                     
       639 +pub struct ReviewStorage {                                                                                           
       640 +    data_dir: PathBuf,                                                                                               
       641 +    input: InputType,                                                                                                
       642 +}                                                                                                                    
       643 +                                                                                                                     
       644 +impl ReviewStorage {                                                                                                 
       645 +    /// 创建新的存储                                                                                                 
       646 +    pub fn new(input: &InputType) -> Result<Self> {                                                                  
       647 +        let base_dir = PathBuf::from(".hrev");                                                                       
       648 +        fs::create_dir_all(&base_dir)?;                                                                              
       649 +                                                                                                                     
       650 +        // 计算输入的 hash 作为目录名                                                                                
       651 +        let input_str = match input {                                                                                
       652 +            InputType::CommitDiff { commit } => format!("commit_{}", commit),                                        
       653 +            InputType::FileContent { path } => format!("file_{}", path),                                             
       654 +            InputType::WorkingTreeDiff => "diff".to_string(),                                                        
       655 +        };                                                                                                           
       656 +                                                                                                                     
       657 +        let mut hasher = Sha256::new();                                                                              
       658 +        hasher.update(input_str.as_bytes());                                                                         
       659 +        let hash = format!("{:x}", hasher.finalize());                                                               
       660 +        let data_dir = base_dir.join(&hash[..16]);                                                                   
       661 +                                                                                                                     
       662 +        fs::create_dir_all(&data_dir)?;                                                                              
       663 +                                                                                                                     
       664 +        Ok(Self {                                                                                                    
       665 +            data_dir,                                                                                                
       666 +            input: input.clone(),                                                                                    
       667 +        })                                                                                                           
       668 +    }                                                                                                                
       669 +                                                                                                                     
       670 +    /// 获取数据文件路径                                                                                             
       671 +    fn data_file(&self) -> PathBuf {                                                                                 
       672 +        self.data_dir.join("data.json")                                                                              
       673 +    }                                                                                                                
       674 +                                                                                                                     
       675 +    /// 加载或创建数据                                                                                               
       676 +    pub fn load_or_create(&self) -> Result<ReviewData> {                                                             
       677 +        let data_file = self.data_file();                                                                            
       678 +                                                                                                                     
       679 +        if data_file.exists() {                                                                                      
       680 +            self.load()                                                                                              
       681 +        } else {                                                                                                     
       682 +            self.create()                                                                                            
       683 +        }                                                                                                            
       684 +    }                                                                                                                
       685 +                                                                                                                     
       686 +    /// 加载数据                                                                                                     
       687 +    pub fn load(&self) -> Result<ReviewData> {                                                                       
       688 +        let data_file = self.data_file();                                                                            
       689 +        let content = fs::read_to_string(&data_file)                                                                 
       690 +            .context("Failed to read review data")?;                                                                 
       691 +        let data: ReviewData = serde_json::from_str(&content)                                                        
       692 +            .context("Failed to parse review data")?;                                                                
       693 +        Ok(data)                                                                                                     
       694 +    }                                                                                                                
       695 +                                                                                                                     
       696 +    /// 保存数据                                                                                                     
       697 +    pub fn save(&self, data: &ReviewData) -> Result<()> {                                                            
       698 +        let data_file = self.data_file();                                                                            
       699 +        let content = serde_json::to_string_pretty(data)                                                             
       700 +            .context("Failed to serialize review data")?;                                                            
       701 +        fs::write(&data_file, content)                                                                               
       702 +            .context("Failed to write review data")?;                                                                
       703 +        Ok(())                                                                                                       
       704 +    }                                                                                                                
       705 +                                                                                                                     
       706 +    /// 创建新数据                                                                                                   
       707 +    fn create(&self) -> Result<ReviewData> {                                                                         
       708 +        let input_str = match &self.input {                                                                          
       709 +            InputType::CommitDiff { commit } => format!("Commit: {}", commit),                                       
       710 +            InputType::FileContent { path } => format!("File: {}", path),                                            
       711 +            InputType::WorkingTreeDiff => "Working Tree Diff".to_string(),                                           
       712 +        };                                                                                                           
       713 +                                                                                                                     
       714 +        Ok(ReviewData {                                                                                              
       715 +            input_type: self.input.clone(),                                                                          
       716 +            input: input_str,                                                                                        
       717 +            comments: Vec::new(),                                                                                    
       718 +            created_at: Utc::now(),                                                                                  
       719 +            status: ReviewStatus::InProgress,                                                                        
       720 +        })                                                                                                           
       721 +    }                                                                                                                
       722 +}                                                                                                                    
       723 +```                                                                                                                  
       724 +                                                                                                                     
       725 +#### 5.7 src/server.rs                                                                                               
       726 +                                                                                                                     
       727 +```rust                                                                                                              
       728 +use anyhow::Result;                                                                                                  
       729 +use axum::{                                                                                                          
       730 +    extract::{                                                                                                       
       731 +        State,                                                                                                       
       732 +        Path,                                                                                                        
       733 +    },                                                                                                               
       734 +    http::StatusCode,                                                                                                
       735 +    response::{IntoResponse, Response},                                                                              
       736 +    routing::{get, post, put, delete},                                                                               
       737 +    Router,                                                                                                          
       738 +    Json,                                                                                                            
       739 +};                                                                                                                   
       740 +use askama::Template;                                                                                                
       741 +use std::sync::Arc;                                                                                                  
       742 +use tokio::sync::RwLock;                                                                                             
       743 +use tower_http::cors::CorsLayer;                                                                                     
       744 +use tracing::info;                                                                                                   
       745 +                                                                                                                     
       746 +use crate::models::{ReviewData, AddCommentRequest, Comment, DataResponse, FileData};                                 
       747 +use crate::storage::ReviewStorage;                                                                                   
       748 +use crate::routes::*;                                                                                                
       749 +use crate::git_ops;                                                                                                  
       750 +                                                                                                                     
       751 +/// 服务器状态                                                                                                       
       752 +#[derive(Clone)]                                                                                                     
       753 +pub struct AppState {                                                                                                
       754 +    pub data: Arc<RwLock<ReviewData>>,                                                                               
       755 +    pub storage: ReviewStorage,                                                                                      
       756 +}                                                                                                                    
       757 +                                                                                                                     
       758 +/// 全局完成信号                                                                                                     
       759 +static COMPLETION_SIGNAL: tokio::sync::Notify = tokio::sync::Notify::const_notify();                                 
       760 +                                                                                                                     
       761 +/// 运行服务器                                                                                                       
       762 +pub async fn run(port: u16, data: ReviewData) -> Result<u16> {                                                       
       763 +    let storage = ReviewStorage::new(&data.input_type)?;                                                             
       764 +    storage.save(&data)?;                                                                                            
       765 +                                                                                                                     
       766 +    let state = AppState {                                                                                           
       767 +        data: Arc::new(RwLock::new(data)),                                                                           
       768 +        storage,                                                                                                     
       769 +    };                                                                                                               
       770 +                                                                                                                     
       771 +    let app = Router::new()                                                                                          
       772 +        .route("/", get(index_handler))                                                                              
       773 +        .route("/api/data", get(get_data_handler))                                                                   
       774 +        .route("/api/comments", post(add_comment_handler))                                                           
       775 +        .route("/api/comments/:id", put(update_comment_handler))                                                     
       776 +        .route("/api/comments/:id", delete(delete_comment_handler))                                                  
       777 +        .route("/api/complete", post(complete_handler))                                                              
       778 +        .layer(CorsLayer::permissive())                                                                              
       779 +        .with_state(state);                                                                                          
       780 +                                                                                                                     
       781 +    // 绑定端口                                                                                                      
       782 +    let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)).await?;                                
       783 +    let actual_port = listener.local_addr()?.port();                                                                 
       784 +    info!("Server running on port {}", actual_port);                                                                 
       785 +                                                                                                                     
       786 +    // 启动服务器                                                                                                    
       787 +    tokio::spawn(async move {                                                                                        
       788 +        axum::serve(listener, app).await.unwrap();                                                                   
       789 +    });                                                                                                              
       790 +                                                                                                                     
       791 +    Ok(actual_port)                                                                                                  
       792 +}                                                                                                                    
       793 +                                                                                                                     
       794 +/// 等待完成信号                                                                                                     
       795 +pub async fn wait_for_completion() -> Result<()> {                                                                   
       796 +    COMPLETION_SIGNAL.notified().await;                                                                              
       797 +    Ok(())                                                                                                           
       798 +}                                                                                                                    
       799 +                                                                                                                     
       800 +/// 主页模板                                                                                                         
       801 +#[derive(Template)]                                                                                                  
       802 +#[template(path = "review.html")]                                                                                    
       803 +struct IndexTemplate {}                                                                                              
       804 +                                                                                                                     
       805 +/// 主页 handler                                                                                                     
       806 +async fn index_handler() -> impl IntoResponse {                                                                      
       807 +    IndexTemplate {}                                                                                                 
       808 +}                                                                                                                    
       809 +```                                                                                                                  
       810 +                                                                                                                     
       811 +#### 5.8 src/routes.rs                                                                                               
       812 +                                                                                                                     
       813 +```rust                                                                                                              
       814 +use axum::{                                                                                                          
       815 +    extract::{                                                                                                       
       816 +        State,                                                                                                       
       817 +        Path,                                                                                                        
       818 +    },                                                                                                               
       819 +    http::StatusCode,                                                                                                
       820 +    response::{IntoResponse, Response},                                                                              
       821 +    Json,                                                                                                            
       822 +};                                                                                                                   
       823 +use tracing::info;                                                                                                   
       824 +                                                                                                                     
       825 +use crate::models::*;                                                                                                
       826 +use crate::server::{AppState, COMPLETION_SIGNAL};                                                                    
       827 +use crate::git_ops;                                                                                                  
       828 +                                                                                                                     
       829 +/// 获取初始数据                                                                                                     
       830 +pub async fn get_data_handler(State(state): State<AppState>) -> Result<Json<DataResponse>, AppError> {               
       831 +    let data = state.data.read().await;                                                                              
       832 +                                                                                                                     
       833 +    // 根据 input_type 获取文件内容                                                                                  
       834 +    let files = match &data.input_type {                                                                             
       835 +        InputType::CommitDiff { commit } => {                                                                        
       836 +            git_ops::get_commit_diff(commit)?                                                                        
       837 +        }                                                                                                            
       838 +        InputType::FileContent { path } => {                                                                         
       839 +            git_ops::get_file_content(path)?                                                                         
       840 +        }                                                                                                            
       841 +        InputType::WorkingTreeDiff => {                                                                              
       842 +            git_ops::get_working_tree_diff()?                                                                        
       843 +        }                                                                                                            
       844 +    };                                                                                                               
       845 +                                                                                                                     
       846 +    let response = DataResponse {                                                                                    
       847 +        type_: match data.input_type {                                                                               
       848 +            InputType::CommitDiff { .. } => "commit_diff".to_string(),                                               
       849 +            InputType::FileContent { .. } => "file_content".to_string(),                                             
       850 +            InputType::WorkingTreeDiff => "working_tree_diff".to_string(),                                           
       851 +        },                                                                                                           
       852 +        title: data.input.clone(),                                                                                   
       853 +        files,                                                                                                       
       854 +        comments: data.comments.clone(),                                                                             
       855 +    };                                                                                                               
       856 +                                                                                                                     
       857 +    Ok(Json(response))                                                                                               
       858 +}                                                                                                                    
       859 +                                                                                                                     
       860 +/// 添加评论                                                                                                         
       861 +pub async fn add_comment_handler(                                                                                    
       862 +    State(state): State<AppState>,                                                                                   
       863 +    Json(req): Json<AddCommentRequest>,                                                                              
       864 +) -> Result<Json<Comment>, AppError> {                                                                               
       865 +    let mut data = state.data.write().await;                                                                         
       866 +                                                                                                                     
       867 +    let comment = Comment::new(req.file, req.line, req.text);                                                        
       868 +    data.comments.push(comment.clone());                                                                             
       869 +                                                                                                                     
       870 +    state.storage.save(&data)?;                                                                                      
       871 +    info!("Added comment: {}", comment.id);                                                                          
       872 +                                                                                                                     
       873 +    Ok(Json(comment))                                                                                                
       874 +}                                                                                                                    
       875 +                                                                                                                     
       876 +/// 更新评论                                                                                                         
       877 +pub async fn update_comment_handler(                                                                                 
       878 +    State(state): State<AppState>,                                                                                   
       879 +    Path(id): Path<String>,                                                                                          
       880 +    Json(mut comment): Json<Comment>,                                                                                
       881 +) -> Result<Json<Comment>, AppError> {                                                                               
       882 +    let mut data = state.data.write().await;                                                                         
       883 +                                                                                                                     
       884 +    if let Some(existing) = data.comments.iter_mut().find(|c| c.id == id) {                                          
       885 +        comment.id = id; // 确保 ID 不变                                                                             
       886 +        *existing = comment.clone();                                                                                 
       887 +        state.storage.save(&data)?;                                                                                  
       888 +        Ok(Json(comment))                                                                                            
       889 +    } else {                                                                                                         
       890 +        Err(AppError::CommentNotFound(id))                                                                           
       891 +    }                                                                                                                
       892 +}                                                                                                                    
       893 +                                                                                                                     
       894 +/// 删除评论                                                                                                         
       895 +pub async fn delete_comment_handler(                                                                                 
       896 +    State(state): State<AppState>,                                                                                   
       897 +    Path(id): Path<String>,                                                                                          
       898 +) -> Result<StatusCode, AppError> {                                                                                  
       899 +    let mut data = state.data.write().await;                                                                         
       900 +                                                                                                                     
       901 +    if let Some(pos) = data.comments.iter().position(|c| c.id == id) {                                               
       902 +        data.comments.remove(pos);                                                                                   
       903 +        state.storage.save(&data)?;                                                                                  
       904 +        info!("Deleted comment: {}", id);                                                                            
       905 +        Ok(StatusCode::NO_CONTENT)                                                                                   
       906 +    } else {                                                                                                         
       907 +        Err(AppError::CommentNotFound(id))                                                                           
       908 +    }                                                                                                                
       909 +}                                                                                                                    
       910 +                                                                                                                     
       911 +/// 完成审查                                                                                                         
       912 +pub async fn complete_handler(State(state): State<AppState>) -> Result<Json<CompletionResponse>, AppError> {         
       913 +    let mut data = state.data.write().await;                                                                         
       914 +    data.status = ReviewStatus::Completed;                                                                           
       915 +    state.storage.save(&data)?;                                                                                      
       916 +                                                                                                                     
       917 +    // 发送完成信号                                                                                                  
       918 +    COMPLETION_SIGNAL.notify_one();                                                                                  
       919 +                                                                                                                     
       920 +    Ok(Json(CompletionResponse {                                                                                     
       921 +        message: "Review completed".to_string(),                                                                     
       922 +        comment_count: data.comments.len(),                                                                          
       923 +    }))                                                                                                              
       924 +}                                                                                                                    
       925 +                                                                                                                     
       926 +/// 完成响应                                                                                                         
       927 +#[derive(serde::Serialize)]                                                                                          
       928 +struct CompletionResponse {                                                                                          
       929 +    message: String,                                                                                                 
       930 +    comment_count: usize,                                                                                            
       931 +}                                                                                                                    
       932 +                                                                                                                     
       933 +/// 应用错误                                                                                                         
       934 +#[derive(Debug)]                                                                                                     
       935 +pub enum AppError {                                                                                                  
       936 +    CommentNotFound(String),                                                                                         
       937 +    Internal(anyhow::Error),                                                                                         
       938 +}                                                                                                                    
       939 +                                                                                                                     
       940 +impl IntoResponse for AppError {                                                                                     
       941 +    fn into_response(self) -> Response {                                                                             
       942 +        let (status, message) = match self {                                                                         
       943 +            AppError::CommentNotFound(id) => (StatusCode::NOT_FOUND, format!("Comment not found: {}", id)),          
       944 +            AppError::Internal(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),                             
       945 +        };                                                                                                           
       946 +        (status, message).into_response()                                                                            
       947 +    }                                                                                                                
       948 +}                                                                                                                    
       949 +                                                                                                                     
       950 +impl From<anyhow::Error> for AppError {                                                                              
       951 +    fn from(e: anyhow::Error) -> Self {                                                                              
       952 +        AppError::Internal(e)                                                                                        
       953 +    }                                                                                                                
       954 +}                                                                                                                    
       955 +```                                                                                                                  
       956 +                                                                                                                     
       957 +#### 5.9 src/output.rs                                                                                               
       958 +                                                                                                                     
       959 +```rust                                                                                                              
       960 +use colored::Colorize;                                                                                               
       961 +use crate::models::{ReviewData, Comment};                                                                            
       962 +                                                                                                                     
       963 +/// 打印评论摘要（Markdown 格式）                                                                                    
       964 +pub fn print_summary(data: &ReviewData) {                                                                            
       965 +    println!();                                                                                                      
       966 +    println!("{}", "═".repeat(60));                                                                                  
       967 +    println!("{}", "📋 Review Summary".bold().cyan());                                                               
       968 +    println!("{}", "═".repeat(60));                                                                                  
       969 +    println!();                                                                                                      
       970 +                                                                                                                     
       971 +    println!("{}: {}", "Input".bold(), data.input);                                                                  
       972 +    println!("{}: {}", "Created".bold(), data.created_at.format("%Y-%m-%d %H:%M:%S"));                               
       973 +    println!("{}: {}", "Comments".bold(), data.comments.len());                                                      
       974 +    println!();                                                                                                      
       975 +                                                                                                                     
       976 +    if data.comments.is_empty() {                                                                                    
       977 +        println!("{}", "No comments added.".dimmed());                                                               
       978 +        return;                                                                                                      
       979 +    }                                                                                                                
       980 +                                                                                                                     
       981 +    // 按文件分组                                                                                                    
       982 +    let mut by_file: std::collections::HashMap<Option<String>, Vec<&Comment>> = std::collections::HashMap::new       
           +();                                                                                                                  
       983 +    for comment in &data.comments {                                                                                  
       984 +        by_file.entry(comment.file.clone()).or_default().push(comment);                                              
       985 +    }                                                                                                                
       986 +                                                                                                                     
       987 +    // 打印评论（Markdown 格式）                                                                                     
       988 +    for (file, comments) in by_file.iter() {                                                                         
       989 +        if let Some(f) = file {                                                                                      
       990 +            println!("\n## 📄 {}", f.bold());                                                                        
       991 +        } else {                                                                                                     
       992 +            println!("\n## 💬 Global Comments");                                                                     
       993 +        }                                                                                                            
       994 +                                                                                                                     
       995 +        for comment in comments {                                                                                    
       996 +            println!();                                                                                              
       997 +            if comment.resolved {                                                                                    
       998 +                print!("✅ ");                                                                                       
       999 +            } else {                                                                                                 
      1000 +                print!("💬 ");                                                                                       
      1001 +            }                                                                                                        
      1002 +                                                                                                                     
      1003 +            if let Some(line) = comment.line {                                                                       
      1004 +                print!("Line {}: ", line.to_string().yellow());                                                      
      1005 +            }                                                                                                        
      1006 +                                                                                                                     
      1007 +            println!("{}", comment.text);                                                                            
      1008 +            println!("    {} {}", "─".dimmed(), comment.created_at.format("%H:%M").to_string().dimmed());            
      1009 +                                                                                                                     
      1010 +            // 统计                                                                                                  
      1011 +            let unresolved = data.comments.iter().filter(|c| !c.resolved).count();                                   
      1012 +            let resolved = data.comments.iter().filter(|c| c.resolved).count();                                      
      1013 +                                                                                                                     
      1014 +            println!();                                                                                              
      1015 +            println!("{}", "─".repeat(60).dimmed());                                                                 
      1016 +            println!("{} {} unresolved, {} resolved",                                                                
      1017 +                "Summary:".bold(),                                                                                   
      1018 +                unresolved.to_string().yellow(),                                                                     
      1019 +                resolved.to_string().green()                                                                         
      1020 +            );                                                                                                       
      1021 +        }                                                                                                            
      1022 +    }                                                                                                                
      1023 +}                                                                                                                    
      1024 +```                                                                                                                  
      1025 +                                                                                                                     
      1026 +```rust                                                                                                              
      1027  // git_ops.rs                                                                                                        
      1028  use git2::{Repository, Diff, Object};                                                                                
      1029                                                                                                                       
  ⎿  Running PostToolUse hook…                                                                                                   
                                                                                                                                 
⏺ 现在让我更新 Claude Code Skill 设计为自动处理模式：                                                                            
                                                                                                                                 
⏺ Updated plan                                                                                                                   
  ⎿  /plan to preview                                                                                                            
      1073  }                                                                                                                    
      1074  ```                                                                                                                  
      1075                                                                                                                       
      1076 -### 6. Claude Code Skill 设计                                                                                        
      1076 +### 6. Claude Code Skill 设计（自动处理模式）                                                                        
      1077                                                                                                                       
      1078 -**文件位置:** `skills/review-changes.md`                                                                             
      1078 +**文件位置:** `.claude/skills/human-review.md`                                                                       
      1079                                                                                                                       
      1080  ```markdown                                                                                                          
      1081  ---                                                                                                                  
      1082 -description: 启动人工审查流程，让用户在浏览器中审查变更并提供反馈                                                    
      1082 +description: 启动人工审查流程，自动读取评论并修改代码                                                                
      1083  ---                                                                                                                  
      1084                                                                                                                       
      1085 -请按以下步骤启动人工审查：                                                                                           
      1085 +## Human Review Skill                                                                                                
      1086                                                                                                                       
      1087 -1. **检测变更类型**：                                                                                                
      1088 -   - 如果是 git 仓库且有未提交变更 → 使用 `hrev diff`                                                                
      1089 -   - 如果用户提供了特定文件路径 → 使用 `hrev <file>`                                                                 
      1090 -   - 如果用户提供了 commit hash → 使用 `hrev <commit>`                                                               
      1087 +自动启动人工审查，等待用户完成评论，然后自动根据反馈修改代码。                                                       
      1088                                                                                                                       
      1092 -2. **启动审查**：                                                                                                    
      1093 -   - 运行 `hrev <input>` 命令                                                                                        
      1094 -   - 工具会自动打开浏览器                                                                                            
      1089 +## 工作流程                                                                                                          
      1090                                                                                                                       
      1096 -3. **等待用户完成**：                                                                                                
      1097 -   - 等待用户在浏览器中完成评论                                                                                      
      1098 -   - 用户点击"完成"后，工具会输出评论摘要                                                                            
      1091 +### 1. 检测变更并启动 hrev                                                                                           
      1092                                                                                                                       
      1100 -4. **处理反馈**：                                                                                                    
      1101 -   - 读取工具输出的评论内容                                                                                          
      1102 -   - 根据评论调整代码或方案                                                                                          
      1103 -   - 如有必要，可以再次启动审查                                                                                      
      1093 +首先判断当前状态：                                                                                                   
      1094 +- 有未提交的 git 变更 → `hrev diff`                                                                                  
      1095 +- 用户指定了文件 → `hrev <file>`                                                                                     
      1096 +- 用户指定了 commit → `hrev <commit>`                                                                                
      1097                                                                                                                       
      1105 -**重要提示**：                                                                                                       
      1106 -- 不要在用户审查期间进行其他操作                                                                                     
      1107 -- 如果用户提出了问题或建议，必须认真对待                                                                             
      1108 -- 审查完成后，总结所有收到的反馈                                                                                     
      1098 +### 2. 等待审查完成                                                                                                  
      1099 +                                                                                                                     
      1100 +- 运行 hrev 命令                                                                                                     
      1101 +- 工具会自动打开浏览器                                                                                               
      1102 +- **等待**用户在浏览器中完成评论                                                                                     
      1103 +- 用户点击"完成"后，hrev 会输出结果并退出                                                                            
      1104 +                                                                                                                     
      1105 +### 3. 解析审查结果                                                                                                  
      1106 +                                                                                                                     
      1107 +hrev 输出包含：                                                                                                      
      1108 +- 终端摘要（评论数量、涉及文件）                                                                                     
      1109 +- Markdown 文件（详细评论 + 上下文）                                                                                 
      1110 +- JSON 文件（结构化数据）                                                                                            
      1111 +                                                                                                                     
      1112 +从输出中提取：                                                                                                       
      1113 +```bash                                                                                                              
      1114 +# 输出示例                                                                                                           
      1115 +Review Results: diff | 5 comment(s)                                                                                  
      1116 +                                                                                                                     
      1117 +📄 src/main.rs                                                                                                       
      1118 +  [L42] Consider using debug logging instead                                                                         
      1119 +  [L55] This function should return a Result                                                                         
      1120 +                                                                                                                     
      1121 +📄 src/parser.rs                                                                                                     
      1122 +  [L12] Variable name unclear                                                                                        
      1123 +  [L30] Missing error handling                                                                                       
      1124 +  [L45] Great implementation!                                                                                        
      1125 +                                                                                                                     
      1126 +💾 Full results saved to:                                                                                            
      1127 +   .hrev/sessions/abc123/review.json                                                                                 
      1128 +   .hrev/sessions/abc123/review.md                                                                                   
      1129  ```                                                                                                                  
      1130                                                                                                                       
      1131 +### 4. 自动处理评论（关键）                                                                                          
      1132 +                                                                                                                     
      1133 +**读取 `.hrev/sessions/<id>/review.md`**，解析每条评论：                                                             
      1134 +                                                                                                                     
      1135 +对于每条评论：                                                                                                       
      1136 +```markdown                                                                                                          
      1137 +#### Line 42: Consider using debug logging instead                                                                   
      1138 +```context                                                                                                           
      1139 +  40 |     fn process(&self) {                                                                                       
      1140 +  41 |         let data = self.load();                                                                               
      1141 +  42 | >>> Consider using debug logging instead                                                                      
      1142 +  43 |         println!("{:?}", data);                                                                               
      1143 +  44 |     }                                                                                                         
      1144 +```                                                                                                                  
      1145 +```                                                                                                                  
      1146 +                                                                                                                     
      1147 +**自动处理策略：**                                                                                                   
      1148 +1. 理解评论内容                                                                                                      
      1149 +2. 定位到对应文件和行号                                                                                              
      1150 +3. 根据评论意图修改代码                                                                                              
      1151 +4. 修改后报告变更                                                                                                    
      1152 +                                                                                                                     
      1153 +### 5. 汇报处理结果                                                                                                  
      1154 +                                                                                                                     
      1155 +完成所有修改后，汇报：                                                                                               
      1156 +```                                                                                                                  
      1157 +✅ 已处理 5 条评论：                                                                                                 
      1158 +                                                                                                                     
      1159 +1. src/main.rs:42 - Consider using debug logging instead                                                             
      1160 +   → 改用 log::debug!() 替代 println!()                                                                              
      1161 +                                                                                                                     
      1162 +2. src/main.rs:55 - This function should return a Result                                                             
      1163 +   → 添加了 Result<(), Error> 返回类型                                                                               
      1164 +                                                                                                                     
      1165 +3. src/parser.rs:12 - Variable name unclear                                                                          
      1166 +   → 重命名为 input_buffer                                                                                           
      1167 +                                                                                                                     
      1168 +4. src/parser.rs:30 - Missing error handling                                                                         
      1169 +   → 添加了 ? 错误传播                                                                                               
      1170 +                                                                                                                     
      1171 +5. src/parser.rs:45 - Great implementation!                                                                          
      1172 +   → （无需修改）                                                                                                    
      1173 +                                                                                                                     
      1174 +是否需要再次 review？                                                                                                
      1175 +```                                                                                                                  
      1176 +                                                                                                                     
      1177 +## 重要规则                                                                                                          
      1178 +                                                                                                                     
      1179 +- **必须等待** hrev 完成后再继续                                                                                     
      1180 +- **必须读取** review.md 文件获取完整上下文                                                                          
      1181 +- **必须处理** 每条评论（即使是"同意"类评论也要确认）                                                                
      1182 +- **修改前** 确认理解评论意图                                                                                        
      1183 +- **修改后** 汇报每条评论的处理方式                                                                                  
      1184 +```                                                                                                                  
      1185 +                                                                                                                     
      1186 +**文件位置:** `.claude/skills/human-review.json`                                                                     
      1187 +                                                                                                                     
      1188 +```json                                                                                                              
      1189 +{                                                                                                                    
      1190 +  "name": "human-review",                                                                                            
      1191 +  "description": "Launch human review and auto-apply feedback",                                                      
      1192 +  "parameters": {                                                                                                    
      1193 +    "target": {                                                                                                      
      1194 +      "type": "string",                                                                                              
      1195 +      "description": "Commit hash, file path, or 'diff'",                                                            
      1196 +      "default": "diff"                                                                                              
      1197 +    }                                                                                                                
      1198 +  }                                                                                                                  
      1199 +}                                                                                                                    
      1200 +```                                                                                                                  
      1201 +                                                                                                                     
      1202 +### Skill 调用示例                                                                                                   
      1203 +                                                                                                                     
      1204 +用户说：                                                                                                             
      1205 +- "Review my changes" → skill 检测变更，调用 `hrev diff`                                                             
      1206 +- "Review this plan" → skill 调用 `hrev plan.md`                                                                     
      1207 +- "Review commit abc123" → skill 调用 `hrev abc123`                                                                  
      1208 +                                                                                                                     
      1209  ### 7. 完整项目结构                                                                                                  
      1210                                                                                                                       
      1211  ```                                                                                                                  
  ⎿  Running PostToolUse hook…                                                                                                   
                                                                                                                                 
⏺ 我注意到计划中有一些重复的内容需要清理，并且还需要补充输出格式和 skill 设计。让我继续完善：                                    
                                                                                                                                 
⏺ Reading Plan(~/.claude/plans/zippy-plotting-ladybug.md)                                                                        
  ⎿  Read 680 lines                                                                                                              
  ⎿  Running PostToolUse hooks… (0/2 done)                                                                                       
                                                                                                                                 
⏺ 计划已更新完成！主要调整：                                                                                                     
                                                                                                                                 
  1. 输出格式 - Markdown 添加 <!-- HREV_AI_START --> 标记，方便 AI 解析结构化评论                                                
  2. 评论结构 - 简化，移除复杂分类，保持纯文本评论                                                                               
  3. Skill 自动处理 - AI 读取 review.md 后自动根据每条评论修改代码并汇报结果                                                     
                                                                                                                                 
  现在可以开始实现了吗？                                                                                                         
  ⎿  User rejected Claude's plan:                                                                                                
     ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮       
     │ hrev CLI 工具实现计划                                                                                             │       
     │                                                                                                                   │       
     │ 需求概述                                                                                                          │       
     │                                                                                                                   │       
     │ 创建一个 CLI 工具 hrev，用于人工代码审查。                                                                        │       
     │                                                                                                                   │       
     │ 三种用法                                                                                                          │       
     │                                                                                                                   │       
     │ 1. hrev <commit-hash> - 显示某个 commit 的 diff                                                                   │       
     │ 2. hrev <file.md> - 显示文档内容（如 plan.md）                                                                    │       
     │ 3. hrev diff - 显示当前 git diff                                                                                  │       
     │                                                                                                                   │       
     │ 功能流程                                                                                                          │       
     │                                                                                                                   │       
     │ 1. 启动后打开浏览器页面                                                                                           │       
     │ 2. 显示对应内容（diff/文档），支持行号                                                                            │       
     │ 3. 用户在 HTML 页面上添加评论（支持行级评论）                                                                     │       
     │ 4. 评论完成后提交，作为工具输出                                                                                   │       
     │                                                                                                                   │       
     │ 额外需求                                                                                                          │       
     │                                                                                                                   │       
     │ - 提供 Claude Code skill/command，引导 AI 在修改后让人 review                                                     │       
     │ - 在 git commit 前、PR 前进行人工 review                                                                          │       
     │                                                                                                                   │       
     │ 技术选型                                                                                                          │       
     │                                                                                                                   │       
     │ - 语言: Rust（性能、单一二进制、无依赖）                                                                          │       
     │ - Web框架: axum（异步、基于 tokio）                                                                               │       
     │ - 模板: askama（编译时模板检查）                                                                                  │       
     │ - CLI: clap（derive API）                                                                                         │       
     │ - Git: git2（Rust git 绑定）                                                                                      │       
     │                                                                                                                   │       
     │ 项目结构                                                                                                          │       
     │                                                                                                                   │       
     │ hrev/                                                                                                             │       
     │ ├── Cargo.toml                                                                                                    │       
     │ ├── Cargo.lock                                                                                                    │       
     │ ├── src/                                                                                                          │       
     │ │   ├── main.rs           # CLI 入口                                                                              │       
     │ │   ├── cli.rs            # 命令行参数解析                                                                        │       
     │ │   ├── server.rs         # Axum 服务器                                                                           │       
     │ │   ├── git_ops.rs        # Git 操作封装                                                                          │       
     │ │   ├── models.rs         # 数据模型                                                                              │       
     │ │   └── templates/                                                                                                │       
     │ │       └── review.html   # 前端页面（askama）                                                                    │       
     │ ├── skills/                                                                                                       │       
     │ │   └── review-changes.md # Claude Code skill 定义                                                                │       
     │ └── README.md                                                                                                     │       
     │                                                                                                                   │       
     │ 详细设计                                                                                                          │       
     │                                                                                                                   │       
     │ 1. CLI 参数设计                                                                                                   │       
     │                                                                                                                   │       
     │ // cli.rs                                                                                                         │       
     │ #[derive(Parser, Debug)]                                                                                          │       
     │ #[command(name = "hrev")]                                                                                         │       
     │ #[command(about = "Human review CLI tool", long_about = None)]                                                    │       
     │ struct Args {                                                                                                     │       
     │     /// Input: commit hash, file path, or "diff"                                                                  │       
     │     #[arg(value_name = "INPUT")]                                                                                  │       
     │     input: String,                                                                                                │       
     │                                                                                                                   │       
     │     /// Port for web server (default: random available port)                                                      │       
     │     #[arg(short, long, default_value = "0")]                                                                      │       
     │     port: u16,                                                                                                    │       
     │                                                                                                                   │       
     │     /// Don't open browser automatically                                                                          │       
     │     #[arg(long, default_value = "false")]                                                                         │       
     │     no_browser: bool,                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 输入判断逻辑：                                                                                                    │       
     │ - 如果 input 是 "diff" → 显示当前 git diff                                                                        │       
     │ - 如果 input 是存在的文件路径 → 显示文件内容                                                                      │       
     │ - 如果 input 是 40 字符 hex string → 解析为 commit hash                                                           │       
     │ - 如果 input 是短 hash（7+ 字符） → 尝试解析为 commit                                                             │       
     │                                                                                                                   │       
     │ 2. 数据模型                                                                                                       │       
     │                                                                                                                   │       
     │ // models.rs                                                                                                      │       
     │ use serde::{Deserialize, Serialize};                                                                              │       
     │                                                                                                                   │       
     │ /// 评论数据存储格式                                                                                              │       
     │ #[derive(Debug, Clone, Serialize, Deserialize)]                                                                   │       
     │ struct ReviewData {                                                                                               │       
     │     /// 输入类型                                                                                                  │       
     │     input_type: InputType,                                                                                        │       
     │     /// 原始输入                                                                                                  │       
     │     input: String,                                                                                                │       
     │     /// 评论列表                                                                                                  │       
     │     comments: Vec<Comment>,                                                                                       │       
     │     /// 创建时间                                                                                                  │       
     │     created_at: DateTime<Utc>,                                                                                    │       
     │     /// 状态                                                                                                      │       
     │     status: ReviewStatus,                                                                                         │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ #[derive(Debug, Clone, Serialize, Deserialize)]                                                                   │       
     │ #[serde(tag = "type")]                                                                                            │       
     │ enum InputType {                                                                                                  │       
     │     CommitDiff { commit: String },                                                                                │       
     │     FileContent { path: String },                                                                                 │       
     │     WorkingTreeDiff,                                                                                              │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ #[derive(Debug, Clone, Serialize, Deserialize)]                                                                   │       
     │ struct Comment {                                                                                                  │       
     │     /// 唯一 ID                                                                                                   │       
     │     id: String,                                                                                                   │       
     │     /// 文件路径（diff 模式下）                                                                                   │       
     │     file: Option<String>,                                                                                         │       
     │     /// 行号                                                                                                      │       
     │     line: Option<u32>,                                                                                            │       
     │     /// 评论内容                                                                                                  │       
     │     text: String,                                                                                                 │       
     │     /// 创建时间                                                                                                  │       
     │     created_at: DateTime<Utc>,                                                                                    │       
     │     /// 已解决标记                                                                                                │       
     │     resolved: bool,                                                                                               │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ #[derive(Debug, Clone, Serialize, Deserialize)]                                                                   │       
     │ enum ReviewStatus {                                                                                               │       
     │     InProgress,                                                                                                   │       
     │     Completed,                                                                                                    │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 评论存储：                                                                                                        │       
     │ - 文件位置: .hrev/<input-hash>/comments.json                                                                      │       
     │ - 输入 hash 的计算: sha256(input) 作为目录名                                                                      │       
     │                                                                                                                   │       
     │ 3. API 设计                                                                                                       │       
     │                                                                                                                   │       
     │ // server.rs                                                                                                      │       
     │ // RESTful API 路由                                                                                               │       
     │                                                                                                                   │       
     │ // GET /api/data - 获取初始数据（diff/文件内容）                                                                  │       
     │ // POST /api/comments - 添加评论                                                                                  │       
     │ // PUT /api/comments/:id - 更新评论                                                                               │       
     │ // DELETE /api/comments/:id - 删除评论                                                                            │       
     │ // POST /api/complete - 完成审查，输出结果并关闭服务器                                                            │       
     │                                                                                                                   │       
     │ // WebSocket (可选，用于实时同步)                                                                                 │       
     │ // WS /ws - 实时推送评论更新                                                                                      │       
     │                                                                                                                   │       
     │ API 响应格式：                                                                                                    │       
     │                                                                                                                   │       
     │ // GET /api/data 响应                                                                                             │       
     │ {                                                                                                                 │       
     │   "type": "commit_diff" | "file_content" | "working_tree_diff",                                                   │       
     │   "title": "Commit abc1234 or path/to/file.md",                                                                   │       
     │   "content": "<html>渲染后的内容</html>",                                                                         │       
     │   "comments": [                                                                                                   │       
     │     {"id": "1", "file": "src/main.rs", "line": 42, "text": "...", "resolved": false}                              │       
     │   ]                                                                                                               │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ // POST /api/comments 请求                                                                                        │       
     │ {                                                                                                                 │       
     │   "file": "src/main.rs",                                                                                          │       
     │   "line": 42,                                                                                                     │       
     │   "text": "这里可以改进..."                                                                                       │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 4. 前端设计                                                                                                       │       
     │                                                                                                                   │       
     │ 页面布局：                                                                                                        │       
     │ ┌─────────────────────────────────────────────────────────┐                                                       │       
     │ │  hrev - Commit: abc1234                    [完成] [导出] │                                                      │       
     │ ├─────────────────────────────────────────────────────────┤                                                       │       
     │ │                                                           │                                                     │       
     │ │  ┌─────────────────────┐  ┌──────────────────────────┐  │                                                       │       
     │ │  │   文件列表          │  │   内容区域               │  │                                                       │       
     │ │  │  ┌───────────────┐  │  │  ┌─────────────────────┐ │  │                                                       │       
     │ │  │  │ src/main.rs   │  │  │  │ 1  fn main() {      │ │  │                                                       │       
     │ │  │  │ src/lib.rs    │  │  │  │ 2      println!();  │ │  │                                                       │       
     │ │  │  │ README.md     │  │  │  │ 3  }                │ │  │                                                       │       
     │ │  │  └───────────────┘  │  │  └─────────────────────┘ │  │                                                       │       
     │ │  │                     │  │                           │  │                                                      │       
     │ │  │  [所有文件]         │  │  💬 [2] 这里可以改进...    │  │                                                     │       
     │ │  │                     │  │                           │  │                                                      │       
     │ │  └─────────────────────┘  └──────────────────────────┘  │                                                       │       
     │ │                                                           │                                                     │       
     │ │  [+ 添加行内评论]  [全局评论]                              │                                                    │       
     │ └─────────────────────────────────────────────────────────┘                                                       │       
     │                                                                                                                   │       
     │ 交互方式：                                                                                                        │       
     │ 1. 点击任意行 → 弹出评论输入框                                                                                    │       
     │ 2. 评论显示在对应行下方                                                                                           │       
     │ 3. 评论支持：编辑、删除、标记已解决                                                                               │       
     │ 4. 右侧显示所有文件列表（diff 模式）                                                                              │       
     │ 5. 点击文件名切换显示                                                                                             │       
     │                                                                                                                   │       
     │ 核心 JavaScript 功能：                                                                                            │       
     │ // app.js                                                                                                         │       
     │ class ReviewApp {                                                                                                 │       
     │   // 加载初始数据                                                                                                 │       
     │   async loadData();                                                                                               │       
     │                                                                                                                   │       
     │   // 渲染 diff/文件内容                                                                                           │       
     │   renderContent(content, type);                                                                                   │       
     │                                                                                                                   │       
     │   // 添加评论                                                                                                     │       
     │   async addComment(file, line, text);                                                                             │       
     │                                                                                                                   │       
     │   // 渲染评论                                                                                                     │       
     │   renderComment(comment);                                                                                         │       
     │                                                                                                                   │       
     │   // 点击行事件                                                                                                   │       
     │   onLineClick(file, line);                                                                                        │       
     │                                                                                                                   │       
     │   // 完成审查                                                                                                     │       
     │   async complete();                                                                                               │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 5. Rust 代码实现（详细）                                                                                          │       
     │                                                                                                                   │       
     │ 5.1 Cargo.toml                                                                                                    │       
     │                                                                                                                   │       
     │ [package]                                                                                                         │       
     │ name = "hrev"                                                                                                     │       
     │ version = "0.1.0"                                                                                                 │       
     │ edition = "2021"                                                                                                  │       
     │ description = "Human review CLI tool"                                                                             │       
     │ license = "MIT"                                                                                                   │       
     │                                                                                                                   │       
     │ [dependencies]                                                                                                    │       
     │ # CLI                                                                                                             │       
     │ clap = { version = "4.5", features = ["derive"] }                                                                 │       
     │                                                                                                                   │       
     │ # Async runtime                                                                                                   │       
     │ tokio = { version = "1.40", features = ["full"] }                                                                 │       
     │                                                                                                                   │       
     │ # Web framework                                                                                                   │       
     │ axum = { version = "0.7", features = ["ws"] }                                                                     │       
     │ tower = "0.5"                                                                                                     │       
     │ tower-http = { version = "0.5", features = ["fs", "cors"] }                                                       │       
     │                                                                                                                   │       
     │ # Templating                                                                                                      │       
     │ askama = { version = "0.12", features = ["with-axum"] }                                                           │       
     │                                                                                                                   │       
     │ # Serialization                                                                                                   │       
     │ serde = { version = "1.0", features = ["derive"] }                                                                │       
     │ serde_json = "1.0"                                                                                                │       
     │                                                                                                                   │       
     │ # Git operations                                                                                                  │       
     │ git2 = "0.19"                                                                                                     │       
     │                                                                                                                   │       
     │ # Date/time                                                                                                       │       
     │ chrono = { version = "0.4", features = ["serde"] }                                                                │       
     │                                                                                                                   │       
     │ # Hashing                                                                                                         │       
     │ sha2 = "0.10"                                                                                                     │       
     │                                                                                                                   │       
     │ # UUID                                                                                                            │       
     │ uuid = { version = "1.10", features = ["v4", "serde"] }                                                           │       
     │                                                                                                                   │       
     │ # Error handling                                                                                                  │       
     │ anyhow = "1.0"                                                                                                    │       
     │ thiserror = "1.0"                                                                                                 │       
     │                                                                                                                   │       
     │ # Logging                                                                                                         │       
     │ tracing = "0.1"                                                                                                   │       
     │ tracing-subscriber = "0.3"                                                                                        │       
     │                                                                                                                   │       
     │ # Browser open                                                                                                    │       
     │ open = "5.3"                                                                                                      │       
     │                                                                                                                   │       
     │ # Terminal colors                                                                                                 │       
     │ colored = "2.1"                                                                                                   │       
     │                                                                                                                   │       
     │ 5.2 src/main.rs                                                                                                   │       
     │                                                                                                                   │       
     │ use anyhow::Result;                                                                                               │       
     │ use clap::Parser;                                                                                                 │       
     │ use tracing::{info, warn};                                                                                        │       
     │                                                                                                                   │       
     │ mod cli;                                                                                                          │       
     │ mod server;                                                                                                       │       
     │ mod git_ops;                                                                                                      │       
     │ mod models;                                                                                                       │       
     │ mod storage;                                                                                                      │       
     │ mod routes;                                                                                                       │       
     │ mod output;                                                                                                       │       
     │                                                                                                                   │       
     │ use cli::Args;                                                                                                    │       
     │ use git_ops::parse_input;                                                                                         │       
     │ use storage::ReviewStorage;                                                                                       │       
     │                                                                                                                   │       
     │ #[tokio::main]                                                                                                    │       
     │ async fn main() -> Result<()> {                                                                                   │       
     │     // 初始化日志                                                                                                 │       
     │     tracing_subscriber::fmt::init();                                                                              │       
     │                                                                                                                   │       
     │     let args = Args::parse();                                                                                     │       
     │                                                                                                                   │       
     │     // 解析输入                                                                                                   │       
     │     let input = parse_input(&args.input)?;                                                                        │       
     │     info!("Parsed input: {:?}", input);                                                                           │       
     │                                                                                                                   │       
     │     // 创建存储                                                                                                   │       
     │     let storage = ReviewStorage::new(&input)?;                                                                    │       
     │     let data = storage.load_or_create()?;                                                                         │       
     │                                                                                                                   │       
     │     // 启动服务器                                                                                                 │       
     │     let port = server::run(args.port, data.clone()).await?;                                                       │       
     │                                                                                                                   │       
     │     // 打开浏览器                                                                                                 │       
     │     if !args.no_browser {                                                                                         │       
     │         if let Err(e) = open::that(format!("http://localhost:{}", port)) {                                        │       
     │             warn!("Failed to open browser: {}", e);                                                               │       
     │             eprintln!("Please open http://localhost:{} in your browser", port);                                   │       
     │         }                                                                                                         │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     // 等待服务器完成                                                                                             │       
     │     server::wait_for_completion().await?;                                                                         │       
     │                                                                                                                   │       
     │     // 输出评论摘要                                                                                               │       
     │     let final_data = storage.load()?;                                                                             │       
     │     output::print_summary(&final_data);                                                                           │       
     │                                                                                                                   │       
     │     Ok(())                                                                                                        │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 5.3 src/cli.rs                                                                                                    │       
     │                                                                                                                   │       
     │ use clap::Parser;                                                                                                 │       
     │                                                                                                                   │       
     │ #[derive(Parser, Debug)]                                                                                          │       
     │ #[command(name = "hrev")]                                                                                         │       
     │ #[command(author = "hrev")]                                                                                       │       
     │ #[command(version = "0.1.0")]                                                                                     │       
     │ #[command(about = "Human review CLI tool", long_about = None)]                                                    │       
     │ pub struct Args {                                                                                                 │       
     │     /// Input: commit hash, file path, or "diff"                                                                  │       
     │     #[arg(value_name = "INPUT")]                                                                                  │       
     │     pub input: String,                                                                                            │       
     │                                                                                                                   │       
     │     /// Port for web server (default: random available port)                                                      │       
     │     #[arg(short, long, default_value = "0")]                                                                      │       
     │     pub port: u16,                                                                                                │       
     │                                                                                                                   │       
     │     /// Don't open browser automatically                                                                          │       
     │     #[arg(long, default_value = "false")]                                                                         │       
     │     pub no_browser: bool,                                                                                         │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 5.4 src/models.rs                                                                                                 │       
     │                                                                                                                   │       
     │ use serde::{Deserialize, Serialize};                                                                              │       
     │ use chrono::{DateTime, Utc};                                                                                      │       
     │ use uuid::Uuid;                                                                                                   │       
     │                                                                                                                   │       
     │ /// 审查数据                                                                                                      │       
     │ #[derive(Debug, Clone, Serialize, Deserialize)]                                                                   │       
     │ pub struct ReviewData {                                                                                           │       
     │     /// 输入类型                                                                                                  │       
     │     pub input_type: InputType,                                                                                    │       
     │     /// 原始输入                                                                                                  │       
     │     pub input: String,                                                                                            │       
     │     /// 评论列表                                                                                                  │       
     │     pub comments: Vec<Comment>,                                                                                   │       
     │     /// 创建时间                                                                                                  │       
     │     pub created_at: DateTime<Utc>,                                                                                │       
     │     /// 状态                                                                                                      │       
     │     pub status: ReviewStatus,                                                                                     │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 输入类型                                                                                                      │       
     │ #[derive(Debug, Clone, Serialize, Deserialize)]                                                                   │       
     │ #[serde(tag = "type")]                                                                                            │       
     │ pub enum InputType {                                                                                              │       
     │     #[serde(rename = "commit_diff")]                                                                              │       
     │     CommitDiff { commit: String },                                                                                │       
     │     #[serde(rename = "file_content")]                                                                             │       
     │     FileContent { path: String },                                                                                 │       
     │     #[serde(rename = "working_tree_diff")]                                                                        │       
     │     WorkingTreeDiff,                                                                                              │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 评论                                                                                                          │       
     │ #[derive(Debug, Clone, Serialize, Deserialize)]                                                                   │       
     │ pub struct Comment {                                                                                              │       
     │     /// 唯一 ID                                                                                                   │       
     │     pub id: String,                                                                                               │       
     │     /// 文件路径（diff 模式下）                                                                                   │       
     │     #[serde(skip_serializing_if = "Option::is_none")]                                                             │       
     │     pub file: Option<String>,                                                                                     │       
     │     /// 行号                                                                                                      │       
     │     #[serde(skip_serializing_if = "Option::is_none")]                                                             │       
     │     pub line: Option<u32>,                                                                                        │       
     │     /// 评论内容                                                                                                  │       
     │     pub text: String,                                                                                             │       
     │     /// 创建时间                                                                                                  │       
     │     pub created_at: DateTime<Utc>,                                                                                │       
     │     /// 已解决标记                                                                                                │       
     │     pub resolved: bool,                                                                                           │       
     │     /// 父评论 ID（用于回复）                                                                                     │       
     │     #[serde(skip_serializing_if = "Option::is_none")]                                                             │       
     │     pub parent_id: Option<String>,                                                                                │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ impl Comment {                                                                                                    │       
     │     pub fn new(file: Option<String>, line: Option<u32>, text: String) -> Self {                                   │       
     │         Self {                                                                                                    │       
     │             id: Uuid::new_v4().to_string(),                                                                       │       
     │             file,                                                                                                 │       
     │             line,                                                                                                 │       
     │             text,                                                                                                 │       
     │             created_at: Utc::now(),                                                                               │       
     │             resolved: false,                                                                                      │       
     │             parent_id: None,                                                                                      │       
     │         }                                                                                                         │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 审查状态                                                                                                      │       
     │ #[derive(Debug, Clone, Serialize, Deserialize)]                                                                   │       
     │ pub enum ReviewStatus {                                                                                           │       
     │     #[serde(rename = "in_progress")]                                                                              │       
     │     InProgress,                                                                                                   │       
     │     #[serde(rename = "completed")]                                                                                │       
     │     Completed,                                                                                                    │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// API 响应 - 初始数据                                                                                           │       
     │ #[derive(Debug, Serialize)]                                                                                       │       
     │ pub struct DataResponse {                                                                                         │       
     │     pub type_: String,                                                                                            │       
     │     pub title: String,                                                                                            │       
     │     pub files: Vec<FileData>,                                                                                     │       
     │     pub comments: Vec<Comment>,                                                                                   │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 文件数据（用于前端渲染）                                                                                      │       
     │ #[derive(Debug, Serialize)]                                                                                       │       
     │ pub struct FileData {                                                                                             │       
     │     pub path: String,                                                                                             │       
     │     pub status: String,                                                                                           │       
     │     pub lines: Vec<LineData>,                                                                                     │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 行数据                                                                                                        │       
     │ #[derive(Debug, Serialize)]                                                                                       │       
     │ pub struct LineData {                                                                                             │       
     │     pub number: u32,                                                                                              │       
     │     pub content: String,                                                                                          │       
     │     #[serde(skip_serializing_if = "Option::is_none")]                                                             │       
     │     pub type_: Option<String>, // "added", "removed", or null                                                     │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// API 请求 - 添加评论                                                                                           │       
     │ #[derive(Debug, Deserialize)]                                                                                     │       
     │ pub struct AddCommentRequest {                                                                                    │       
     │     #[serde(rename = "file")]                                                                                     │       
     │     pub file: Option<String>,                                                                                     │       
     │     pub line: Option<u32>,                                                                                        │       
     │     pub text: String,                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 5.5 src/git_ops.rs                                                                                                │       
     │                                                                                                                   │       
     │ use anyhow::{Context, Result};                                                                                    │       
     │ use git2::{Repository, Diff, Delta};                                                                              │       
     │ use std::path::{Path, PathBuf};                                                                                   │       
     │ use std::fs;                                                                                                      │       
     │                                                                                                                   │       
     │ use crate::models::{InputType, FileData, LineData, ReviewData, Comment, ReviewStatus};                            │       
     │ use crate::storage::ReviewStorage;                                                                                │       
     │                                                                                                                   │       
     │ /// 解析输入                                                                                                      │       
     │ pub fn parse_input(input: &str) -> Result<InputType> {                                                            │       
     │     // 检查是否是 "diff" 关键字                                                                                   │       
     │     if input == "diff" {                                                                                          │       
     │         return Ok(InputType::WorkingTreeDiff);                                                                    │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     // 检查是否是文件路径                                                                                         │       
     │     if Path::new(input).exists() {                                                                                │       
     │         return Ok(InputType::FileContent {                                                                        │       
     │             path: input.to_string(),                                                                              │       
     │         });                                                                                                       │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     // 尝试解析为 commit hash                                                                                     │       
     │     if let Ok(repo) = Repository::discover(".") {                                                                 │       
     │         if let Ok(_) = repo.revparse_single(input) {                                                              │       
     │             return Ok(InputType::CommitDiff {                                                                     │       
     │                 commit: input.to_string(),                                                                        │       
     │             });                                                                                                   │       
     │         }                                                                                                         │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     Err(anyhow::anyhow!("无法解析输入: {}. 请提供: commit hash, 文件路径, 或 'diff'", input))                     │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 获取工作区 diff                                                                                               │       
     │ pub fn get_working_tree_diff() -> Result<Vec<FileData>> {                                                         │       
     │     let repo = Repository::discover(".")?;                                                                        │       
     │     let head = repo.head()?;                                                                                      │       
     │     let head_tree = head.peel_to_tree()?;                                                                         │       
     │                                                                                                                   │       
     │     let mut diff_opts = git2::DiffOptions::new();                                                                 │       
     │     diff_opts.include_unmodified(false);                                                                          │       
     │     diff_opts.recurse_untracked_dirs(true);                                                                       │       
     │                                                                                                                   │       
     │     let diff = repo.diff_tree_to_workdir_with_index(Some(&head_tree), Some(&mut diff_opts))?;                     │       
     │                                                                                                                   │       
     │     diff_to_file_data(&diff, &repo)                                                                               │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 获取 commit diff                                                                                              │       
     │ pub fn get_commit_diff(commit_hash: &str) -> Result<Vec<FileData>> {                                              │       
     │     let repo = Repository::discover(".")?;                                                                        │       
     │     let obj = repo.revparse_single(commit_hash)?;                                                                 │       
     │     let commit = obj.peel_to_commit()?;                                                                           │       
     │                                                                                                                   │       
     │     let parent = commit.parent(0)?;                                                                               │       
     │     let parent_tree = parent.tree()?;                                                                             │       
     │     let commit_tree = commit.tree()?;                                                                             │       
     │                                                                                                                   │       
     │     let diff = repo.diff_tree_to_tree(                                                                            │       
     │         Some(&parent_tree),                                                                                       │       
     │         Some(&commit_tree),                                                                                       │       
     │         None,                                                                                                     │       
     │     )?;                                                                                                           │       
     │                                                                                                                   │       
     │     diff_to_file_data(&diff, &repo)                                                                               │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 获取文件内容                                                                                                  │       
     │ pub fn get_file_content(path: &str) -> Result<Vec<FileData>> {                                                    │       
     │     let content = fs::read_to_string(path)?;                                                                      │       
     │     let lines: Vec<LineData> = content                                                                            │       
     │         .lines()                                                                                                  │       
     │         .enumerate()                                                                                              │       
     │         .map(|(i, line)| LineData {                                                                               │       
     │             number: (i + 1) as u32,                                                                               │       
     │             content: line.to_string(),                                                                            │       
     │             type_: None,                                                                                          │       
     │         })                                                                                                        │       
     │         .collect();                                                                                               │       
     │                                                                                                                   │       
     │     Ok(vec![FileData {                                                                                            │       
     │         path: path.to_string(),                                                                                   │       
     │         status: "view".to_string(),                                                                               │       
     │         lines,                                                                                                    │       
     │     }])                                                                                                           │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 将 git2 Diff 转换为 FileData                                                                                  │       
     │ fn diff_to_file_data(diff: &Diff, repo: &Repository) -> Result<Vec<FileData>> {                                   │       
     │     let mut files = Vec::new();                                                                                   │       
     │                                                                                                                   │       
     │     diff.foreach(                                                                                                 │       
     │         &mut |delta, _progress| {                                                                                 │       
     │             let path = delta.new_file().path().and_then(|p| p.to_str()).unwrap_or("unknown");                     │       
     │             let status = match delta.status() {                                                                   │       
     │                 Delta::Added => "added",                                                                          │       
     │                 Delta::Deleted => "deleted",                                                                      │       
     │                 Delta::Modified => "modified",                                                                    │       
     │                 Delta::Renamed => "renamed",                                                                      │       
     │                 Delta::Copied => "copied",                                                                        │       
     │                 _ => "modified",                                                                                  │       
     │             };                                                                                                    │       
     │                                                                                                                   │       
     │             files.push(FileData {                                                                                 │       
     │                 path: path.to_string(),                                                                           │       
     │                 status: status.to_string(),                                                                       │       
     │                 lines: Vec::new(),                                                                                │       
     │             });                                                                                                   │       
     │             true                                                                                                  │       
     │         },                                                                                                        │       
     │         None,                                                                                                     │       
     │         Some(|delta, hunk| {                                                                                      │       
     │             // 处理每个 hunk                                                                                      │       
     │             true                                                                                                  │       
     │         }),                                                                                                       │       
     │         Some(|delta, hunk, line| {                                                                                │       
     │             // 处理每一行                                                                                         │       
     │             let path = delta.new_file().path().and_then(|p| p.to_str()).unwrap_or("unknown");                     │       
     │             if let Some(file) = files.iter_mut().find(|f| f.path == path) {                                       │       
     │                 let line_num = line.new_lineno().unwrap_or(0);                                                    │       
     │                 let content = std::str::from_utf8(line.content()).unwrap_or("").trim_end().to_string();           │       
     │                 let line_type = match line.origin() {                                                             │       
     │                     '+' | '>' => Some("added".to_string()),                                                       │       
     │                     '-' | '<' => Some("removed".to_string()),                                                     │       
     │                     _ => None,                                                                                    │       
     │                 };                                                                                                │       
     │                                                                                                                   │       
     │                 file.lines.push(LineData {                                                                        │       
     │                     number: line_num,                                                                             │       
     │                     content,                                                                                      │       
     │                     type_: line_type,                                                                             │       
     │                 });                                                                                               │       
     │             }                                                                                                     │       
     │             true                                                                                                  │       
     │         }),                                                                                                       │       
     │     )?;                                                                                                           │       
     │                                                                                                                   │       
     │     Ok(files)                                                                                                     │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 5.6 src/storage.rs                                                                                                │       
     │                                                                                                                   │       
     │ use anyhow::{Context, Result};                                                                                    │       
     │ use std::fs;                                                                                                      │       
     │ use std::path::PathBuf;                                                                                           │       
     │ use sha2::{Sha256, Digest};                                                                                       │       
     │ use chrono::Utc;                                                                                                  │       
     │                                                                                                                   │       
     │ use crate::models::{ReviewData, ReviewStatus, InputType, Comment};                                                │       
     │ use crate::git_ops;                                                                                               │       
     │                                                                                                                   │       
     │ /// 审查存储管理                                                                                                  │       
     │ pub struct ReviewStorage {                                                                                        │       
     │     data_dir: PathBuf,                                                                                            │       
     │     input: InputType,                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ impl ReviewStorage {                                                                                              │       
     │     /// 创建新的存储                                                                                              │       
     │     pub fn new(input: &InputType) -> Result<Self> {                                                               │       
     │         let base_dir = PathBuf::from(".hrev");                                                                    │       
     │         fs::create_dir_all(&base_dir)?;                                                                           │       
     │                                                                                                                   │       
     │         // 计算输入的 hash 作为目录名                                                                             │       
     │         let input_str = match input {                                                                             │       
     │             InputType::CommitDiff { commit } => format!("commit_{}", commit),                                     │       
     │             InputType::FileContent { path } => format!("file_{}", path),                                          │       
     │             InputType::WorkingTreeDiff => "diff".to_string(),                                                     │       
     │         };                                                                                                        │       
     │                                                                                                                   │       
     │         let mut hasher = Sha256::new();                                                                           │       
     │         hasher.update(input_str.as_bytes());                                                                      │       
     │         let hash = format!("{:x}", hasher.finalize());                                                            │       
     │         let data_dir = base_dir.join(&hash[..16]);                                                                │       
     │                                                                                                                   │       
     │         fs::create_dir_all(&data_dir)?;                                                                           │       
     │                                                                                                                   │       
     │         Ok(Self {                                                                                                 │       
     │             data_dir,                                                                                             │       
     │             input: input.clone(),                                                                                 │       
     │         })                                                                                                        │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     /// 获取数据文件路径                                                                                          │       
     │     fn data_file(&self) -> PathBuf {                                                                              │       
     │         self.data_dir.join("data.json")                                                                           │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     /// 加载或创建数据                                                                                            │       
     │     pub fn load_or_create(&self) -> Result<ReviewData> {                                                          │       
     │         let data_file = self.data_file();                                                                         │       
     │                                                                                                                   │       
     │         if data_file.exists() {                                                                                   │       
     │             self.load()                                                                                           │       
     │         } else {                                                                                                  │       
     │             self.create()                                                                                         │       
     │         }                                                                                                         │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     /// 加载数据                                                                                                  │       
     │     pub fn load(&self) -> Result<ReviewData> {                                                                    │       
     │         let data_file = self.data_file();                                                                         │       
     │         let content = fs::read_to_string(&data_file)                                                              │       
     │             .context("Failed to read review data")?;                                                              │       
     │         let data: ReviewData = serde_json::from_str(&content)                                                     │       
     │             .context("Failed to parse review data")?;                                                             │       
     │         Ok(data)                                                                                                  │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     /// 保存数据                                                                                                  │       
     │     pub fn save(&self, data: &ReviewData) -> Result<()> {                                                         │       
     │         let data_file = self.data_file();                                                                         │       
     │         let content = serde_json::to_string_pretty(data)                                                          │       
     │             .context("Failed to serialize review data")?;                                                         │       
     │         fs::write(&data_file, content)                                                                            │       
     │             .context("Failed to write review data")?;                                                             │       
     │         Ok(())                                                                                                    │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     /// 创建新数据                                                                                                │       
     │     fn create(&self) -> Result<ReviewData> {                                                                      │       
     │         let input_str = match &self.input {                                                                       │       
     │             InputType::CommitDiff { commit } => format!("Commit: {}", commit),                                    │       
     │             InputType::FileContent { path } => format!("File: {}", path),                                         │       
     │             InputType::WorkingTreeDiff => "Working Tree Diff".to_string(),                                        │       
     │         };                                                                                                        │       
     │                                                                                                                   │       
     │         Ok(ReviewData {                                                                                           │       
     │             input_type: self.input.clone(),                                                                       │       
     │             input: input_str,                                                                                     │       
     │             comments: Vec::new(),                                                                                 │       
     │             created_at: Utc::now(),                                                                               │       
     │             status: ReviewStatus::InProgress,                                                                     │       
     │         })                                                                                                        │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 5.7 src/server.rs                                                                                                 │       
     │                                                                                                                   │       
     │ use anyhow::Result;                                                                                               │       
     │ use axum::{                                                                                                       │       
     │     extract::{                                                                                                    │       
     │         State,                                                                                                    │       
     │         Path,                                                                                                     │       
     │     },                                                                                                            │       
     │     http::StatusCode,                                                                                             │       
     │     response::{IntoResponse, Response},                                                                           │       
     │     routing::{get, post, put, delete},                                                                            │       
     │     Router,                                                                                                       │       
     │     Json,                                                                                                         │       
     │ };                                                                                                                │       
     │ use askama::Template;                                                                                             │       
     │ use std::sync::Arc;                                                                                               │       
     │ use tokio::sync::RwLock;                                                                                          │       
     │ use tower_http::cors::CorsLayer;                                                                                  │       
     │ use tracing::info;                                                                                                │       
     │                                                                                                                   │       
     │ use crate::models::{ReviewData, AddCommentRequest, Comment, DataResponse, FileData};                              │       
     │ use crate::storage::ReviewStorage;                                                                                │       
     │ use crate::routes::*;                                                                                             │       
     │ use crate::git_ops;                                                                                               │       
     │                                                                                                                   │       
     │ /// 服务器状态                                                                                                    │       
     │ #[derive(Clone)]                                                                                                  │       
     │ pub struct AppState {                                                                                             │       
     │     pub data: Arc<RwLock<ReviewData>>,                                                                            │       
     │     pub storage: ReviewStorage,                                                                                   │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 全局完成信号                                                                                                  │       
     │ static COMPLETION_SIGNAL: tokio::sync::Notify = tokio::sync::Notify::const_notify();                              │       
     │                                                                                                                   │       
     │ /// 运行服务器                                                                                                    │       
     │ pub async fn run(port: u16, data: ReviewData) -> Result<u16> {                                                    │       
     │     let storage = ReviewStorage::new(&data.input_type)?;                                                          │       
     │     storage.save(&data)?;                                                                                         │       
     │                                                                                                                   │       
     │     let state = AppState {                                                                                        │       
     │         data: Arc::new(RwLock::new(data)),                                                                        │       
     │         storage,                                                                                                  │       
     │     };                                                                                                            │       
     │                                                                                                                   │       
     │     let app = Router::new()                                                                                       │       
     │         .route("/", get(index_handler))                                                                           │       
     │         .route("/api/data", get(get_data_handler))                                                                │       
     │         .route("/api/comments", post(add_comment_handler))                                                        │       
     │         .route("/api/comments/:id", put(update_comment_handler))                                                  │       
     │         .route("/api/comments/:id", delete(delete_comment_handler))                                               │       
     │         .route("/api/complete", post(complete_handler))                                                           │       
     │         .layer(CorsLayer::permissive())                                                                           │       
     │         .with_state(state);                                                                                       │       
     │                                                                                                                   │       
     │     // 绑定端口                                                                                                   │       
     │     let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)).await?;                             │       
     │     let actual_port = listener.local_addr()?.port();                                                              │       
     │     info!("Server running on port {}", actual_port);                                                              │       
     │                                                                                                                   │       
     │     // 启动服务器                                                                                                 │       
     │     tokio::spawn(async move {                                                                                     │       
     │         axum::serve(listener, app).await.unwrap();                                                                │       
     │     });                                                                                                           │       
     │                                                                                                                   │       
     │     Ok(actual_port)                                                                                               │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 等待完成信号                                                                                                  │       
     │ pub async fn wait_for_completion() -> Result<()> {                                                                │       
     │     COMPLETION_SIGNAL.notified().await;                                                                           │       
     │     Ok(())                                                                                                        │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 主页模板                                                                                                      │       
     │ #[derive(Template)]                                                                                               │       
     │ #[template(path = "review.html")]                                                                                 │       
     │ struct IndexTemplate {}                                                                                           │       
     │                                                                                                                   │       
     │ /// 主页 handler                                                                                                  │       
     │ async fn index_handler() -> impl IntoResponse {                                                                   │       
     │     IndexTemplate {}                                                                                              │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 5.8 src/routes.rs                                                                                                 │       
     │                                                                                                                   │       
     │ use axum::{                                                                                                       │       
     │     extract::{                                                                                                    │       
     │         State,                                                                                                    │       
     │         Path,                                                                                                     │       
     │     },                                                                                                            │       
     │     http::StatusCode,                                                                                             │       
     │     response::{IntoResponse, Response},                                                                           │       
     │     Json,                                                                                                         │       
     │ };                                                                                                                │       
     │ use tracing::info;                                                                                                │       
     │                                                                                                                   │       
     │ use crate::models::*;                                                                                             │       
     │ use crate::server::{AppState, COMPLETION_SIGNAL};                                                                 │       
     │ use crate::git_ops;                                                                                               │       
     │                                                                                                                   │       
     │ /// 获取初始数据                                                                                                  │       
     │ pub async fn get_data_handler(State(state): State<AppState>) -> Result<Json<DataResponse>, AppError> {            │       
     │     let data = state.data.read().await;                                                                           │       
     │                                                                                                                   │       
     │     // 根据 input_type 获取文件内容                                                                               │       
     │     let files = match &data.input_type {                                                                          │       
     │         InputType::CommitDiff { commit } => {                                                                     │       
     │             git_ops::get_commit_diff(commit)?                                                                     │       
     │         }                                                                                                         │       
     │         InputType::FileContent { path } => {                                                                      │       
     │             git_ops::get_file_content(path)?                                                                      │       
     │         }                                                                                                         │       
     │         InputType::WorkingTreeDiff => {                                                                           │       
     │             git_ops::get_working_tree_diff()?                                                                     │       
     │         }                                                                                                         │       
     │     };                                                                                                            │       
     │                                                                                                                   │       
     │     let response = DataResponse {                                                                                 │       
     │         type_: match data.input_type {                                                                            │       
     │             InputType::CommitDiff { .. } => "commit_diff".to_string(),                                            │       
     │             InputType::FileContent { .. } => "file_content".to_string(),                                          │       
     │             InputType::WorkingTreeDiff => "working_tree_diff".to_string(),                                        │       
     │         },                                                                                                        │       
     │         title: data.input.clone(),                                                                                │       
     │         files,                                                                                                    │       
     │         comments: data.comments.clone(),                                                                          │       
     │     };                                                                                                            │       
     │                                                                                                                   │       
     │     Ok(Json(response))                                                                                            │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 添加评论                                                                                                      │       
     │ pub async fn add_comment_handler(                                                                                 │       
     │     State(state): State<AppState>,                                                                                │       
     │     Json(req): Json<AddCommentRequest>,                                                                           │       
     │ ) -> Result<Json<Comment>, AppError> {                                                                            │       
     │     let mut data = state.data.write().await;                                                                      │       
     │                                                                                                                   │       
     │     let comment = Comment::new(req.file, req.line, req.text);                                                     │       
     │     data.comments.push(comment.clone());                                                                          │       
     │                                                                                                                   │       
     │     state.storage.save(&data)?;                                                                                   │       
     │     info!("Added comment: {}", comment.id);                                                                       │       
     │                                                                                                                   │       
     │     Ok(Json(comment))                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 更新评论                                                                                                      │       
     │ pub async fn update_comment_handler(                                                                              │       
     │     State(state): State<AppState>,                                                                                │       
     │     Path(id): Path<String>,                                                                                       │       
     │     Json(mut comment): Json<Comment>,                                                                             │       
     │ ) -> Result<Json<Comment>, AppError> {                                                                            │       
     │     let mut data = state.data.write().await;                                                                      │       
     │                                                                                                                   │       
     │     if let Some(existing) = data.comments.iter_mut().find(|c| c.id == id) {                                       │       
     │         comment.id = id; // 确保 ID 不变                                                                          │       
     │         *existing = comment.clone();                                                                              │       
     │         state.storage.save(&data)?;                                                                               │       
     │         Ok(Json(comment))                                                                                         │       
     │     } else {                                                                                                      │       
     │         Err(AppError::CommentNotFound(id))                                                                        │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 删除评论                                                                                                      │       
     │ pub async fn delete_comment_handler(                                                                              │       
     │     State(state): State<AppState>,                                                                                │       
     │     Path(id): Path<String>,                                                                                       │       
     │ ) -> Result<StatusCode, AppError> {                                                                               │       
     │     let mut data = state.data.write().await;                                                                      │       
     │                                                                                                                   │       
     │     if let Some(pos) = data.comments.iter().position(|c| c.id == id) {                                            │       
     │         data.comments.remove(pos);                                                                                │       
     │         state.storage.save(&data)?;                                                                               │       
     │         info!("Deleted comment: {}", id);                                                                         │       
     │         Ok(StatusCode::NO_CONTENT)                                                                                │       
     │     } else {                                                                                                      │       
     │         Err(AppError::CommentNotFound(id))                                                                        │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 完成审查                                                                                                      │       
     │ pub async fn complete_handler(State(state): State<AppState>) -> Result<Json<CompletionResponse>, AppError> {      │       
     │     let mut data = state.data.write().await;                                                                      │       
     │     data.status = ReviewStatus::Completed;                                                                        │       
     │     state.storage.save(&data)?;                                                                                   │       
     │                                                                                                                   │       
     │     // 发送完成信号                                                                                               │       
     │     COMPLETION_SIGNAL.notify_one();                                                                               │       
     │                                                                                                                   │       
     │     Ok(Json(CompletionResponse {                                                                                  │       
     │         message: "Review completed".to_string(),                                                                  │       
     │         comment_count: data.comments.len(),                                                                       │       
     │     }))                                                                                                           │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 完成响应                                                                                                      │       
     │ #[derive(serde::Serialize)]                                                                                       │       
     │ struct CompletionResponse {                                                                                       │       
     │     message: String,                                                                                              │       
     │     comment_count: usize,                                                                                         │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ /// 应用错误                                                                                                      │       
     │ #[derive(Debug)]                                                                                                  │       
     │ pub enum AppError {                                                                                               │       
     │     CommentNotFound(String),                                                                                      │       
     │     Internal(anyhow::Error),                                                                                      │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ impl IntoResponse for AppError {                                                                                  │       
     │     fn into_response(self) -> Response {                                                                          │       
     │         let (status, message) = match self {                                                                      │       
     │             AppError::CommentNotFound(id) => (StatusCode::NOT_FOUND, format!("Comment not found: {}", id)),       │       
     │             AppError::Internal(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),                          │       
     │         };                                                                                                        │       
     │         (status, message).into_response()                                                                         │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ impl From<anyhow::Error> for AppError {                                                                           │       
     │     fn from(e: anyhow::Error) -> Self {                                                                           │       
     │         AppError::Internal(e)                                                                                     │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 5.9 src/output.rs                                                                                                 │       
     │                                                                                                                   │       
     │ use colored::Colorize;                                                                                            │       
     │ use crate::models::{ReviewData, Comment};                                                                         │       
     │                                                                                                                   │       
     │ /// 打印评论摘要（Markdown 格式）                                                                                 │       
     │ pub fn print_summary(data: &ReviewData) {                                                                         │       
     │     println!();                                                                                                   │       
     │     println!("{}", "═".repeat(60));                                                                               │       
     │     println!("{}", "📋 Review Summary".bold().cyan());                                                            │       
     │     println!("{}", "═".repeat(60));                                                                               │       
     │     println!();                                                                                                   │       
     │                                                                                                                   │       
     │     println!("{}: {}", "Input".bold(), data.input);                                                               │       
     │     println!("{}: {}", "Created".bold(), data.created_at.format("%Y-%m-%d %H:%M:%S"));                            │       
     │     println!("{}: {}", "Comments".bold(), data.comments.len());                                                   │       
     │     println!();                                                                                                   │       
     │                                                                                                                   │       
     │     if data.comments.is_empty() {                                                                                 │       
     │         println!("{}", "No comments added.".dimmed());                                                            │       
     │         return;                                                                                                   │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     // 按文件分组                                                                                                 │       
     │     let mut by_file: std::collections::HashMap<Option<String>, Vec<&Comment>> = std::collections::HashMap::new(); │       
     │     for comment in &data.comments {                                                                               │       
     │         by_file.entry(comment.file.clone()).or_default().push(comment);                                           │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     // 打印评论（Markdown 格式）                                                                                  │       
     │     for (file, comments) in by_file.iter() {                                                                      │       
     │         if let Some(f) = file {                                                                                   │       
     │             println!("\n## 📄 {}", f.bold());                                                                     │       
     │         } else {                                                                                                  │       
     │             println!("\n## 💬 Global Comments");                                                                  │       
     │         }                                                                                                         │       
     │                                                                                                                   │       
     │         for comment in comments {                                                                                 │       
     │             println!();                                                                                           │       
     │             if comment.resolved {                                                                                 │       
     │                 print!("✅ ");                                                                                    │       
     │             } else {                                                                                              │       
     │                 print!("💬 ");                                                                                    │       
     │             }                                                                                                     │       
     │                                                                                                                   │       
     │             if let Some(line) = comment.line {                                                                    │       
     │                 print!("Line {}: ", line.to_string().yellow());                                                   │       
     │             }                                                                                                     │       
     │                                                                                                                   │       
     │             println!("{}", comment.text);                                                                         │       
     │             println!("    {} {}", "─".dimmed(), comment.created_at.format("%H:%M").to_string().dimmed());         │       
     │                                                                                                                   │       
     │             // 统计                                                                                               │       
     │             let unresolved = data.comments.iter().filter(|c| !c.resolved).count();                                │       
     │             let resolved = data.comments.iter().filter(|c| c.resolved).count();                                   │       
     │                                                                                                                   │       
     │             println!();                                                                                           │       
     │             println!("{}", "─".repeat(60).dimmed());                                                              │       
     │             println!("{} {} unresolved, {} resolved",                                                             │       
     │                 "Summary:".bold(),                                                                                │       
     │                 unresolved.to_string().yellow(),                                                                  │       
     │                 resolved.to_string().green()                                                                      │       
     │             );                                                                                                    │       
     │         }                                                                                                         │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ // git_ops.rs                                                                                                     │       
     │ use git2::{Repository, Diff, Object};                                                                             │       
     │                                                                                                                   │       
     │ /// 获取 commit diff                                                                                              │       
     │ fn get_commit_diff(repo: &Repository, commit_hash: &str) -> Result<DiffData>;                                     │       
     │                                                                                                                   │       
     │ /// 获取工作区 diff                                                                                               │       
     │ fn get_working_tree_diff(repo: &Repository) -> Result<DiffData>;                                                  │       
     │                                                                                                                   │       
     │ /// 获取文件内容                                                                                                  │       
     │ fn get_file_content(path: &Path) -> Result<String>;                                                               │       
     │                                                                                                                   │       
     │ /// 解析输入（自动判断类型）                                                                                      │       
     │ fn parse_input(input: &str) -> Result<ParsedInput>;                                                               │       
     │                                                                                                                   │       
     │ enum ParsedInput {                                                                                                │       
     │     Commit(String),  // commit hash                                                                               │       
     │     File(PathBuf),   // file path                                                                                 │       
     │     Diff,            // working tree diff                                                                         │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ struct DiffData {                                                                                                 │       
     │     files: Vec<FileDiff>,                                                                                         │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ struct FileDiff {                                                                                                 │       
     │     path: String,                                                                                                 │       
     │     old_path: Option<String>,  // for rename                                                                      │       
     │     status: FileStatus,                                                                                           │       
     │     hunks: Vec<DiffHunk>,                                                                                         │       
     │     lines: Vec<Line>,  // 带行号的内容，用于渲染                                                                  │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ struct DiffHunk {                                                                                                 │       
     │     old_start: u32,                                                                                               │       
     │     old_lines: u32,                                                                                               │       
     │     new_start: u32,                                                                                               │       
     │     new_lines: u32,                                                                                               │       
     │     header: String,                                                                                               │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ enum FileStatus {                                                                                                 │       
     │     Modified,                                                                                                     │       
     │     Added,                                                                                                        │       
     │     Deleted,                                                                                                      │       
     │     Renamed,                                                                                                      │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 6. Claude Code Skill 设计（自动处理模式）                                                                         │       
     │                                                                                                                   │       
     │ 文件位置: .claude/skills/human-review.md                                                                          │       
     │                                                                                                                   │       
     │ ---                                                                                                               │       
     │ description: 启动人工审查流程，自动读取评论并修改代码                                                             │       
     │ ---                                                                                                               │       
     │                                                                                                                   │       
     │ ## Human Review Skill                                                                                             │       
     │                                                                                                                   │       
     │ 自动启动人工审查，等待用户完成评论，然后自动根据反馈修改代码。                                                    │       
     │                                                                                                                   │       
     │ ## 工作流程                                                                                                       │       
     │                                                                                                                   │       
     │ ### 1. 检测变更并启动 hrev                                                                                        │       
     │                                                                                                                   │       
     │ 首先判断当前状态：                                                                                                │       
     │ - 有未提交的 git 变更 → `hrev diff`                                                                               │       
     │ - 用户指定了文件 → `hrev <file>`                                                                                  │       
     │ - 用户指定了 commit → `hrev <commit>`                                                                             │       
     │                                                                                                                   │       
     │ ### 2. 等待审查完成                                                                                               │       
     │                                                                                                                   │       
     │ - 运行 hrev 命令                                                                                                  │       
     │ - 工具会自动打开浏览器                                                                                            │       
     │ - **等待**用户在浏览器中完成评论                                                                                  │       
     │ - 用户点击"完成"后，hrev 会输出结果并退出                                                                         │       
     │                                                                                                                   │       
     │ ### 3. 解析审查结果                                                                                               │       
     │                                                                                                                   │       
     │ hrev 输出包含：                                                                                                   │       
     │ - 终端摘要（评论数量、涉及文件）                                                                                  │       
     │ - Markdown 文件（详细评论 + 上下文）                                                                              │       
     │ - JSON 文件（结构化数据）                                                                                         │       
     │                                                                                                                   │       
     │ 从输出中提取：                                                                                                    │       
     │ ```bash                                                                                                           │       
     │ # 输出示例                                                                                                        │       
     │ Review Results: diff | 5 comment(s)                                                                               │       
     │                                                                                                                   │       
     │ 📄 src/main.rs                                                                                                    │       
     │   [L42] Consider using debug logging instead                                                                      │       
     │   [L55] This function should return a Result                                                                      │       
     │                                                                                                                   │       
     │ 📄 src/parser.rs                                                                                                  │       
     │   [L12] Variable name unclear                                                                                     │       
     │   [L30] Missing error handling                                                                                    │       
     │   [L45] Great implementation!                                                                                     │       
     │                                                                                                                   │       
     │ 💾 Full results saved to:                                                                                         │       
     │    .hrev/sessions/abc123/review.json                                                                              │       
     │    .hrev/sessions/abc123/review.md                                                                                │       
     │                                                                                                                   │       
     │ 4. 自动处理评论（关键）                                                                                           │       
     │                                                                                                                   │       
     │ 读取 .hrev/sessions/<id>/review.md，解析每条评论：                                                                │       
     │                                                                                                                   │       
     │ 对于每条评论：                                                                                                    │       
     │ #### Line 42: Consider using debug logging instead                                                                │       
     │ ```context                                                                                                        │       
     │   40 |     fn process(&self) {                                                                                    │       
     │   41 |         let data = self.load();                                                                            │       
     │   42 | >>> Consider using debug logging instead                                                                   │       
     │   43 |         println!("{:?}", data);                                                                            │       
     │   44 |     }                                                                                                      │       
     │                                                                                                                   │       
     │ **自动处理策略：**                                                                                                │       
     │ 1. 理解评论内容                                                                                                   │       
     │ 2. 定位到对应文件和行号                                                                                           │       
     │ 3. 根据评论意图修改代码                                                                                           │       
     │ 4. 修改后报告变更                                                                                                 │       
     │                                                                                                                   │       
     │ ### 5. 汇报处理结果                                                                                               │       
     │                                                                                                                   │       
     │ 完成所有修改后，汇报：                                                                                            │       
     │ ✅ 已处理 5 条评论：                                                                                              │       
     │                                                                                                                   │       
     │ 1. src/main.rs:42 - Consider using debug logging instead                                                          │       
     │ → 改用 log::debug!() 替代 println!()                                                                              │       
     │ 2. src/main.rs:55 - This function should return a Result                                                          │       
     │ → 添加了 Result<(), Error> 返回类型                                                                               │       
     │ 3. src/parser.rs:12 - Variable name unclear                                                                       │       
     │ → 重命名为 input_buffer                                                                                           │       
     │ 4. src/parser.rs:30 - Missing error handling                                                                      │       
     │ → 添加了 ? 错误传播                                                                                               │       
     │ 5. src/parser.rs:45 - Great implementation!                                                                       │       
     │ → （无需修改）                                                                                                    │       
     │                                                                                                                   │       
     │ 是否需要再次 review？                                                                                             │       
     │                                                                                                                   │       
     │ ## 重要规则                                                                                                       │       
     │                                                                                                                   │       
     │ - **必须等待** hrev 完成后再继续                                                                                  │       
     │ - **必须读取** review.md 文件获取完整上下文                                                                       │       
     │ - **必须处理** 每条评论（即使是"同意"类评论也要确认）                                                             │       
     │ - **修改前** 确认理解评论意图                                                                                     │       
     │ - **修改后** 汇报每条评论的处理方式                                                                               │       
     │                                                                                                                   │       
     │ 文件位置: .claude/skills/human-review.json                                                                        │       
     │                                                                                                                   │       
     │ {                                                                                                                 │       
     │   "name": "human-review",                                                                                         │       
     │   "description": "Launch human review and auto-apply feedback",                                                   │       
     │   "parameters": {                                                                                                 │       
     │     "target": {                                                                                                   │       
     │       "type": "string",                                                                                           │       
     │       "description": "Commit hash, file path, or 'diff'",                                                         │       
     │       "default": "diff"                                                                                           │       
     │     }                                                                                                             │       
     │   }                                                                                                               │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ Skill 调用示例                                                                                                    │       
     │                                                                                                                   │       
     │ 用户说：                                                                                                          │       
     │ - "Review my changes" → skill 检测变更，调用 hrev diff                                                            │       
     │ - "Review this plan" → skill 调用 hrev plan.md                                                                    │       
     │ - "Review commit abc123" → skill 调用 hrev abc123                                                                 │       
     │                                                                                                                   │       
     │ 7. 完整项目结构                                                                                                   │       
     │                                                                                                                   │       
     │ hrev/                                                                                                             │       
     │ ├── Cargo.toml                                                                                                    │       
     │ ├── Cargo.lock                                                                                                    │       
     │ ├── src/                                                                                                          │       
     │ │   ├── main.rs           # CLI 入口，main 函数                                                                   │       
     │ │   ├── cli.rs            # Args 结构体，输入解析                                                                 │       
     │ │   ├── server.rs         # Axum 服务器，路由定义                                                                 │       
     │ │   ├── git_ops.rs        # Git 操作封装                                                                          │       
     │ │   ├── models.rs         # 数据结构定义                                                                          │       
     │ │   ├── storage.rs        # 评论存储管理                                                                          │       
     │ │   └── routes.rs         # API handler                                                                           │       
     │ ├── templates/                                                                                                    │       
     │ │   └── review.html       # Askama 模板                                                                           │       
     │ ├── static/                                                                                                       │       
     │ │   └── app.js            # 前端逻辑（可内联到 html）                                                             │       
     │ ├── skills/                                                                                                       │       
     │ │   └── review-changes.md # Claude Code skill                                                                     │       
     │ └── README.md                                                                                                     │       
     │                                                                                                                   │       
     │ 8. 关键文件预览                                                                                                   │       
     │                                                                                                                   │       
     │ src/main.rs:                                                                                                      │       
     │ #[tokio::main]                                                                                                    │       
     │ async fn main() -> Result<()> {                                                                                   │       
     │     let args = Args::parse();                                                                                     │       
     │     let parsed = parse_input(&args.input)?;                                                                       │       
     │                                                                                                                   │       
     │     let (data, port) = setup_review_session(parsed).await?;                                                       │       
     │                                                                                                                   │       
     │     if !args.no_browser {                                                                                         │       
     │         open_browser(port);                                                                                       │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     run_server(data, port).await?;                                                                                │       
     │                                                                                                                   │       
     │     // 输出评论摘要                                                                                               │       
     │     print_summary(&data);                                                                                         │       
     │                                                                                                                   │       
     │     Ok(())                                                                                                        │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 9. 四个关键技术问题的解决方案                                                                                     │       
     │                                                                                                                   │       
     │ 问题 1: 用户评论怎么收集？                                                                                        │       
     │                                                                                                                   │       
     │ 前端收集流程：                                                                                                    │       
     │                                                                                                                   │       
     │ // static/app.js - 评论收集逻辑                                                                                   │       
     │ class ReviewApp {                                                                                                 │       
     │     constructor() {                                                                                               │       
     │         this.comments = [];                                                                                       │       
     │         this.currentFile = null;                                                                                  │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     // 点击行时触发                                                                                               │       
     │     onLineClick(lineElement) {                                                                                    │       
     │         const file = this.currentFile;                                                                            │       
     │         const line = parseInt(lineElement.dataset.line);                                                          │       
     │         this.openCommentModal(file, line);                                                                        │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     // 打开评论输入弹窗                                                                                           │       
     │     openCommentModal(file, line) {                                                                                │       
     │         const modal = document.getElementById('comment-modal');                                                   │       
     │         const textarea = document.getElementById('comment-text');                                                 │       
     │                                                                                                                   │       
     │         modal.dataset.file = file;                                                                                │       
     │         modal.dataset.line = line;                                                                                │       
     │         textarea.value = '';                                                                                      │       
     │         modal.classList.remove('hidden');                                                                         │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     // 提交评论                                                                                                   │       
     │     async submitComment() {                                                                                       │       
     │         const modal = document.getElementById('comment-modal');                                                   │       
     │         const comment = {                                                                                         │       
     │             file: modal.dataset.file,                                                                             │       
     │             line: parseInt(modal.dataset.line),                                                                   │       
     │             text: document.getElementById('comment-text').value,                                                  │       
     │             timestamp: new Date().toISOString()                                                                   │       
     │         };                                                                                                        │       
     │                                                                                                                   │       
     │         // 发送到后端                                                                                             │       
     │         const response = await fetch('/api/comments', {                                                           │       
     │             method: 'POST',                                                                                       │       
     │             headers: { 'Content-Type': 'application/json' },                                                      │       
     │             body: JSON.stringify(comment)                                                                         │       
     │         });                                                                                                       │       
     │                                                                                                                   │       
     │         const saved = await response.json();                                                                      │       
     │         this.comments.push(saved);                                                                                │       
     │         this.renderComments();                                                                                    │       
     │         modal.classList.add('hidden');                                                                            │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 后端存储流程：                                                                                                    │       
     │                                                                                                                   │       
     │ // src/storage.rs - 评论持久化                                                                                    │       
     │ use serde_json;                                                                                                   │       
     │ use std::fs;                                                                                                      │       
     │ use std::path::PathBuf;                                                                                           │       
     │                                                                                                                   │       
     │ pub struct CommentStorage {                                                                                       │       
     │     session_dir: PathBuf,                                                                                         │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ impl CommentStorage {                                                                                             │       
     │     pub fn new(session_id: &str) -> Result<Self> {                                                                │       
     │         let dir = dirs::home_dir()                                                                                │       
     │             .unwrap()                                                                                             │       
     │             .join(".hrev")                                                                                        │       
     │             .join("sessions")                                                                                     │       
     │             .join(session_id);                                                                                    │       
     │         fs::create_dir_all(&dir)?;                                                                                │       
     │         Ok(Self { session_dir: dir })                                                                             │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     // 添加评论                                                                                                   │       
     │     pub fn add_comment(&self, comment: &Comment) -> Result<()> {                                                  │       
     │         let mut comments = self.load_comments()?;                                                                 │       
     │         comments.push(comment.clone());                                                                           │       
     │         self.save_comments(&comments)?;                                                                           │       
     │         Ok(())                                                                                                    │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     // 保存到 JSON                                                                                                │       
     │     fn save_comments(&self, comments: &[Comment]) -> Result<()> {                                                 │       
     │         let path = self.session_dir.join("comments.json");                                                        │       
     │         let json = serde_json::to_string_pretty(comments)?;                                                       │       
     │         fs::write(path, json)?;                                                                                   │       
     │         Ok(())                                                                                                    │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 问题 2: JS 渲染 diff 用哪一套？                                                                                   │       
     │                                                                                                                   │       
     │ 方案：自己实现简单 diff 渲染，不依赖外部库                                                                        │       
     │                                                                                                                   │       
     │ // static/app.js - 简单 diff 渲染                                                                                 │       
     │ class DiffRenderer {                                                                                              │       
     │     // 渲染单个 diff hunk                                                                                         │       
     │     renderHunk(hunk) {                                                                                            │       
     │         return hunk.lines.map(line => {                                                                           │       
     │             const className = line.type === 'added' ? 'added' :                                                   │       
     │                             line.type === 'removed' ? 'removed' : 'context';                                      │       
     │             const prefix = line.type === 'added' ? '+' :                                                          │       
     │                           line.type === 'removed' ? '-' : ' ';                                                    │       
     │             return `<div class="diff-line ${className}"                                                           │       
     │                         data-file="${hunk.file}"                                                                  │       
     │                         data-line="${line.number}"                                                                │       
     │                         onclick="app.onLineClick(this)">                                                          │       
     │                         <span class="line-prefix">${prefix}</span>                                                │       
     │                         <span class="line-content">${this.escapeHtml(line.content)}</span>                        │       
     │                     </div>`;                                                                                      │       
     │         }).join('');                                                                                              │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     // 渲染文件 diff                                                                                              │       
     │     renderFileDiff(fileDiff) {                                                                                    │       
     │         return `                                                                                                  │       
     │             <div class="file-diff" data-file="${fileDiff.path}">                                                  │       
     │                 <div class="file-header">                                                                         │       
     │                     <span class="file-status ${fileDiff.status}">${fileDiff.status}</span>                        │       
     │                     <span class="file-path">${fileDiff.path}</span>                                               │       
     │                 </div>                                                                                            │       
     │                 ${fileDiff.hunks.map(h => this.renderHunk(h)).join('')}                                           │       
     │             </div>                                                                                                │       
     │         `;                                                                                                        │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     escapeHtml(text) {                                                                                            │       
     │         const div = document.createElement('div');                                                                │       
     │         div.textContent = text;                                                                                   │       
     │         return div.innerHTML;                                                                                     │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ CSS 样式：                                                                                                        │       
     │                                                                                                                   │       
     │ /* static/app.css - Diff 样式 */                                                                                  │       
     │ .diff-line {                                                                                                      │       
     │     font-family: 'Monaco', 'Menlo', monospace;                                                                    │       
     │     font-size: 13px;                                                                                              │       
     │     line-height: 20px;                                                                                            │       
     │     padding: 0 10px;                                                                                              │       
     │     cursor: pointer;                                                                                              │       
     │     transition: background 0.15s;                                                                                 │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ .diff-line:hover {                                                                                                │       
     │     background: rgba(255, 255, 255, 0.05);                                                                        │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ .diff-line .line-prefix {                                                                                         │       
     │     color: #8b949e;                                                                                               │       
     │     margin-right: 10px;                                                                                           │       
     │     user-select: none;                                                                                            │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ .diff-line.added {                                                                                                │       
     │     background: rgba(46, 160, 67, 0.15);                                                                          │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ .diff-line.added .line-prefix {                                                                                   │       
     │     color: #3fb950;                                                                                               │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ .diff-line.removed {                                                                                              │       
     │     background: rgba(248, 81, 73, 0.15);                                                                          │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ .diff-line.removed .line-prefix {                                                                                 │       
     │     color: #f85149;                                                                                               │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 问题 3: 怎么打包到一个 binary 里面？                                                                              │       
     │                                                                                                                   │       
     │ 方案：使用 rust-embed 嵌入静态资源                                                                                │       
     │                                                                                                                   │       
     │ # Cargo.toml 添加依赖                                                                                             │       
     │ [dependencies]                                                                                                    │       
     │ rust-embed = "8.0"                                                                                                │       
     │ mime_guess = "2.0"                                                                                                │       
     │                                                                                                                   │       
     │ // src/static_assets.rs - 嵌入静态资源                                                                            │       
     │ use rust_embed::RustEmbed;                                                                                        │       
     │                                                                                                                   │       
     │ #[derive(RustEmbed)]                                                                                              │       
     │ #[folder = "static/"]                                                                                             │       
     │ struct Assets;                                                                                                    │       
     │                                                                                                                   │       
     │ #[derive(RustEmbed)]                                                                                              │       
     │ #[folder = "templates/"]                                                                                          │       
     │ struct Templates;                                                                                                 │       
     │                                                                                                                   │       
     │ pub fn get_asset(path: &str) -> Option<(Vec<u8>, &'static str)> {                                                 │       
     │     let asset = Assets::get(path.trim_start_matches('/'))?;                                                       │       
     │     let mime = mime_guess::from_path(path)                                                                        │       
     │         .first_or_octet_stream()                                                                                  │       
     │         .to_string();                                                                                             │       
     │     // leak 转换为 &'static str，生命周期与程序相同                                                               │       
     │     let mime_static: &'static str = Box::leak(mime.into_boxed_str());                                             │       
     │     Some((asset.data.to_vec(), mime_static))                                                                      │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ pub fn get_template(name: &str) -> Option<String> {                                                               │       
     │     let asset = Templates::get(name)?;                                                                            │       
     │     Some(String::from_utf8(asset.data.to_vec()).ok()?)                                                            │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ Axum 路由使用嵌入资源：                                                                                           │       
     │                                                                                                                   │       
     │ // src/routes.rs                                                                                                  │       
     │ use axum::{response::Response, body::Body, http::{StatusCode, header}};                                           │       
     │ use crate::static_assets::get_asset;                                                                              │       
     │                                                                                                                   │       
     │ pub async fn serve_static(path: String) -> Response {                                                             │       
     │     match get_asset(&path) {                                                                                      │       
     │         Some((data, mime)) => {                                                                                   │       
     │             let mut response = Response::new(Body::from(data));                                                   │       
     │             response.headers_mut().insert(                                                                        │       
     │                 header::CONTENT_TYPE,                                                                             │       
     │                 header::HeaderValue::from_static(mime)                                                            │       
     │             );                                                                                                    │       
     │             response                                                                                              │       
     │         }                                                                                                         │       
     │         None => {                                                                                                 │       
     │             let mut response = Response::new(Body::empty());                                                      │       
     │             *response.status_mut() = StatusCode::NOT_FOUND;                                                       │       
     │             response                                                                                              │       
     │         }                                                                                                         │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ pub async fn serve_index() -> Response {                                                                          │       
     │     match get_template("review.html") {                                                                           │       
     │         Some(html) => {                                                                                           │       
     │             let mut response = Response::new(Body::from(html));                                                   │       
     │             response.headers_mut().insert(                                                                        │       
     │                 header::CONTENT_TYPE,                                                                             │       
     │                 header::HeaderValue::from_static("text/html; charset=utf-8")                                      │       
     │             );                                                                                                    │       
     │             response                                                                                              │       
     │         }                                                                                                         │       
     │         None => {                                                                                                 │       
     │             let mut response = Response::new(Body::from("Template not found"));                                   │       
     │             *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;                                           │       
     │             response                                                                                              │       
     │         }                                                                                                         │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 项目结构调整：                                                                                                    │       
     │                                                                                                                   │       
     │ hrev/                                                                                                             │       
     │ ├── Cargo.toml                                                                                                    │       
     │ ├── src/                                                                                                          │       
     │ │   ├── main.rs                                                                                                   │       
     │ │   ├── static_assets.rs    # 嵌入资源                                                                            │       
     │ │   └── ...                                                                                                       │       
     │ ├── static/                 # 开发时存在，编译后嵌入二进制                                                        │       
     │ │   ├── app.js                                                                                                    │       
     │ │   └── app.css                                                                                                   │       
     │ └── templates/                                                                                                    │       
     │     └── review.html                                                                                               │       
     │                                                                                                                   │       
     │ 编译后：                                                                                                          │       
     │ - cargo build --release 生成单一二进制 target/release/hrev                                                        │       
     │ - 无需携带 static/ 目录                                                                                           │       
     │ - 可直接复制到任何地方运行                                                                                        │       
     │                                                                                                                   │       
     │ 问题 4: 如何将用户的评论与文件、行号、上下文整理为合适的输出？                                                    │       
     │                                                                                                                   │       
     │ 输出格式设计（两者兼顾：人类可读 + AI 可解析）：                                                                  │       
     │                                                                                                                   │       
     │ // src/output.rs - 评论输出格式                                                                                   │       
     │ use serde::{Deserialize, Serialize};                                                                              │       
     │                                                                                                                   │       
     │ #[derive(Debug, Serialize, Deserialize)]                                                                          │       
     │ pub struct ReviewOutput {                                                                                         │       
     │     pub session_id: String,                                                                                       │       
     │     pub review_type: String,                                                                                      │       
     │     pub target: String,                                                                                           │       
     │     pub created_at: String,                                                                                       │       
     │     pub completed_at: String,                                                                                     │       
     │     pub summary: ReviewSummary,                                                                                   │       
     │     pub comments: Vec<GroupedComment>,                                                                            │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ #[derive(Debug, Serialize, Deserialize)]                                                                          │       
     │ pub struct ReviewSummary {                                                                                        │       
     │     pub total_comments: usize,                                                                                    │       
     │     pub total_files: usize,                                                                                       │       
     │     pub files_with_comments: Vec<String>,                                                                         │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ #[derive(Debug, Serialize, Deserialize)]                                                                          │       
     │ pub struct GroupedComment {                                                                                       │       
     │     pub file: String,                                                                                             │       
     │     pub comments: Vec<FileComment>,                                                                               │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ #[derive(Debug, Serialize, Deserialize)]                                                                          │       
     │ pub struct FileComment {                                                                                          │       
     │     pub line: usize,                                                                                              │       
     │     pub text: String,                                                                                             │       
     │     pub context_before: Vec<String>,  // 上文 2 行                                                                │       
     │     pub context_after: Vec<String>,   // 下文 2 行                                                                │       
     │     pub created_at: String,                                                                                       │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ // 输出为 Markdown 格式（AI 友好 + 人类可读）                                                                     │       
     │ pub fn output_markdown(output: &ReviewOutput) -> String {                                                         │       
     │     let mut md = String::new();                                                                                   │       
     │                                                                                                                   │       
     │     // 顶部摘要（人类快速查看）                                                                                   │       
     │     md.push_str("╔════════════════════════════════════════════════════════════╗\n");                              │       
     │     md.push_str(&format!("║  Review Results: {} | {} comment(s) ║\n",                                             │       
     │         output.target, output.summary.total_comments));                                                           │       
     │     md.push_str("╚════════════════════════════════════════════════════════════╝\n\n");                            │       
     │                                                                                                                   │       
     │     // AI 结构化区域（用特殊标记）                                                                                │       
     │     md.push_str("<!-- HREV_AI_START -->\n");                                                                      │       
     │     md.push_str(&format!("## Review: {} ({})\n\n", output.target, output.review_type));                           │       
     │     md.push_str(&format!("**Files:** {}\n", output.summary.files_with_comments.join(", ")));                      │       
     │     md.push_str(&format!("**Comments:** {} total\n\n", output.summary.total_comments));                           │       
     │                                                                                                                   │       
     │     for group in &output.comments {                                                                               │       
     │         md.push_str(&format!("### File: {}\n\n", group.file));                                                    │       
     │                                                                                                                   │       
     │         for comment in &group.comments {                                                                          │       
     │             md.push_str(&format!("#### Line {}: {}\n\n",                                                          │       
     │                 comment.line, comment.text));                                                                     │       
     │                                                                                                                   │       
     │             // 上下文代码块                                                                                       │       
     │             md.push_str("```context\n");                                                                          │       
     │             for (i, line) in comment.context_before.iter().enumerate() {                                          │       
     │                 let line_num = comment.line as isize - comment.context_before.len() as isize + 1 + i as isize;    │       
     │                 md.push_str(&format!("{:>4} | {}\n", line_num, line));                                            │       
     │             }                                                                                                     │       
     │             md.push_str(&format!("{:>4} | >>> {}\n", comment.line, comment.text));                                │       
     │             for (i, line) in comment.context_after.iter().enumerate() {                                           │       
     │                 md.push_str(&format!("{:>4} | {}\n", comment.line + 1 + i, line));                                │       
     │             }                                                                                                     │       
     │             md.push_str("```\n\n");                                                                               │       
     │         }                                                                                                         │       
     │     }                                                                                                             │       
     │     md.push_str("<!-- HREV_AI_END -->\n\n");                                                                      │       
     │                                                                                                                   │       
     │     md                                                                                                            │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ // 输出为 JSON 格式（供程序解析）                                                                                 │       
     │ pub fn output_json(output: &ReviewOutput) -> String {                                                             │       
     │     serde_json::to_string_pretty(output).unwrap()                                                                 │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ // 终端输出（人类可读）                                                                                           │       
     │ pub fn print_terminal(output: &ReviewOutput) {                                                                    │       
     │     println!("╔════════════════════════════════════════════════════════════╗");                                   │       
     │     println!("║                    Code Review Complete                    ║");                                   │       
     │     println!("╚════════════════════════════════════════════════════════════╝");                                   │       
     │     println!();                                                                                                   │       
     │     println!("  Target: {} ({})", output.target, output.review_type);                                             │       
     │     println!("  Comments: {} across {} file(s)",                                                                  │       
     │         output.summary.total_colors,                                                                              │       
     │         output.summary.total_files);                                                                              │       
     │     println!();                                                                                                   │       
     │                                                                                                                   │       
     │     for group in &output.comments {                                                                               │       
     │         println!("  📄 {}", group.file);                                                                          │       
     │         for comment in &group.comments {                                                                          │       
     │             println!("    [L{}] {}", comment.line,                                                                │       
     │                 comment.text.chars().take(50).collect::<String>());                                               │       
     │         }                                                                                                         │       
     │         println!();                                                                                               │       
     │     }                                                                                                             │       
     │                                                                                                                   │       
     │     println!("  💾 Full results saved to:");                                                                      │       
     │     println!("     .hrev/sessions/{}/review.json", output.session_id);                                            │       
     │     println!("     .hrev/sessions/{}/review.md", output.session_id);                                              │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 带上下文的评论收集：                                                                                              │       
     │                                                                                                                   │       
     │ // src/git_ops.rs - 获取上下文                                                                                    │       
     │ impl GitOps {                                                                                                     │       
     │     /// 获取指定行的上下文                                                                                        │       
     │     pub fn get_line_context(                                                                                      │       
     │         &self,                                                                                                    │       
     │         file_path: &str,                                                                                          │       
     │         line: usize,                                                                                              │       
     │         before: usize,                                                                                            │       
     │         after: usize,                                                                                             │       
     │     ) -> Result<(Vec<String>, Vec<String>)> {                                                                     │       
     │         let content = self.get_file_content(file_path, None)?;                                                    │       
     │         let lines: Vec<&str> = content.lines().collect();                                                         │       
     │                                                                                                                   │       
     │         let start = if line > before { line - before - 1 } else { 0 };                                            │       
     │         let end = (line + after).min(lines.len());                                                                │       
     │                                                                                                                   │       
     │         let context_before = lines[start..line].to_vec();                                                         │       
     │         let context_after = lines[line..end].to_vec();                                                            │       
     │                                                                                                                   │       
     │         Ok((                                                                                                      │       
     │             context_before.into_iter().map(|s| s.to_string()).collect(),                                          │       
     │             context_after.into_iter().map(|s| s.to_string()).collect(),                                           │       
     │         ))                                                                                                        │       
     │     }                                                                                                             │       
     │ }                                                                                                                 │       
     │                                                                                                                   │       
     │ 10. 验证计划                                                                                                      │       
     │                                                                                                                   │       
     │ 完成后的测试流程：                                                                                                │       
     │ 1. cargo build --release - 编译二进制                                                                             │       
     │ 2. ./target/release/hrev diff - 测试 diff 模式                                                                    │       
     │ 3. ./target/release/hrev plan.md - 测试文件模式                                                                   │       
     │ 4. ./target/release/hrev abc1234 - 测试 commit 模式                                                               │       
     │ 5. 在浏览器中添加评论，验证保存和导出                                                                             │       
     │ 6. 检查 .hrev/sessions/ 目录查看输出文件                                                                          │       
     │ 7. 测试 Claude Code skill 集成                                                                                    │       
     │                                                                                                                   │       
     │ ---                                                                                                               │       
     ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯