quangdang46/why

GitHub: quangdang46/why

结合 Git 历史与大语言模型,为开发者解释代码存在原因并评估删除风险,从而实现更安全的代码重构。

Stars: 0 | Forks: 0

# why ## 快速开始 1. 安装 `why`。 2. 运行设置命令: ``` why config init ``` 3. 提问一个关于代码历史的问题: ``` why src/auth.rs:verify_token ``` 如果你只想记住一个设置命令,请记住 `why config init`。 这是主要的设置流程,允许你选择 `anthropic`、`openai`、`zai` 或 `custom`。 ## 安装 ### 安装已发布的二进制文件 ``` curl -fsSL "https://raw.githubusercontent.com/quangdang46/why/main/install.sh?$(date +%s)" | bash ``` ### 从此检出目录在本地构建 ``` cargo run -q -p why-core -- --help cargo build -p why-core --release ./target/release/why --help ``` 发布的 Cargo 包是 `why-core`,安装后的二进制文件名称为 `why`。 目前**不支持**使用 `cargo install why-core` 进行安装,因为 `crates/core/Cargo.toml` 仍然设置了 `publish = false`。 ### 生成 Shell 补全和 Man 手册 ``` cargo run -q -p why-core -- completions bash > why.bash cargo run -q -p why-core -- completions zsh > _why cargo run -q -p why-core -- completions fish > why.fish cargo run -q -p why-core -- manpage > why.1 ``` ### 配置 CLI 如果你跳过了上面的快速开始步骤,请运行: ``` why config init ``` 这是主要的设置流程,允许你交互式地选择 `anthropic`、`openai`、`zai` 或 `custom`。 ### 验证和基准测试 ``` cargo fmt --all -- --check cargo clippy --workspace --all-targets --all-features -- -D warnings cargo test --workspace --all-features ``` 选择性加入的真实仓库 CLI 覆盖率: ``` WHY_REAL_REPO_PATH=/absolute/path/to/git/checkout \ cargo test -p why-workspace --test integration_real_repo_cli -- --nocapture ``` - 真实仓库测试是选择性加入的,因此默认的测试套件保持确定性。 - 它们在运行 `why` 之前,将提供的代码检出克隆到一个临时的测试仓库中。 基准测试命令: ``` cargo bench --package why-workspace --bench cache_bench cargo bench --package why-workspace --bench archaeology_bench cargo bench --package why-workspace --bench scanner_bench ``` GitHub Actions 也通过 `.github/workflows/bench.yml` 暴露了相同的 Criterion 运行,并将 `target/criterion/**` 作为构件上传。 ## 问题所在 Claude Code 无法访问 git 历史。它只能看到代码现在的样子——而没有任何关于以下内容的上下文: - 为什么代码会这样写? - 这是不是针对某个突发事件的热修复? - 这是不是 2019 年的“临时”代码后来变成了永久代码? - 如果我删除这段代码会破坏什么? 开发者经常会删除看起来已经废弃、但实际上是关键安全修复的代码。 Claude Code 也会做同样的事情——因为它无法读取过去的历史。 ## 为什么 `why` 与众不同 | | 仅 Claude Code | why | |---|---|---| | 理解 git 历史 | ❌ | ✅ | | 读取 PR 描述 | ❌ | ✅ | | 解释提交原因 | ❌ | ✅ | | 删除前的风险评估 | ❌ | ✅ | | 将代码与过去的事件关联 | ❌ | ✅ | **这是本套件中 Claude Code 在字面意义上无法复制的唯一工具**——因为 git 历史不会出现在任何上下文窗口中。 ## 工作原理 ``` 1. Identify (pure Rust, git2 crate) └── tree-sitter locates exact byte range of target function/line └── git2 runs git blame on that range └── collect all unique commits that touched those lines 2. Gather (pure Rust, git2 crate) └── for each commit: message, author, date, diff └── check for PR refs (#123), issue refs (fixes #456) └── extract comments and TODOs near the target code 3. Synthesize (LLM call — configured provider) └── feed structured git data to the configured model └── ask: "why does this exist? what risk if removed?" └── returns human-readable explanation + risk level ``` 每次查询只有**一次 LLM 调用**,并将结构化的 git 数据作为输入。 ## 技术栈 | Crate | 用途 | |---|---| | `git2` | 原生 git 操作——无需 git 二进制文件 | | `tree-sitter` | 精确定位函数边界 | | 感知 provider 的 HTTP 客户端 | 通过 Anthropic 或兼容 OpenAI 的 provider 将 git 数据合成为解释 | | `clap` | CLI | | `serde_json` | 结构化输出 | ## 使用方法 ### 查询目标 `why` 使用位置目标语法,而不是 `fn|file|line` 子命令。 支持的查询形式: - `why :` - `why :` - `why :` - `why --lines ` 支持符号解析的语言: - Rust (`.rs`) - Go (`.go`) - JavaScript (`.js`) - TypeScript (`.ts`, `.tsx`) - Java (`.java`) - Python (`.py`) 重要规则: - 行号从 1 开始(基于 1 的索引)。 - `--lines` 必须使用 `START:END` 格式。 - 不要将 `--lines` 与 `:` 或 `:` 混合使用。 - 除非与 `--lines` 配对使用,否则裸文件路径不是有效的查询。 ### 常见查询示例 ``` # 为何编写这一特定行? why src/auth.rs:42 # 为什么存在此行范围? why src/auth.rs --lines 40:45 --no-llm # 为什么存在此符号? why src/auth.rs:verify_token # 针对 Rust impl 方法的限定符号查询 why src/auth.rs:AuthService::login --team # Machine-readable archaeology 输出 why src/auth.rs:verify_token --json # 将 archaeology 限制在最近的 commits why src/auth.rs:verify_token --since 30 # 检查在历史上与此 target 共变的文件 why src/auth.rs:verify_token --coupled # 显示可能的 owners 和 bus-factor 信号 why src/auth.rs:verify_token --team # 遍历过去的机械编辑,找到可能的真实原始 commit why src/auth.rs:verify_token --blame-chain # 显示感知重命名的 target 演变历史 why src/auth.rs:verify_token --evolution # 询问是否应拆分某个 symbol why src/auth.rs:verify_token --split # 在 target 上方编写有证据支撑的 annotation why src/auth.rs:verify_token --annotate # 当文件更改时刷新报告 why src/auth.rs:verify_token --watch --no-llm # 审查重命名 Rust symbol 是否安全 why src/auth.rs:verify_token --rename-safe ``` ### 查询标志 | 标志 | 用途 | 备注 | |---|---|---| | `--json` | 输出机器可读的结果 | 适用于主查询流程和许多子命令 | | `--no-llm` | 跳过 LLM 合成 | 适用于 CI、本地验证或无密钥环境 | | `--no-cache` | 绕过缓存结果 | 强制进行全新查询,而不是重用 `.why/cache.jsonl` | | `--since ` | 将历史限制在最近的提交中 | 适用于查询式考古/报告模式 | | `--coupled` | 显示文件级别的共同变更耦合 | 适合在大型重构之前使用 | | `--team` | 显示所有权和巴士因子信号 | 适合在选择审查人之前使用 | | `--blame-chain` | 跳过可能的机械提交 | 帮助查找某行或符号的真正来源 | | `--evolution` | 显示感知重命名的目标历史 | 时间线样式的输出 | | `--split` | 建议是否应拆分某个符号 | 面向符号的查询模式 | | `--annotate` | 在目标上方插入简短且有证据支撑的文档注解 | 这会修改文件 | | `--watch` | 在文件更改时重新运行默认报告 | 需要交互式终端 | | `--rename-safe` | 显示目标风险以及重命名分析的调用者风险 | 目前仅支持 Rust 符号目标 | ### 全仓库范围和审查命令 大多数报告风格的子命令也支持 `--json`。 ``` # 按变动次数 × 启发式风险对 repository 热点进行排名 why hotspots --limit 10 # Repository 健康状况摘要 why health why health --ci 80 # 从 staged diff 生成对 reviewer 友好的 PR 模板 why pr-template # 结合 archaeology 支持的发现来审查 staged diff why diff-review --no-llm why diff-review --post-github-comment --github-ref '#42' # 对 incident 窗口内的可疑 commits 进行排名 why explain-outage --from 2025-11-03T14:00 --to 2025-11-03T16:30 # 将高风险 functions 与 coverage 数据进行交叉引用 why coverage-gap --coverage lcov.info # 查找在静态分析下显示为未调用的高风险 functions why ghost --limit 10 # 对新工程师应首先了解的 symbols 进行排名 why onboard --limit 10 # 查找过期的 TODOs、HACK/TEMP 标记以及已失效的 remove-after 日期 why time-bombs --age-days 180 ``` 关键行为说明: - `why pr-template` 读取的是**已暂存的 diff**,而不是未暂存的更改。 - `why diff-review` 同样读取**已暂存的 diff**。 - `why diff-review --post-github-comment` 需要一个有效的 GitHub 引用(例如 `#42`)以及已配置的 GitHub 远程仓库/token 路径。 - `why ghost` 使用启发式静态分析,并在终端输出中对此发出警告。 - `why health --ci ` 在债务分数超过阈值时,以退出码 `3` 退出。 - `why health` 回归门禁在配置的回归预算失败时,以退出码 `4` 退出。 ### 集成与开发者命令 ``` # 运行 MCP stdio server why mcp # 通过 stdio 运行聚焦 hover 的 LSP server why lsp # 启动交互式 archaeology shell why shell # 为受支持的 AI 工具输出 shell wrappers why context-inject # 安装或移除受管理的 git hooks why install-hooks --warn-only why uninstall-hooks # 生成 shell completions 或 man page why completions bash > why.bash why completions zsh > _why why completions fish > why.fish why manpage > why.1 ``` 更多细节: - `why shell` 启动一个带有索引补全支持的交互式 shell。 - 除非你自己显式传递 `--no-llm`,否则 shell 查询默认使用 `--no-llm`。 - 内置的 shell 命令包括 `help`、`reload`、`hotspots`、`health`、`ghost`、`exit` 和 `quit`。 - `why lsp` 是一个面向悬停提示的 LSP 服务器,返回 Markdown 悬停内容和完整报告的 CLI 提示。 - `why context-inject` 输出的 shell 代码旨在按以下方式使用: eval "$(why context-inject)" 生成的包装器目前针对受支持的提示工具,例如 `claude`、`sgpt` 和 `llm`。 ### 历史上的 Node 原型 在 `poc/` 目录下仍然存在一个 Node.js 原型,但它**不是**正式发布接口。 诸如此类的示例仅属于原型阶段: ``` node poc/index.js fn verifyToken src/auth.js node poc/index.js file src/legacy/payment_v1.js node poc/index.js fn verifyToken src/auth.js --raw ``` 上面文档中描述的 Rust CLI 是当前工具受支持的正式接口。 ## 输出示例 ``` $ why src/auth.rs:42 why: src/auth.rs (line 42) Commits touching this line: a3f9b2c alice 2024-01-12 fix: tokens not expiring on logout 8d2e1f4 bob 2022-09-04 extend auth flow for refresh token handling No LLM synthesis (--no-llm or no API key). Heuristic risk: MEDIUM. ``` ## 风险语义和解释风格 `why` 应该让保守的更改决策变得更容易,而不是听起来比证据所支持的更有把握。 ### 风险级别 - **HIGH** —— 代码显示出安全敏感性、事件历史、关键的后向兼容行为,或其他表明删除可能会以非同寻常的方式破坏生产环境行为的信号。将此视为停止并调查的信号:在未经深入审查之前,不要删除或进行重大重构。 - **MEDIUM** —— 代码似乎与迁移、重试、遗留路径或过渡行为相关,更改可能是安全的,但前提是要理解周围的上下文。 - **LOW** —— 可用的历史记录和附近的代码没有显示出特殊的运维或兼容性压力。除非出现更强有力的证据,否则这是普通的工具代码。 ### 解释风格规则 - 将**证据**与**推断**分开。提交消息、注释和代码标记是证据;从中得出的结论是推断。 - 当历史记录稀少、嘈杂或模棱两可时,明确说明**未知因素**。 - 不要凭空捏造证据中不存在的突发事件、PR 上下文或依赖关系。 - 保持输出易于浏览:先是简洁的总结,然后是支持性历史,最后是风险。 - 当只有 1-2 个提交或弱信号可用时,向下校准置信度。 ### 置信度指南 `why` 在内部将置信度建模为一个枚举,并将其序列化为以下 JSON/字符串值之一: - **low** —— 历史记录单薄、提交消息薄弱,或几乎没有确凿的上下文。 - **medium** —— 有一些有用的历史信号,但直接证据有限。 - **medium-high** —— 历史意图明确,如热修复、突发事件或兼容性轨迹。 - **high** —— 多个确凿的来源指向相同的解释。 ## 与 Claude Code 集成 添加到你项目的 `CLAUDE.md` 中: ``` ## 自定义工具 - `why :` — explain why a specific line was written - `why --lines ` — explain why a line range exists - `why :` — explain why a supported symbol exists - `why : --coupled` — inspect co-change dependencies before a deeper refactor - `why : --team` — identify likely owners before asking for review on risky code - `why : --blame-chain` — skip mechanical edits to find the real origin commit - `why : --evolution` — inspect rename-aware target history before large moves - `why diff-review --no-llm` — review the staged diff before opening a PR - `why health --json` — export a machine-readable repo health snapshot **Always run `why` before deleting or significantly refactoring any function that exists in git history for more than 6 months.** ``` 推荐的 Claude Code 工作流: 1. 在删除或重写不熟悉的代码之前,先在确切的符号或行范围上运行 `why`。 2. 如果报告的风险是 **HIGH**,请将其视为停止并调查的信号,而不是快速继续的建议。 3. 对于大型重构,还应根据你需要了解的内容运行 `--coupled`、`--team`、`--blame-chain` 或 `--evolution`。 4. 在发起 PR 之前,对已暂存的 diff 运行 `why diff-review`。 5. 对于编辑器/工具集成,请选择与你工作流匹配的接口: - `why mcp` 用于支持 MCP 的编辑器 - `why lsp` 用于面向悬停提示的编辑器集成 - `eval "$(why context-inject)"` 用于 shell 包装的提示工具 推荐的代码审查例行程序: - 在提议删除看起来陈旧的代码时,包含 `why ... --json` 或终端摘要 - 当更改涉及运维敏感路径并且你需要找到最佳审查人时,使用 `why ... --team` - 在拆分或重新定位历史上噪音较大的函数之前,使用 `why ... --coupled` - 在共享分支之前,使用 `why diff-review` 总结暂存更改的风险 有关特定于 MCP 的设置示例,请参阅 `docs/mcp-setup.md`。 ## 配置和凭证 `why` 支持分层配置: 1. 内置默认值 2. 位于 `$XDG_CONFIG_HOME/why/why.toml` 或 `~/.config/why/why.toml` 的全局配置 3. 仓库本地的 `why.local.toml` 使用 CLI 管理这些层: ``` # Global config 为默认 target why config init --provider anthropic --model claude-haiku-4-5-20251001 # 使用 --local 进行针对 repo 的覆盖 why config init --local --provider zai --model glm-5 why config init --local --provider custom --model local-model --base-url https://api.example.com/v1/chat/completions # 检查有效的合并 config,而不打印 secrets why config get why config get --json ``` 如果你在交互式终端中运行 `why config init`,而没有通过标志传递值,CLI 会提示你选择 provider、model、base URL、auth token、retries、max tokens 和 timeout。你可以将值留空以保留当前值或 provider 默认值,之后再编辑 `why.toml` 或 `why.local.toml`。 支持的 provider: - `anthropic` - `openai` - `zai` - `custom` (兼容 OpenAI) 当前内置的默认值: - `anthropic` → 模型 `claude-haiku-4-5-20251001`,基础 URL `https://api.anthropic.com/v1/messages` - `openai` → 模型 `gpt-5.4`,基础 URL `https://api.openai.com/v1/chat/completions` - `zai` → 模型 `glm-5`,基础 URL `https://api.z.ai/api/anthropic/v1/messages` - `custom` → 无内置模型或基础 URL `why config get` 会隐藏机密,并通过 `llm.auth_configured` 报告认证是否已配置。 环境变量优先于配置值。空值将被忽略。 Provider 凭证环境变量: ``` export ANTHROPIC_API_KEY=your_anthropic_api_key_here export OPENAI_API_KEY=your_openai_api_key_here export ZAI_API_KEY=your_zai_api_key_here export CUSTOM_API_KEY=your_custom_api_key_here ``` 配置示例: ``` [risk] default_level = "LOW" [risk.keywords] high = ["pci", "reconciliation"] medium = ["terraform", "webhook", "idempotency"] [git] max_commits = 8 recency_window_days = 90 mechanical_threshold_files = 50 coupling_scan_commits = 500 coupling_ratio_threshold = 0.30 [cache] max_entries = 500 [llm] provider = "openai" model = "gpt-5.4" base_url = "https://api.openai.com/v1/chat/completions" auth_token = "your_provider_token_here" retries = 3 max_tokens = 500 timeout = 30 [github] remote = "origin" # token = "ghp_..." # 可选的回退方案;优先使用 GITHUB_TOKEN env var ``` `[risk.keywords]` 使用特定于团队或领域的术语扩展内置的启发式词汇表。匹配不区分大小写,并且可以影响排序后的证据相关性和启发式风险级别。 对于 GitHub 丰富化工作,请在环境可用时设置 `GITHUB_TOKEN`;配置也可以包含可选的 `[github]` 回退 token 和远程名称。环境变量优先于配置,空值将被忽略。 机密处理指南: - 尽可能首选环境变量 - 如果你选择的话,全局配置对于本地开发是可以接受的 - 仓库本地的 `why.local.toml` 通常应避免包含机密,因为它更容易被意外提交 有关当前配置表面的完整记录示例,请参阅 `.why.toml.example`。 ## 缓存和 `.why/` 目录语义 当前行为: - 查询结果缓存在仓库根目录的 `.why/cache.jsonl` 中,每行一个 JSON 对象 - 缓存键包括目标以及当前的 `HEAD` hash 前缀,因此更改历史会自然使先前的条目失效 - 当重用存储的 `WhyReport` 时,终端输出会显示 `[cached]` - `--no-cache` 绕过缓存读取并强制进行全新查询 - `[cache].max_entries` 控制 `.why/cache.jsonl` 中保留的查询报告数量 - 滚动的健康快照单独存储在 `.why/health.jsonl` 中,每行一个 JSON 对象 - 最多保留 52 个健康快照 - CI 可以使用 `.github/health-baseline.json` 强制执行健康回归预算 运维期望: - 将 `.why/` 视为本地运行时状态,而不是受源代码控制的项目状态 - 在正常的开发工作流中,`.why/` 应被 git 忽略 - 在 Unix 上,缓存目录和运行时文件以仅限所有者的权限写入(`.why/` 为 `0700`,`cache.jsonl`、`health.jsonl` 和 `runtime.log` 为 `0600`) - 如果你想清除本地缓存结果,删除 `.why/cache.jsonl` 是安全的;`why` 会在下次缓存运行时重新创建它 - 如果你想重置本地健康趋势历史,删除 `.why/health.jsonl` 是安全的 - 当合成失败并且 `why` 回退到启发式模式时,LLM 回退原因会附加到 `.why/runtime.log` 中 ## `why doctor` 使用 `why doctor` 验证当前的有效设置,并执行一次小型的实时 LLM 测试调用。 ``` why doctor why doctor --json ``` 它报告: - 有效配置路径和已解析的 LLM 设置, - 认证是否已配置, - LLM 客户端是否可以初始化, - 实时 LLM 调用是否成功。 如果实时调用失败,`why doctor` 会直接报告错误,并且运行时日志仍可在 `.why/runtime.log` 中找到。 ## 索引位置 没有持久化索引——`why` 按需读取 git 历史。 对于交互式使用足够快(每次查询约 1-3 秒)。 ### 健康回归门禁 将 `why health` 与已提交的基线结合使用,可以在任何债务分数或信号回归时报错失败: ``` cargo run -p why-core --bin why -- health \ --baseline-file .github/health-baseline.json \ --require-baseline \ --max-regression 0 \ --max-signal-regression time_bombs=0 \ --max-signal-regression high_risk_files=0 \ --max-signal-regression hotspot_files=0 \ --max-signal-regression stale_hacks=0 ``` 在已知的良好主线变更发生后,通过重新运行来有意更新 `.github/health-baseline.json`: ``` cargo run -p why-core --bin why -- health --json --write-baseline .github/health-baseline.json ``` 退出码摘要: - `0` —— 检查通过 - `3` —— CI 阈值失败 (`--ci`) - `4` —— 回归门禁失败 (`--max-regression` / `--max-signal-regression`) ## 路线图 - [ ] GitHub/GitLab PR 标题 + 描述集成(通过 API) - [ ] 从提交消息解析 Jira/Linear 工单 - [ ] `why --since ` 用于最近的变更上下文 - [ ] 团队归咎 —— 谁最了解这段代码? - [ ] 支持悬停时内联显示 `why` 的 VS Code 扩展
标签:AI编程助手, Anthropic, C2, Cargo, CIS基准, CLI, DevTools, DLL 劫持, Git Blame, Git历史, LLM, OpenAI, Rust, Unmanaged PE, WiFi技术, 云安全监控, 代码分析, 代码审查, 代码理解, 代码重构, 内存规避, 凭证管理, 可视化界面, 大语言模型, 威胁情报, 安全删除, 安全编码, 开发者工具, 源代码管理, 网络流量审计, 通知系统, 静态分析