rdzehtsiar/hotpath
GitHub: rdzehtsiar/hotpath
一款离线本地优先的代码库智能分析工具,通过扫描 Go 源码和 Git 历史识别高风险、高复杂度的代码热点区域。
Stars: 1 | Forks: 0
# Hotpath
[](https://github.com/rdzehtsiar/hotpath/actions/workflows/tests.yml)
[](https://codecov.io/gh/rdzehtsiar/hotpath)
[](https://sonarcloud.io/summary/new_code?id=rdzehtsiar_hotpath)
Hotpath 是一款处于早期开发阶段的离线、本地优先的代码库智能工具。它的长远目标是帮助工程师识别代码库中存在风险、高昂成本、不稳定、臃肿或架构漂移的区域,且无需上传源代码或依赖托管服务。
目前的实现范围远小于该产品方向。本 README 仅记录当前代码库中已有的功能。
## 快速开始
```
# 索引当前 repository
hotpath scan
# 显示生产环境中的主要 Go 文件 hotspots
hotpath hotspots
# 解释单个文件
hotpath explain internal/service/service.go
# 输出 machine-readable JSON 摘要以替代 terminal output
hotpath scan --json
# 删除并完全重建本地 index
Remove-Item -Recurse -Force .hotpath; hotpath scan
```
评分是**实验性的参考信号**,并非客观事实。置信度和校准程度会因代码库大小和 Git 历史结构而异。如果 Git 置信度为 `bounded` 或 `first_parent_only`,则 churn 和 age 指标仅反映收集窗口内可达的首级父级历史。
## 当前 CLI
CLI 目前提供以下子命令:
```
hotpath scan
hotpath explain
hotpath hotspots
```
### `hotpath scan`
`hotpath scan` 在当前工作目录下运行。它会:
- 根据本地忽略规则枚举代码库文件
- 读取文件元数据和有界的内容窗口
- 对基本的内容类型、生成路径和 vendor 路径进行分类
- 解析受支持的 Go 文件
- 尽可能计算由 Go 派生的近似源码指标
- 当目录是非浅层的 Git 工作树时,收集有界的本地 Git 历史
- 将派生的本地数据持久化到 Hotpath index 中
- 输出文件和 Git 处理的终端进度
`hotpath scan --json` 会将精简的 JSON 扫描摘要写入 stdout,且不输出终端进度。除了显式版本化的命令 JSON 外,目前没有 CI 门禁或稳定的报告 schema。
v1 schema 字段包括:
| 字段 | 类型 | 描述 |
| --- | --- | --- |
| `schema_version` | integer | Schema 版本标识。目前为 `1`。 |
| `command` | string | 始终为 `"scan"`。 |
| `files.detected` | integer | 枚举的代码库文件总数(应用忽略规则后)。 |
| `files.analyzed` | integer | 完成分析的文件数。 |
| `git.skipped` | boolean | 当未尝试进行 Git 分析时为 `true`。 |
| `git.mode` | string 或 null | 收集模式:`"full"`、`"incremental"` 或 `"up_to_date"`。跳过时为 Null。 |
| `git.confidence` | string 或 null | 参考置信度信号:`"full"`、`"bounded"`、`"incremental"`、`"first_parent_only"`、`"shallow_skipped"`、`"not_git"` 或 `"error_skipped"`。跳过时为 Null。 |
| `git.commits_total` | integer 或 null | Git 可用的可达提交总数。跳过时为 Null。 |
| `git.commits_processed` | integer | 本次扫描中实际处理的提交数。跳过或复用时为 `0`。 |
| `git.diagnostic` | string 或 null | 当记录到 Git 限制时的诊断代码(无消息文本),例如 `"merge_heavy"`。无限制时为 Null。 |
| `git.index_action` | string 或 null | Git index 的处理方式:`"fully_rebuilt"`、`"incrementally_updated"`、`"reused"`、`"cleared_not_git"` 或 `"cleared_options_changed"`。跳过时为 Null。 |
| `index.records_stored` | integer | 写入本地 index 的数据库记录总数。 |
### `hotpath hotspots`
`hotpath hotspots` 读取最近的现有 `.hotpath/index.sqlite`,并从最近完成的扫描中输出排名靠前的生产级 Go 文件热点。默认视图排除了测试文件。
使用 `hotpath hotspots --tests` 可输出 Go 测试文件热点。测试热点评分使用相同的参考公式,但它们反映的是测试维护压力,不应将其解读为生产环境运行时风险。
### `hotpath explain `
`hotpath explain ` 读取最近的现有 `.hotpath/index.sqlite` 并输出单个文件的已索引上下文。它是只读的:不会刷新 index、重新运行分析或修改 `.hotpath/`。如果 index 缺失、过期或不包含请求的文件,请先运行 `hotpath scan`。
路径可以是相对于代码库的路径,也可以是已索引代码库根目录下的绝对路径:
```
hotpath explain internal/service/service.go
```
文本输出包括文件事实、原始指标、标准化得分项、权重、得分事实、限制、解析器诊断、所有权记录、Git 收集上下文、co-change 伙伴以及源码耦合上下文。JSON 输出使用版本化的 `hotpath.explain.v1` schema 以便自动化处理。
已扫描但未评分的文件仍会解释其已索引的事实并返回成功。其得分会被报告为不可用,原因可能是不受支持的语言、生成/vendor 排除、缺少解析器指标或缺少评分行。
## 本地 Index
扫描输出将作为派生的本地缓存数据持久化至:
```
.hotpath/index.sqlite
```
index 是本地工作数据,而非稳定的公共数据库格式。它可能包含相对于代码库的路径、文件元数据、解析器派生的 Go 事实、Git 指标、源码依赖记录,以及 Go 文件、package 和项目风险评分行。它可以被删除并从本地代码库数据中重新构建。
请勿提交 `.hotpath/`。
## 语言支持
目前仅支持对 Go 进行感知语言的处理。
Hotpath 仍会将其他语言的文件作为普通文件进行扫描,但默认分析器仅注册了 Go 解析器。Rust、TypeScript、TSX、Python 和其他语言文件不会被当前的默认 pipeline 解析,也不会获得派生的语言指标或 Go 文件风险评分。
## Go 处理限制
当前的 Go 处理是有意限制的:
- Go 识别基于扩展名:仅将路径以 `.go` 结尾的文件视为 Go 文件。
- 以 `_test.go` 结尾的 Go 文件在本地 index 中被标记为测试文件,以便将其 churn、大小和复杂度与生产源码文件分开解读。
- Go 文件必须可作为 UTF-8 文本读取。
- 大于活动内容窗口的文件不会被解析。默认内容窗口为 1 MiB。
- 被截断的文本文件不会从扫描器获得行数统计。
- 二进制文件和无效的 UTF-8 文件不会被解析为 Go。
- 解析器基于 tree-sitter,在从恢复的语法树中收集部分事实时,可能会发出解析错误诊断。
- 提取的 Go 符号目前是精简的事实,而非完整的语义模型。解析器记录顶级函数、方法、类型规范、struct、interface 和 import。
- 符号输出目前不包含源码范围、签名、接收者详细信息、package 文档、注释、调用点或完整的类型信息。
- Import 提取记录字符串字面量的 import 目标。
- `source_coupling_pressure` 派生自已解析的本地 Go import 边。它是一个方向性的协调风险信号,而不是完整的依赖图、构建图、运行时图或调用图。
- Go 源码依赖解析是基于 package 路径的保守策略。存在本地 `go.mod` 模块前缀时,它会将其作为已知 package 的前缀和活跃 Go 文件目录使用。外部 import 以及无法匹配到已知本地 package 的 import 将保持未解析状态。
- `complexity_pressure` 派生自已解析的 Go 控制流语法。它是一个用于热点排名的信号,不是严格正确的 cyclomatic complexity 实现,也不是 Go 执行语义的完整模型。
- Go 文件风险评分目前仅限于语言为 `go` 的活跃记录。
- 默认的热点、package 风险和项目风险输出以生产环境为核心:非生成、非 vendor 的 `_test.go` 文件仍会被评分和索引,但从生产项目和 package 风险中排除。使用特定的测试热点输出可单独检查它们。
- Go package 风险是对同一个代码库相对目录中已评分的生产级 Go 文件的近似聚合。它使用从文件位置派生的 package 路径,而不是完整的 Go 构建元数据。
- 生成的 Go 文件和 vendor Go 文件默认排除在 Go 文件风险评分之外,因此生成的 churn 或 vendor 代码不会主导热点排名。它们的生成/vendor 标志仍保留在文件事实中,以供检查。
- 项目风险仅感知 Go。包含很少或没有 Go 代码的代码库会得到较低或不可用的评分覆盖率,而不是广泛的代码库风险分析。
## Go 指标公式
以下 Go 指标公式描述了当前 `hotpath.score.go.v1` 的输入。它们是**实验性的**:公式权重、标准化阈值和术语选择会随着工具的成熟而变化。请将评分视为用于热点排名的近似的、由解析器支持的参考信号——而不是 Go 语言规范、稳定的公共 API 或客观的代码质量衡量标准。所有标准化值都被限制在 `0.0..=1.0` 范围内。
### 近似认知复杂度
Hotpath 在提取每个解析过的顶级函数或方法体后,根据 tree-sitter 语法树计算 Go 认知复杂度。文件认知复杂度是每个函数复杂度的总和。最大函数复杂度是该文件中最大的单个函数复杂度值。
对于当前的 Go 解析器,这些语法结构会增加复杂度:
| 解析器匹配的 Go 语法 | 指标类型 | 增加的分数 | 嵌套效应 |
| --- | --- | --- | --- |
| `if` 语句 | branch | `1 + current_nesting` | 子控制流嵌套深度增加一级。 |
| `for` 语句,包括 range 循环 | loop | `1 + current_nesting` | 子控制流嵌套深度增加一级。 |
| 表达式 switch、类型 switch 和 `select` 语句 | switch | `1 + current_nesting` | 子控制流嵌套深度增加一级。 |
| 表达式 case、类型 case、通信 case 和 default case | case | `1 + current_nesting` | 子控制流嵌套深度增加一级。 |
| 源文本包含 `&&` 或 `||` 的二元表达式 | boolean chain | `1` | 子控制流保持相同的嵌套级别。 |
| `break`、`continue` 和 `goto` 语句 | jump | `1` | 不添加子嵌套。 |
如果解析器发出该指标类型,通用复杂度缩减器可以将 `else if` 节点评分为 `1 + current_nesting`,但当前的 Go 解析器不会发出单独的 `else if` 类型。它会遍历恢复的语法树并对其找到的嵌套 `if` 语句进行评分。`return`、`defer`、`go`、`fallthrough`、调用、不包含 `&&` 或 `||` 的布尔表达式以及 package 初始化顺序本身目前不会增加复杂度。
文件风险公式使用的标准化认知复杂度得分为:
```
file_complexity_score = min(file_cognitive_complexity / 150, 1.0)
function_complexity_score = min(max_function_complexity / 30, 1.0)
normalized_cognitive_complexity = max(file_complexity_score, function_complexity_score)
```
如果两个由解析器支持的复杂度值均不可用,则该文件的风险贡献中将忽略复杂度项,并记录 `missing_cognitive_complexity` 限制。如果其中一个值不可用,则将其视为零,同时对另一个值进行标准化。
### 近似源码耦合
源码耦合派生自解析为已知本地 Go package 路径的本地 Go import 引用。解析器记录字符串字面量的 import 目标。在 index 完成期间,Hotpath 将每个活跃的 Go 文件目录视为已知的 package 路径;代码库根目录中的文件使用 `.` 作为其 package 路径。package 路径来自文件位置,而不是 Go package 声明。
本地 import 解析是保守且确定性的:
1. 存在时,从代码库根目录的 `go.mod` 中读取第一个非空的 `module ...` 指令。
2. 对于每个 Go import 目标,去除周围的空白字符。
3. 如果 import 完全等于 module 前缀,则尝试将其解析为 `.`。
4. 如果 import 以 `module_prefix/` 开头,则去除该前缀并尝试剩余路径。
5. 还会在去除前导/尾随斜杠并将反斜杠标准化为 `/` 之后,按原样尝试 import 目标。
6. 解析为与活跃 Go 文件目录匹配的第一个候选对象。
不匹配已知本地 package 的 import 将保持未解析状态。
当前的源码耦合计算不使用外部 package、生成的构建图、仅测试变体、构建标签、工作区文件、vendor module 语义、package 声明、类型信息、调用点和运行时行为。
对于每个已解析的本地 import 边,Hotpath 会存储源文件路径、源文件的 package 路径和目标 package 路径。来自同一源文件且指向同一目标 package 和引用类型的重复边会被折叠。
具体化的耦合值为:
```
source_coupling_in = count(distinct source files importing this file's package path)
source_coupling_out = count(distinct resolved target package paths imported by this file)
```
入站耦合是 package 级别的然后附加到目标 package 中的每个文件。出站耦合是文件级别的。文件风险公式使用的标准化源码耦合得分为:
```
inbound_score = min(source_coupling_in / 25, 1.0)
outbound_score = min(source_coupling_out / 15, 1.0)
normalized_source_coupling = max(inbound_score, outbound_score)
```
评分说明中显示的原始源码耦合项是 `source_coupling_in` 和 `source_coupling_out` 中的较大值,而标准化项使用上述单独的入站和出站阈值。
## Git 处理限制
当前的 Git 处理也有一定限制:
- Git 处理依赖于本地 `git` 可执行文件。
- 非 Git 目录会扫描文件,但 Git 历史被标记为不可用。
- 浅层代码库会跳过 Git 派生分析。
- 默认的 Git 历史扫描最多限制为 50,000 次提交。
- 默认的 Git 历史扫描限制为相对于 `HEAD` 提交者时间戳的过去 730 天内的提交。
- 扫描输出和本地 index 会公开当前的 Git 收集边界:`max_commits`、`max_age_days`、`first_parent`、重命名检测、co-change 文件限制以及近期 churn 参考窗口。
- Git 置信度元数据仅供参考,可能是 `bounded`、`full`、`incremental`、`first_parent_only`、`shallow_skipped`、`not_git` 或 `error_skipped`。
- 增量 Git 扫描在先前已索引的 `HEAD` 是当前 `HEAD` 的祖先时使用该提交;否则 Hotpath 回退到完整的有限扫描。
- index 会记录 Git 数据是被复用、增量更新、完全重建、因选项更改而被清除,还是因 Git 分析不可用而被清除。
- Git log 和 show 命令使用 `--first-parent`,因此当前的扫描模型可能会遗漏侧分支历史。
- Hotpath 会比较有界的首级父级和所有可达的提交计数。如果侧分支历史大得多,或者许多首级父级提交是合并提交,它会记录繁重合并警告,因为 Git 指标可能少计了侧分支的工作量。
- Git log 和 show 命令使用基本的 Git 重命名检测。重命名跟随是基于路径和启发式的:检测到的重命名记录会归因于重命名后的路径,但不寻常的重命名/复制历史可能仍然是近似的。
- 包含根提交。
- 合并处理遵循当前的首级父级命令行为,而不是对所有父级进行完整的历史分析。
- 近期 churn 使用相对于 `HEAD` 提交者时间戳的固定 90 天窗口,而不是机器的挂钟时间。
- 提交时间戳偏差、rebase、导入、重写的历史以及不寻常的提交者日期可能会扭曲近因性和 age 指标。
- 作者身份是完全一致的 Git 作者字符串,格式为 `Name `。不应用 `.mailmap`、bot 检测、账号合并、大小写折叠和域名标准化。index 明确记录了会忽略 `.mailmap`。
- 没有数字行数统计的二进制更改和 numstat 记录将贡献零行增加和删除。
- 所有权加权使用更改行数、730 天的近因半衰期、批量更改抑制、持续活动加权以及针对低份额作者的 `others` 分组。它是一种操作性启发式方法,而不是代码所有权策略。
- 对于 co-change 对生成,将跳过触及超过 100 个文件的提交,但它们的 churn 和作者记录仍会被记录。
- Hotpath 记录有关广泛提交、最广泛的提交、可能的自动化作者以及高度作者集中度的失真元数据。这些信号是可见的限制;Hotpath 不会在当前评分模型中对 bot 或批量更改作者进行标准化。
- 非 Git 目录、浅层代码库、没有 `HEAD` 的空代码库、缺失 `git`、权限失败以及不受支持的 Git 命令失败会被报告为可操作的 Git 诊断,同时文件扫描会继续进行。
- Co-change 是来自提交的文件对广度,而不是语义耦合。Hotpath 将 co-change 压力和源码耦合压力报告为参考信号。
- Git 指标作为派生缓存数据存储,不是稳定的公共 schema。
## 原则
- 默认完全离线
- 默认无遥测
- 无需云 API
- 在可行的情况下实现确定性结果
- 仅作为参考的指标,而非自动得出的绝对事实
- 公开的限制
- 跨平台行为
## 目标受众
Hotpath 适用于:
- 高级和首席工程师
- 技术负责人
- 平台和 DevOps 工程师
- monorepo 维护者
- 进行代码库审计的顾问
- 使用 AI 编码工具并关注代码膨胀或上下文增长的团队
## 产品方向
Hotpath 的构建目标是回答以下问题:
- 哪些文件结合了高 churn、大体积和集中的运维所有权
- 哪些模块增长最快
- 复杂度压力或源码耦合压力集中在哪里
- 哪些更改触及了已知的热点
- 代码库中有多少内容加载到 AI 编码上下文中的成本很高
- 架构规则是否在发生漂移
- 为什么分配了某个热点分数
该产品方向的大部分内容尚未实现。
## 不适用的方面
Hotpath 不旨在成为:
- 云端 SaaS 产品
- 安全扫描器
- AI 聊天助手
- IDE 插件
- 替代人类工程判断的工具
- 隐藏或不透明质量分数的来源
在这些界面存在时,评分和报告应具有可解释性和可复现性,并被视为决策支持。
## 开发说明
Hotpath 预计使用 Rust 作为核心实现。
常见的 Rust 检查:
```
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test
```
## 隐私
Hotpath 被设计为本地工具。当前的工作流程不应需要网络访问、遥测、云 API、托管服务或上传代码库内容。
本地 index 可能包含敏感的派生代码库信息,例如代码库相对路径、文件事实、Git 指标、所有权启发式方法、依赖记录和风险评分。请将 `.hotpath/` 视为本地缓存数据。
## 许可证
在 Apache License, Version 2.0 下授权。
参见 [LICENSE](./LICENSE.txt)。
标签:Go, Ruby工具, SOC Prime, 云安全监控, 代码分析, 凭证管理, 开发工具, 日志审计, 架构分析, 通知系统, 静态分析