The-Billy-Company/blast
GitHub: The-Billy-Company/blast
Blast 是一款无需预计算图的代码爆炸半径分析工具,帮助 Agent 和开发者评估修改某个符号的完整影响范围。
Stars: 1 | Forks: 0
# blast:面向编程 Agent 的爆炸半径工具
- [概述](#overview)
- [为什么选择它而不是 grep?](#why-this-over-grep)
- [支持](#support)
- [安装](#install)
- [阅读报告](#reading-a-report)
- [种子](#seed)
- [依赖者](#dependents)
- [依赖项](#dependencies)
- [注释](#comments)
- [孪生与波及](#twins-and-ripple)
- [优先级排名](#what-outranks-what)
- [实用配方](#recipes)
- [出处](#provenance)
- [契约](#contracts)
- [构建与测试](#build-and-test)
- [项目来源](#where-this-came-from)
## 概述
一个准备修改某个符号的 Agent 只有一个问题,而且绝不是“这个字符串出现在哪里”。而是“什么会因此损坏”。这是两个截然不同的问题,而 grep 只能回答第一个。
`blast SYMBOL` 负责回答第二个。它会报告该符号在哪里声明、哪些函数依赖于它、它反过来又依赖于什么、描述它且一旦你修改它就会失效的注释,以及历史上经常与之同步变动的文件。
每一条边(edge)都是根据当前代码库的实时状态按需推导出来的。没有需要配置的项目模型,也没有需要重建的图(graph),因此另一个 Agent 在一秒钟前刚保存的文件就已经包含在答案中了,而且对于一种没人教过该工具如何处理的语言,其中的符号依然能够被解析。
最后这一点正是它的取舍所在。编译器前端在其专门构建的语言上会更加精确;而 blast 无需解析器,直接读取字节形状(byte shape),因此它能同时覆盖多语言混合树(polyglot tree)中的每一种语言,代价是必须依赖启发式算法,但它会明确标注这些算法而不是将其隐藏起来。
## 为什么选择它而不是 grep?
当你准备修改代码,并且想在动手前了解改动的影响范围(footprint)时,请使用 blast。它是为在陌生代码树中进行编辑的 Agent,以及在凌晨两点做同样事情的人类准备的。
当你已经知道文本模式(pattern)并且想要获取匹配行时,请改用 [gist](https://github.com/The-Billy-Company/gist)。gist 是一个与 ripgrep 性能相当的索引搜索工具,而计算一份 blast 报告的成本远高于计算你实际想要的那几行匹配结果。
当问题涉及的是相似性而不是某个具体符号时——比如哪个文件与此文件相似、整个代码树中有哪些重复内容、哪些文件共同解释了某个任务——请改用 [relate](https://github.com/The-Billy-Company/relate)。
当你有语言服务器(language server)、代码树只使用一种语言且项目能够正常构建时,请改用语言服务器。而当这三个条件中有任何一个不成立时,就是你使用 blast 的时候。
## 支持
当报告出错时,请向此仓库提交 bug:比如 blast 遗漏的调用点、凭空捏造的行,或者是它未能找到的定义。请附上符号名称和 `--json` 报告,因为该报告包含了 blast 所推断出的全部信息。
当问题出在匹配本身时——比如某个本该匹配的模式没有匹配成功,或者是 Unicode 边界读取错误——请向 [irregex](https://github.com/The-Billy-Company/irregex) 提交 bug。irregex 是这三个工具门面底层的引擎,匹配相关的 bug 可以在其中被独立复现。
当问题出在相似性计算时——比如不是孪生文件却被误认,或者是出处(provenance)短语被归结到了错误的文件——请向 relate 提交 bug。亲缘关系(kinship)和归属判定(attribution)的核心逻辑都在那里;blast 只是对它们进行了组合调用。
请通过 [SECURITY.md](SECURITY.md) 中的流程报告安全漏洞,切勿作为公开的 issue 提交。
## 安装
使用 Zig 工具链从源码构建,或者直接从发布版本中获取 CLI。
```
zig build # blast → zig-out/bin/blast
```
在 Windows 上,安装程序会构建 `blast.exe`,将其放置在用户专属目录中,并在不需要管理员权限的情况下将该目录添加到用户的 PATH 环境变量中:
```
.\install.ps1
```
该二进制文件在执行 `blast` 动词时是完全独立的。`provenance` 额外需要读取由 relate 写入的 codex 书架(shelf),因此如果你想要归属判定功能,请安装 [relate](https://github.com/The-Billy-Company/relate)。
各语言的绑定包均已发布,且它们都是驱动同一个二进制文件而不是重新实现它,因此 CLI 是这三者的共同前提条件:
| | 安装命令 | 代码写法 |
|---|---|---|
| Python | `pip install blast-search` | `import blast` |
| Rust | `cargo add blast-search` | `use blast::…` |
| Go | `go get github.com/The-Billy-Company/blast/bindings/go` | `import ".../bindings/go/compose"` |
由于 `blast` 这个简短名称在 PyPI 和 crates.io 上均已被占用,且那里的名称是永久的,因此发行版带上了 `-search` 后缀,而你输入的标识符依然是 `blast` —— 这就像是 bs4 / PIL 的拆分一样。这三者都会拉取共享的底层基础库,在 PyPI 上是 `irregex`,在 crates.io 上是 `irgx`。针对各语言的详细信息请参阅 [`bindings/python`](bindings/python/README.md)、[`bindings/rust`](bindings/rust/README.md) 和 [`bindings/go`](bindings/go/README.md)。
## 阅读报告
一份报告包含六个部分,它们的排序依据是你需要对其进行编辑的可能性高低。在任何符号上运行它以查看其结构:
```
blast runBlast
```
每个部分都有上限,因此无论该符号多么常见,报告都不会撑爆上下文窗口(context window)。传入 `--budget N` 可以用近似 token 数量进一步限制它;被裁剪的内容会计入 `stats.omitted`,而不是被静默丢弃。
精确证据与统计证据绝不会混为一谈。行号和定义/使用(def/use)分类来源于字节匹配;孪生距离(twin distance)来源于压缩亲缘关系;它们存放在各自独立的字段中,绝不会融合成一个你无法拆解的相关性得分(relevance score)。
### 种子
种子是符号声明的地方,外加对它属于哪种事物的猜测。在多个地方声明过的符号会列出所有声明位置,且最核心的声明排在最前面。
只有源文件才能声明事物。文档中的定义列表和配置文件中的键,它们的表现形式看起来都像是声明,因此它们只会被记录为提及(mention),绝不能伪装成该符号的归属地。
### 依赖者
依赖者即引用(reference),这也是你运行该工具的原因。每一行列出了文件、行号、存在外层函数时的该外层函数,以及该行是使用了该符号还是重新定义了该符号。
函数体之外的引用也会被计算在内。注册表、分发表(dispatch table)、导出列表、路由映射(route map)以及依赖注入(dependency-injection)的配置连线,正是当某个名称发生变动时会导致构建失败的那些边(edge),它们存在于文件作用域内,而这是函数形状(function-shaped)的搜索所无法看到的。
字符串文字(string literal)中的引用同样会被计算,并标记为 `str`。在反射、SQL 和路由表中,名称通常是通过字符串来绑定的,因此丢弃它们会丢失真实的边——但字符串的证据强度弱于函数调用,报告中会说明它找到的是哪一种,而不是将两者混为一谈。
### 依赖项
依赖项反转了这个问题:种子本身依赖于什么。这是列出一旦发生改变就会导致它损坏的那些事物,当符号表现异常(而不是要被移动)时,这正是你想要的。
解析过程刻意保持保守。种子自身的参数和局部变量会被排除在外,限定性的 `head.member` 仅在其 head 所指明的模块内部进行解析,而由整个包声明的名称会被视为环境(ambient)的一部分,而不是作为依赖项。
只有函数才拥有依赖于其他事物的函数体,因此对于类型或值,报告完全不会给出任何依赖项,而不会把它声明语句旁边的那些单词当成依赖项给出来。
### 注释
提及该符号的注释,正是你的编辑即将使其失效的那部分文档。这就是过时文档的重灾区,也是代码审查中最容易被忽略,而用户最先察觉的部分。
注释内的提及绝不会被计为依赖者,而字符串内的提及绝不会被计为注释。这两个判定都是由同一个无解析器的词法分析器完成的,因此这两个部分对于注释在哪里结束,绝对不会产生分歧。
### 孪生与波及
孪生是指与种子文件相比压缩效果极好的文件——它们是近乎重复的文件、分支或者是并行实现。它们是一种协同编辑的信号,而不是依赖关系:相互之间没有任何引用,但从历史上看这些文件是同步变动的,因此在这里的修改通常在那里也需要进行同样的修改。
波及是指二度跳跃。它指出了调用种子的依赖者的那些文件,因此通过某个依赖者传播的改动可能会触及到这些文件,并且每一行都记录了是哪个依赖者连接了这两者。
两者都是基于统计的,并且都被明确标注了。孪生会附带其距离值,以便你判断该结论的强度;而波及行会附带其桥接的名称,以便你一眼就能将其排除。
## 优先级排名
在整个报告中,手写代码(authored code)的优先级始终高于生成代码(generated code)。生成文件是根据契约重新生成的,因此它几乎永远不会成为 Agent 的编辑目标——而且如果不进行排名,它就会占据绝对主导地位,因为代码生成器会在它吐出的每一个 stub、descriptor 和 client shim 中重复某个符号。
被标记为 `gen` 的生成行会被排在最后,而不是被删除。有时候,你想要的证据恰好就在那个生成的调用点上;如果工具将其静默隐藏,那就是在关于影响半径的问题上撒谎了。
排名在上限截断之前运行,这才是最关键的地方。这意味着,一个拥有六个手写调用点和四百个生成调用点的符号,在报告中只会显示那六个手写调用点;而如果是先到先得的报告方式,其整个预算可能会被各种 stub 填满,根本不会提及你真正必须修改的代码。
代码生成的识别,首先依据 generated-by 头部标记,其次依据文件名命名规范。两者在设计上都很宽容:错误的降级只会导致报告重新排序,而这里的任何信号都不会隐藏掉一个真实的匹配。
## 实用配方
在重命名任何东西之前,先询问重命名会触及哪些内容。
```
blast WalletService
```
当你已经知道改动是局部的时,可以把范围缩小到某个子树。作用域是可选的,因为如果爆炸半径在目录处就停下,那将是一种欺骗,所以缩小范围必须由你主动指定。
```
blast Session services/backend clients/web
```
当是由 Agent 而不是人类来阅读报告时,请将其作为一个完整的 JSON 对象获取。该模式是稳定的,每个部分都是一个具名的键(key),并且没有任何内容会在未被统计的情况下被截断。
```
blast Session --json
```
当上下文空间紧张时,请限制报告的大小。种子、统计信息和备注是核心脊梁,绝不会被裁剪;尾部内容会最先被裁掉,并且是从证据效力最弱的部分开始裁起。
```
blast Session --budget 800
```
在没有其他文档的情况下,以专为机器阅读而编写的格式询问该工具能做什么。
```
blast --schema
```
## 出处
`blast provenance TEXT` 以同样的严谨态度回答了另一个不同的问题:这段文本从哪里来,以及当前的代码树中是否仍然包含它。它是为那些你拿到手并准备粘贴的代码片段准备的动词。
归属判定由 relate 完成,验证由 blast 完成。relate 将每个最大逐字短语归属于 codex 书架上的一个样本文件,然后 blast 会重新读取该文件当前的字节,并精确地重新查找该短语。
只有当当前的实时文件中仍然保留了该短语时,它才会显现出来。这就是在这里而不是在 relate 中做这件事的全部意义所在:针对昨天构建的书架所作出的归属判定,可能会指向一个在此期间已经被删除的行,而 blast 绝不会报告这样的行。
```
blast provenance 'const fd = std.posix.openat(std.posix.AT.FDCWD, path'
```
将 `--min-phrase` 提高到其十二字节下限以上以过滤掉琐碎的引用,使用 `-C` 扩展每个定位短语周围的上下文行数。书架来源于 `relate index --shelf`,如果缺少书架,provenance 会明确指出这一点,而不是返回一个空答案。
请注意,这两个动词对 JSON 的结构处理方式不同,因为它们回答的是不同类型的对象。blast 报告是一个单一的 JSON 对象;而 provenance 输出的是 NDJSON,每个被归属的短语占据一行。
## 契约
结果永远输出到 stdout,诊断信息永远输出到 stderr。你通过管道捕获的运行和你在屏幕上实时观看的运行,在 stdout 上产生的字节是完全相同的,因此被捕获的报告与直接阅读的报告绝不会出现分歧。
退出代码采用类似 ripgrep 的格式。零表示动词执行成功,二表示发生了用法、解析错误或书架缺失错误,而即使报告中没有任何行,退出代码依然是零。
曾经被用作动词的名称现在只会触发诊断信息,绝不会成为静默别名。` context` 和 `blast family` 已经合并到了 relate 的 `--matching` 修饰符中,调用它们中的任何一个都会打印出已替代它的新调用方式,并以退出代码二退出——这样,写死的脚本就会响亮地报错失败,而不是悄无声息地偏离到已经变更的语义上。
`blast --schema` 是机器可读的契约:包含每一个动词、每一个 flag、它的类型和默认值、退出代码以及备注。请阅读它而不是去解析 `--help`,因为 `--help` 是写给人看的;另外请注意,`--version` 报告的是该包自身的版本号,而不是底层引擎的版本号。
## 构建与测试
使用 Zig 工具链进行构建、测试和类型检查。
```
zig build # the blast binary → zig-out/bin/blast
zig build test # the unit suite
zig build check # compile-only
```
除非 `-Dcli-optimize` 另有指定,否则构建默认采用 ReleaseFast 模式。这里的测试套件规模刻意做得很小:这个包只是底层引擎的一个门面,那些引擎自带了规模庞大的测试套件,因此 blast 的大部分行为已经在底层得到了验证。
该包的导入拓扑结构由 [`contract/blast.ward`](contract/blast.ward) 进行机器检查,它最多允许两跳的可达性。该门面位于 [`src/surface/face/blast/`](src/surface/face/blast/) 中,并导入所有其他模块:从 irregex 导入引擎、语料库和 argv;从 relate 导入亲缘关系、书架和组合核心逻辑;从 gist 导入 CLI 底座。
## 项目来源
blast 是从一个私有 monorepo 内部的某个包路径中提取出来的,提取点是 `ce430bbaab`。它曾短暂地被命名为 irregex,这就是为什么该名称现在归属于引擎包,而不是归属于某个二进制文件。
开发时使用通过 `build.zig.zon` 路径依赖连接的兄弟检出(sibling checkouts);发布时则会固定 URL 和哈希值。许可证为 Apache-2.0,与其底下的所有包保持一致。
标签:AI编程助手, SOC Prime, 云安全监控, 代码分析, 代码搜索, 凭证管理, 可视化界面, 开发工具, 日志审计, 逆向工具, 静态分析