rdzehtsiar/hotpath

GitHub: rdzehtsiar/hotpath

一款离线本地优先的代码库智能分析工具,通过扫描 Go 源码和 Git 历史识别高风险、高复杂度的代码热点区域。

Stars: 1 | Forks: 0

# Hotpath [![Tests](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/f697b1842a230552.svg)](https://github.com/rdzehtsiar/hotpath/actions/workflows/tests.yml) [![codecov](https://codecov.io/gh/rdzehtsiar/hotpath/graph/badge.svg)](https://codecov.io/gh/rdzehtsiar/hotpath) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=rdzehtsiar_hotpath&metric=alert_status)](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, 云安全监控, 代码分析, 凭证管理, 开发工具, 日志审计, 架构分析, 通知系统, 静态分析