内容
# 🕸️ aspark-graph
> **一个精简的本地知识图谱,它连接了一个存储库的代码与其交付产物 —— 这样代理和人类可以追踪用户故事到实现它的代码,并查看变更的故事级影响范围。**
aspark-graph 读取一个存储库 —— 它的源代码 **和** 它的
[aSPARK](https://github.com/a-lottes/aSPARK) `.spark/` 交付轨迹(规格、计划、评审、QA 报告)—— 并构建一个单一的可查询图谱,通过 CLI **和** MCP 服务器提供服务。它是 **确定性的**(tree-sitter + 声明的产物链接;**没有 LLM,没有网络**)和 **可丢弃的**(图谱是一个可重建的读取模型,永远不是事实来源)。
---
## 它存在的两个问题
一切都服务于以下问题:
| 问题 | 工具 | 直白的含义 |
|---|---|---|
| *"哪个代码实现了这个用户故事,它的验收标准是否通过了 QA?"* | `story_trace US-2` | 跟随故事 → 验收标准 → 计划任务 → 代码 → QA 结果,零搜索。 |
| *"如果我更改这些文件,哪些故事和验收标准在影响范围内 —— 需要重新验证什么?"* | `impact src/foo.py` | 遍历代码 → 任务 → 故事/验收标准,并为每个命中标记其可信度。 |
## 为什么存在
当 AI 代理(或监督它的开发人员)处理一个 **太大而无法理解** 的 aSPARK 管理存储库时,这两个问题正是 aSPARK 自己审查和 QA 门户所依赖的 —— 而今天,唯一能回答它们的方法是 `Grep`/`Glob` 加上手动阅读 `.spark/` 文件。
规格 → 计划 → 审查 → QA 轨迹 *是* 机器可解析的,并且 *是* 通过意图与代码链接的。但是,没有什么能连接两者,所以代理每次搜索时都会重新推导链接:**缓慢、不完整且不可复制。** aspark-graph 一次性确定性地计算连接,并允许您查询它。
它通过在代码方面有意做 **更少** 的事情,而不是一般的代码图谱,并添加一个一般图谱所没有的东西:**交付产物**。该产物层使得故事追踪、门户感知的影響和孤立检测成为可能。
## 何时使用 —— 何时不使用
- ✅ **使用它** 在一个 aSPARK 存储库中,它大到足以使“读取所有相关文件”不可行时,您需要在打开文件之前获得快速、可复制的答案。
- ❌ **跳过它** 在一个小到可以理解的存储库中(只需阅读文件),或一个没有 `.spark/` 产物的存储库中(产物层是重点)。
- 🤝 **还想要一个广泛的语义代码图谱吗?** 将 [Graphify](https://github.com/safishamsi/graphify) 与它一起运行 —— 不同的范围,没有冲突。aspark-graph 是 aSPARK 的加速器,而不是代码搜索工具的替代品。
**在 aSPARK 门户中使用 aspark-graph?** 请参阅
[`docs/aspark-integration.md`](docs/aspark-integration.md) 以获取直接插入
`CLAUDE.md` 块的说明,这些块将 `/peer-review` 和 `/demo-day` 门户连接到查询工具。
---
## 图谱模型(阅读它以解释任何结果)
图谱是一个有类型的有向多图。每个节点 ID 都是 **稳定且确定性的**,仅从内容和位置导出,因此两个构建的不变存储库会产生字节相同的 ID 和字节相同的 `graph.json`。
**节点类型**
| 层 | 类型 | 来源 |
|---|---|---|
| 代码 | `File`, `Class`, `Function` | tree-sitter 提取 |
| 产物 | `Feature`, `Story`, `AcceptanceCriterion`, `Task`, `Finding`, `QACheck` | `.spark/` 模板 |
**边类型**
| 边 | 方向 | 含义 |
|---|---|---|
| `contains` | File → Class/Function | 代码结构 |
| `imports` | File → File | 已解析的导入 |
| `calls` | Function → Function | 尽力而为,可能不存在 |
| `has_story` | Feature → Story | 产物结构 |
| `has_ac` | Story → AcceptanceCriterion | “ |
| `has_task` | Feature → Task | “ |
| `maps_to` | Task → Story | 计划将任务链接到它服务的故事 |
| `implements` | Task → File/Function | **代码↔故事桥梁**(尽力而为;见下文) |
| `verifies` | QACheck → AcceptanceCriterion | 验收标准的 QA 结果 |
| `found_in` | Finding → File | 审查发现的位置 |
**置信度层级** —— 每个产物/代码链接都带有层级,并且 `impact` 报告 **最弱链接在最强路径上**,以便您可以适当地信任结果:
| 层级 | 排名 | 来源 |
|---|---|---|
| `declared` | 最强 | 计划任务中的显式 `files:` 注释 |
| `extracted` | 中间 | tree-sitter(`contains`/`imports`)—— 确定性结构 |
| `inferred` | 最弱 | 从 git 历史记录中自我推导 —— 将其视为提示,在采取行动之前请确认。 |
> **阅读结果:** 标记为 `inferred` 的 `impact` 命中的是通过 git 历史记录猜测到达故事的;标记为 `declared` 的命中基于作者编写的链接。
> 置信度层级从不 *提高* 置信度 —— 它报告最弱的步骤,因此推断边只能 *降低* 路径的置信度,而不能掩盖真实的路径。
**节点 ID 方案**(在构造 `get_node`/`shortest_path` 查询时很有用):
```
file:<relpath> 例如 file:src/aspark_graph/queries.py
def:<relpath>::<qualname> 例如 def:src/foo.py::Widget.render
feature:<name> 例如 feature:aspark-graph
story:<feature>:<id> 例如 story:aspark-graph:US-1
ac:<feature>:<id> 例如 ac:aspark-graph:AC-1.2
task:<feature>:<id> 例如 task:aspark-graph:T3
finding:<feature>:<id> 例如 finding:aspark-graph:F1
qa:<feature>:<ac>#<index> 例如 qa:aspark-graph:AC-1.1#0
```
---
## 安装
需要 Python ≥ 3.11 和 [uv](https://docs.astral.sh/uv/)。aspark-graph 尚未发布到包索引,因此请从此存储库的检出中安装:
```bash
git clone https://github.com/a-lottes/aSPARK-graph.git aspark-graph
cd aspark-graph
uv sync # 安装到本地 .venv
uv run aspark-graph build . # 为当前存储库构建图谱
```
将它添加到 Claude Code 作为 MCP 服务器,指向您的检出:
```bash
claude mcp add aspark-graph -- uv run --directory /path/to/aspark-graph aspark-graph serve
```
在 aspark-graph 发布到包索引之前,上述从源代码路径是支持的安装方式。
## 构建图谱
```bash
aspark-graph build [path] # 扫描代码 + .spark/,写入 .aspark-graph/graph.json
```
图谱被写入存储库根目录的 `.aspark-graph/graph.json`(gitignore 它 —— 它是可重建的)。在未更改的存储库上重新运行 `build` 会生成 **字节相同** 的图谱。解析失败 **大声** 地报告 `.spark/` 模板漂移(它会命名文件和不匹配)而不是静默猜测。
## 查询
每个查询都可在 **CLI 和 MCP 上使用**,并且它们通过构造返回 **相同的答案**(所有查询逻辑都位于同一个共享模块中;CLI 和服务器是它的薄适配器,并且一个对等测试强制执行它)。输出是 JSON。
### CLI
```bash
# 两个主要查询
aspark-graph query story_trace US-2 --feature my-feature
aspark-graph query impact src/foo.py src/bar.py
aspark-graph query impact --diff HEAD~1..HEAD # 变更范围的影响范围
# 门户和新鲜度
aspark-graph query gate_health my-feature # 该特性的验收标准是否涵盖/通过?
aspark-graph query staleness # 图谱是否仍与磁盘上的存储库匹配?
# 图谱导航
aspark-graph query get_node "file:src/foo.py"
aspark-graph query find_nodes Widget --type Class
aspark-graph query get_neighbors "story:my-feature:US-1" --edge-type has_ac
aspark-graph query shortest_path "task:my-feature:T1" "ac:my-feature:AC-1.1"
```
### MCP
相同的操作作为 MCP 工具公开:`build_graph`, `story_trace`, `impact`, `gate_health`, `staleness`, `get_node`, `find_nodes`, `get_neighbors`, `shortest_path`. 在构建(或任何域错误)之前查询返回一个干净的 `{“found”: false, ...}` 形状的结果 —— 永远不会出现原始回溯。
## 将代码链接到故事
`impact` 和 `story_trace` 的有用性取决于它们可以找到的 `implements`(任务→代码)链接。aspark-graph 通过三种方式建立这些链接,从强到弱置信度:
| 置信度 | 来源 | 如何选择 |
|---|---|---|
| `declared` | 计划任务上的显式 `files:` 注释 | 在 `plan.md` 中,在任务的*完成定义*单元格中添加 `files: <path>`,例如 `… ; files: src/foo.py`。仅当文件存在时创建链接 —— 悬空路径被忽略,永远不会被构造。 |
| `inferred` | Git 提交历史记录 | 在提交消息中引用任务 ID **和** 故事 ID —— 主题 `T3: add parser (US-1)` 或 `Refs: T3, US-1` 尾部。在提交所触及的任何文件都链接到任务,置信度为 `inferred`。 |
| `extracted` | tree-sitter(`contains`/`imports`) | 自动 —— 无需操作。 |
**推荐用于 aSPARK 存储库:** 每个任务创建一个提交,其消息中包含任务和故事 ID(aSPARK 的工作流程已经鼓励这种约定)。仅凭这一点就足以让 `impact` 在从未手动注释的存储库上回答。
推断是 **确定性的**(它只读取已提交的状态 —— 文件路径和消息 ID,永远不是时间戳)并且 **离线的**;如果 git 无法访问,它就会被简单地跳过。当多个 `.spark/` 特性重用相同的 `T<n>`/`US-<n>` 编号时,一个提交被解析为一个 **单一** 特性,然后链接(通过它所触及的 `.spark/<feature>/` 树,或消息中的任务→故事配对);一个真正模糊的提交贡献 **没有** 边 —— 一个诚实的缺失优于一个错误的跨特性链接。
## 支持的语言
代码提取涵盖 **TypeScript/JavaScript、Python 和 Java**(tree-sitter)。其他语言的文件被记录为未解析的 `File` 节点 —— 构建永远不会因未知语言而失败。
## 设计保证(为什么您可以信任输出)
- **确定性。** 在未更改的存储库上进行字节相同的重建;解析影响依赖项被精确固定;双重构建测试强制执行。
- **离线和无 LLM。** 没有网络,没有模型调用 —— 只有 AST 解析和产物模板解析。
- **大声失败,绝不沉默。** 模板漂移引发命名错误;它永远不会跳过或猜测。
- **干净的错误。** 域错误(漂移,图谱未构建)表现为一行消息,退出状态为非零或结构化字典 —— 永远不会出现堆栈跟踪。
- **可丢弃。** 图谱是一个读取模型。删除 `.aspark-graph/` 并重建;事实来源始终是代码和 `.spark/` 文件。
## 超出范围(v0.1.0)
更多语言、LLM/自然语言层、精确的调用图解析、增量更新、视觉 UI、导出(Neo4j/GraphML/Obsidian)和 HTTP/团队模式都超出了范围。请参阅 `.spark/aspark-graph/spec.md` 以获取完整的、诚实的超出范围列表。
## 开发
```bash
uv sync --extra dev
uv run pytest
```
该项目 **自我测试**:它自己的 `.spark/aspark-graph/` 轨迹是主要的测试夹具,因此对解析器或查询的任何更改都针对真实的 aSPARK 轨迹进行检查。
## 许可证
MIT © Andreas Lottes。aSPARK 产品家族的一部分。
代码图谱先前作品:[Graphify](https://github.com/safishamsi/graphify) —— 不同的范围。