内容
# hashloom
[](https://github.com/davet47/hashloom/actions/workflows/ci.yml)

**Hashloom 将软件单元视为基于内容的合约,而不是文件。** 一个 MCP 服务器,使得代理再生循环变得廉价。
由于合约是基于内容寻址的,并且依赖关系是明确的,因此代理可以重用验证,精确计算计算爆炸半径,并从几百个令牌的上下文中再生代码,而不是重新读取整个文件。构建系统询问哪些文件更改了。Hashloom 询问哪些软件义务更改了。
名称是一个创造性的词汇,它准确地表达了它是什么:一个由哈希值索引的织布机。在织布机上, warp 线是固定、耐用的线,而梭子则在它们之间织造可丢弃的纬线。**合约是 warp,代码是纬线。** (直到 0.3.3,这个项目以 `heddle-mcp` 的名义发布;引擎未更改。)
## 问题
代理反复支付以重新发现软件结构。基于规范的开发工具使得规范成为持久的工件,而代码是可再生的,但它们运行在普通的 文件上,因此每个再生循环都会重新导出项目已经知道的内容:
1. **上下文获取很昂贵。** 再生一个单元意味着重新读取整个规范和源文件:数千个令牌来学习几个百令牌的内容。
2. **验证未被缓存。** 每次再生都重新运行(并重新读取输出)整个相关测试表面,即使单元的合约没有更改。
3. **爆炸半径是按照惯例,而不是机制。** 当规范更改时,没有什么能准确告诉代理哪些依赖项已失效。
## 模型
Hashloom 将每个软件单元视为具有明确依赖关系的基于内容的合约,而不是文件。合约是一个小的 YAML 规范(签名、不变式、示例、依赖关系名称);其背后的实现是可再生的纬线。由于每个合约都被哈希,其依赖关系被命名,因此代理从文件中重新导出的结构成为 Hashloom 计算和提供的内容。
## 结果
该模型购买了三样东西,全部是机械的:
- **验证缓存。** 绿色测试结果基于合约、实现和依赖关系哈希,并从缓存中提供,直到其中一个更改。pytest 仅在真正错过时运行。
- **机械爆炸半径。** 合约更改报告了确切的失效依赖项集,通过哈希传递和按哈希,而不是按照惯例。
- **小上下文数据包。** 再生一个单元的代理获取合约、其依赖关系的签名及其调用者,作为一个包含几百个令牌的数据包,而不是整个文件闭包。
### 数字
对一个包含 20 个合约的示例项目进行相同的三个再生任务,一次使用原始文件读取,一次使用 hashloom(tiktoken cl100k,使用 `uv run python bench/benchmark.py` 复制):
| 任务 | 原始文件 | hashloom | 减少 |
| --- | --- | --- | --- |
| revenue_by_region | 1925 | 369 | 5.2 倍 |
| top_customers | 2137 | 337 | 6.3 倍 |
| revenue_by_category | 1942 | 396 | 4.9 倍 |
| **总计** | **6004** | **1102** | **5.4 倍** |
原始模式计算基于文件的代理每任务读取的内容:单元的规范文件、每个传递依赖的规范文件、依赖闭包中的每个源模块、单元的测试文件以及运行套件的输出。它故意对基准线很宽容:它假设代理已经知道确切的依赖闭包,而这正是 hashloom为您计算的内容。
相同的方法论适用于所有四个示例项目的每个单元 - Python、Go、TypeScript 和 Java - 通过 `bench/sweep.py`,并且适用于任何 hashloom 项目,包括您自己的项目。完整的扫描平均值低于门控(它们计算几乎没有受益的叶类型);比率跟踪依赖深度,因此更深的项目得分更高。所有数字、它们的分布和诚实的警告都在 [docs/benchmarks.md](docs/benchmarks.md) 中。
## 快速开始
```bash
pip install hashloom
# 或从源代码:pip install "git+https://github.com/davet47/hashloom"
cd your-project
hashloom init # 创建 .hashloom/ 和 contracts/
hashloom index # 从 contracts/ 构建存储
```
将 Claude Code 指向它:
```bash
claude mcp add hashloom -- hashloom serve
```
(标准输入输出传输;服务器通过从工作目录向上遍历到最近的 `.hashloom/` 来解析项目。)
不熟悉工作流程?[docs/getting-started.md](docs/getting-started.md) 通过构建一个包合约优先的代理来讲解 - 工作规则,给它,审查循环和验证门。
## 合约
`contracts/` 中每个单元的一个 YAML 文件。最小,可手写,可哈希:
```yml
name: revenue_by_region
signature: "(sales: list[Sale]) -> dict[Region, float]"
deps: [Sale, Region] # 其他合约名称
invariants:
- 排除已完成为 false 的销售
- 排除金额为 null 的销售
examples:
- 输入: "[Sale(region='QLD', amount=10, completed=True)]"
输出: "{'QLD': 10.0}"
tests: [tests/test_revenue.py::test_revenue_by_region] # pytest 节点 ID
impl: src/revenue.py::revenue_by_region # 当前织入的纬线
status: inferred # 反向工程的,不是人工审查的;确认后省略
```
子目录是命名空间:`contracts/billing/invoice.yaml` 是合约 `billing/invoice`,因此相同的短名称可以存在于不同的文件夹中。合约的 `name` 必须与其在 `contracts/` 下的路径匹配。
### 何时编写合约
合约位于稳定的缝隙:其他单元依赖的接口,您期望它能比当前实现更持久。背后的实现是可丢弃的纬线,可以自由再生。删除合约而不将其放在应有的位置是正确的使用,而不是失败。失败模式是相反的,过度固定您愿意重写的内部,这会将持久层变成繁琐的工作。
合约是审查的工件。编写一个很便宜,变得更便宜,所以真正的成本是审查,而不是编写。错误的合约比没有合约更糟糕,因为持久的工件现在撒谎了;代理将根据这个规格重新生成代码,而这个规格本身就是不正确的。像审查接口一样审查合约,而不是像扫描生成的代码一样。
可以通过现有代码反向工程的合约可以声明它尚未获得审查:`status: inferred`。然后工具会标记 - 永远不会拒绝 - 任何基于它的爆炸半径或验证答案(依赖项上的 `inferred: true`,验证结果上的 `inferred` 列表,`status` 中的审查队列)。不存在意味着 `confirmed`,在审查后确认推断的合约是免费的:状态是出处,而不是含义,因此切换无效化没有任何内容。
### 哈希语义
- **合约哈希**:sha256 超过规范形式:键排序,空格标准化,注释剥离,示例顺序保留,依赖顺序忽略。`impl`、`tests`、`invariants` 和 `status` 被排除,因此 **重新定位文件永远不会失效**,重新措辞不变式是免费的,确认推断的合约永远不会失效任何内容。不变式是文档,而不是机器义务;真正的检查是测试,其源代码在验证密钥中。
- **实现哈希**:sha256 超过实现的规范 AST,因此重新格式化和注释编辑永远不会破坏缓存。文档字符串也被剥离。
- **验证密钥**:`(合约哈希,实现哈希,测试源哈希,工具链标识,传递依赖合约哈希)`。Hashloom 缓存验证结果,其密钥是更改任何合约、实现、测试源或工具链版本都会强制重新运行。工具链组件(`python 3.11.7`、`go 1.21.5`、`node <v> ts <v>`、`java 21.0.3`)是使共享或跨机器绿色声音的内容:3.11 通过永远不会提供给 3.13。失败永远不会从缓存中提供。两个警告。缓存通过假设确定性测试,因此绿色结果依赖于挂钟时间、网络或随机性,可以比条件过期。并且测试源哈希涵盖每个测试函数的规范 AST,而不是它调用的 conftest 夹具或帮助程序,因此仅更改这些不会强制重新运行(参见 [Roadmap](ROADMAP.md))。
## MCP 工具(整个表面)
| 工具 | 功能 |
| --- | --- |
| `get_contract` | 约 300 个令牌的上下文数据包:合约 + 哈希 + 一行依赖签名 + 调用者列表 |
| `put_contract` | 验证,写入 `contracts/<name>.yaml`,返回新哈希,变更语义差异和所有失效依赖 |
| `get_dependents` | 爆炸半径查询,直接或传递,名称 + 哈希;推断(未审查)合约被标记 |
| `verify` | 每单元 `cached-pass` / `pass` / `fail` 加上顶级 `ok` 门控位;`radius=true` 将每个名称扩展到其完整的爆炸半径;仅在缓存丢失时运行测试;失败以 ≤40 个令牌的断言摘要返回,从不回溯;推断合约在闭包中被标记 |
| `status` | 脏合约,过时验证,缓存命中率,已解析验证解释器,累积令牌计数器 |
每个工具都返回结构化错误 - `{"error": {"code": "unknown_dep", "message": "'Regoin' not found — nearest: 'Region'"}}` - 从不堆栈跟踪。
### 验证解释器
`verify` 使用项目的 python 运行测试,按顺序解析:
`hashloom serve --python PATH` → `.hashloom/config.json` (`{"python": "..."}`) → 自动检测的 `<project>/.venv` → 运行 hashloom 的解释器。因此全局安装的 hashloom 可以在不安装到其中的情况下验证项目与其自己的虚拟环境;`hashloom status` 显示它解析的解释器。
`.hashloom/config.json` 还接受 `verify_timeout`(每个 pytest 运行的秒数,默认为 300)用于需要比默认值更长时间的套件,以及 `pycache_trust`(默认为 `true`);设置 `pycache_trust: false` - 或传递 `--no-pycache-trust` - 在每次验证运行之前清除项目的 `__pycache__`,因此过时的 `.pyc` 永远不会遮蔽当前源代码。
### 超越 Python
实现的文件扩展名选择语言适配器 - 每个都带来规范化的 AST 哈希器和测试运行器,因此相同的美观与含义哈希语义适用于每种语言:
- **Go** (`.go`):通过标准库 `go/ast` 哈希,运行 `go test -json`。工具链通过 `.hashloom/config.json` 解析为 `{"go": "..."}`,否则 PATH 上的 `go`。
- **TypeScript** (`.ts`/`.tsx`/`.mts`/`.cts`):通过目标项目的**自己的** `typescript` 编译器 API 哈希,自动从 `package.json` 检测测试运行器(vitest / jest,否则 Node 的内置 `node:test`)。需要 Node >= 22.6;配置键 `{"node": "..."}`。
- **Java** (`.java`):通过单文件 `javac`-tree 助手(仅限 JDK,无依赖)哈希,自动从构建清单中检测测试运行器 - `pom.xml` 路由到 Maven,`build.gradle`/`build.gradle.kts` 到 Gradle,并且提交的 `mvnw`/`gradlew` 包装器优于 PATH 二进制文件。需要 JDK >= 11 加上 Maven 或 Gradle;配置键 `{"java": "..."}`。
Python 仍然是默认语言;项目可以自由混合语言。
### 团队共享验证缓存
默认情况下,缓存位于本地的 `.hashloom/store.db`。将其指向共享后端,一个绿色验证为整个团队服务 - CI 仅验证一次,团队成员的代理获得 `cached-pass`:
```jsonc
// .hashloom/config.json
{"shared": {"url": "https://cache.internal:8770", "token": "..."}}
```
使用 `python -m hashloom.cache_server`(一个带有 SQLite 存储的简单 bearer-token HTTP 服务;一个操作过程,而不是 CLI 命令)在任何地方运行后端。只有绿色被发布 - 失败永远不会跨越边界 - 工具链中的密钥规则以上保持共享绿色声音在机器间。如果共享存储无法访问,验证会静默降级为本地。
## 命令行界面
`hashloom init` · `hashloom index` · `hashloom serve` · `hashloom status` · `hashloom verify`。`.hashloom/` 下的 sqlite 存储是派生状态:随时删除它,`hashloom index` 会从 `contracts/` 重新构建它。
`hashloom verify <name>…` 从命令行运行与 MCP 工具相同的缓存验证,并以非零退出,除非每个单元都是绿色的(失败,未知名称或无法验证的单元都会阻止)- 将其放入 CI 或预提交钩子中。 `--radius` 将每个名称扩展到其自身和所有传递依赖项,因此一个命令控制更改的整个爆炸半径:`hashloom verify --radius Sale`。
## 尝试示例项目
```bash
cd examples/sales
hashloom init && hashloom index && hashloom serve # 然后将您的代理指向它
```
20 个合约,25 个测试,三个依赖层深。每个示例目录都附带自己的 README,包含每种语言的运行说明。
在 `examples/go-ledger` 中有对应的 Go 示例(8 个合约,一个小型双重记账系统,相同的循环:`hashloom init && hashloom index`,然后 `hashloom verify --radius Entry` 在幕后使用 `go test` - 需要 Go 工具链),在 `examples/ts-cart` 中有 TypeScript 示例(8 个合约,一个购物车;先安装 `npm install` 以获取其 `typescript`,然后相同的循环 - 验证在 Node 的内置 `node:test` 上运行,Node >= 22.6),以及在 `examples/java-payroll` 中有 Java 示例(11 个合约,一个每周工资运行,三个层深 - 记录,`Class.method` 限定,参数化括号表和 `@Nested` 测试类,带有点节点 ID;一个 Spring 服务层与零框架依赖的形状。相同的循环 - `hashloom verify --radius TimeSheet` 在幕后运行 Maven - 需要 JDK >= 17 和 Maven)。
## 开发
```bash
uv sync
uv run pytest # 完整套件;哈希稳定性是承载套件
uv run python bench/benchmark.py
```
故意保持小:5 个 MCP 工具,5 个 CLI 命令,合约为纯 YAML。README 中没有的所有内容都是一个 [问题](ISSUES.md)。
## 许可证
Apache 2.0
<!-- mcp-name: io.github.davet47/hashloom -->