Catalog PyPI
PACKAGE EXPLORERView in registry

aifix-code

PyPI

测试失败驱动的自我修复 agentic loop:红着的测试进去,一条验证过的补丁分支出来

agentagenticcode-repairlanggraphllmmavenpytesttest-repair自动修复
Versions collected1
Direct dependencies4From release 0.7.1
Last observed21 Oct, 10:43 UTC

README

Version 0.7.1 documentation

aifix

PyPI Python License Tests

红着的测试进去,一条验证过的补丁分支出来。

aifix 是一个测试失败驱动的自我修复循环:它跑一遍你的测试,把失败的用例一个个 排队,让模型去定位、去改,然后用测试本身判断改得对不对。改对了就提交到一条 独立分支上,改错了当场回滚。人只在最后做一个决定:这个 merge 要不要按下去。

你的仓库(红着 3 个用例)
        │
        ▼
   aifix run .
        │
        ▼
分支 aifix/a1b2c3d4(多了 2 个提交)+ 一份报告
        │
        ▼
   你看一眼 diff,git merge

目录


它到底做什么

给它一个 git 仓库,它会:

  1. 先跑一遍全量测试,看清有几个红的(这一步没有任何模型参与);
  2. 对每个红的用例,让一个诊断模型读 traceback 和相关源码,猜缺陷在哪;
  3. 让一个修复模型带着工具(读文件、搜代码、改代码、跑目标用例)去改;
  4. 再跑一遍全量测试,用改前改后的失败集合对比出一个判定:
    • 修好了(目标用例转绿,且没有任何新的红)→ 提交到交付分支
    • 没改善(目标还红着)→ 回滚,换个思路再试,最多 3 轮
    • 引入回归(冒出了新的红)→ 立刻回滚
  5. 跑完给一份 Markdown 报告:修好了几个、花了多少钱、哪些补丁值得多看一眼。

全程不碰你的主工作区。 所有改动发生在一个 git worktree 里,交付物是一条 新分支(aifix/<run_id>),要不要合并完全是你的事。

支持三种项目:pytest(Python)、Maven(Java)和 vitest(JS/TS 前端)。

前后端同仓的工程(Python 后端 + vitest 前端)一次 run 可以跑两套,设一个变量即可:

export AIFIX_ADAPTERS=pytest,vitest

不设的话只用探测到的第一个,另一侧的用例一条都不执行 —— 那不是「通过」,是不 存在。为什么要人显式说一句而不是自动全跑,见 configuration.md。


三条不肯让步的原则

这三条决定了这个项目所有别扭的地方,值得先读:

1. 只有零 LLM 的确定性代码有资格说「修好了」

判定「这个补丁行不行」的是 verify.compare() —— 二十行 Python,比较两次测试 结果的失败集合。模型说什么都不算数。

只要出现任何新的失败,哪怕目标用例真的修好了,一律判「引入回归」并回滚。 这比「净改善」保守得多,但这正是敢在真实仓库上跑它的前提。

2. 测试是判卷标准,所以模型碰不到测试文件

修复模型的工具面是白名单,只有 8 个工具,没有 shell、没有网络。而所有能写 文件的工具都过同一道守卫:写到测试目录下 → 硬拒绝。

理由很直白:让测试通过的唯一正确方式是改源码。允许模型改测试,等于允许它改判 卷标准 —— 而它是真的会去试的(评测里出现过:模型把 add 改成有状态函数,去 满足一个自相矛盾的断言,顺手删掉了没有测试覆盖的 mul)。

3. 数字不能撒谎

这个项目为「假的 0.00」栽过三次,所以现在到处都是这类处理:

  • 花了 token 却算出 0 元 → 显示「未知(未配置 AIFIX_PRICE_MAP)」,不显示 ¥0.00
  • 一批 run 里有几次取不到修复数 → 报告写「修复 ≥N(不完整:3 次里有 2 次取不到)」,不直接求和
  • 测试进程没跑成 → 中止并退出码非 0,绝不说「你的仓库全绿」
  • baseline 里全是「整个测试文件导入失败」→ 判定为环境故障并中止,不当成待修的 bug 去烧钱

装上并跑起来

环境要求

  • Python ≥ 3.11
  • git
  • 目标项目自己能跑起来的测试环境(详见下面那条「最常见的坑」)

安装

pip install aifix-code        # 或者:uv tool install aifix-code

装完之后命令叫 aifix(不是 aifix-code):

aifix --help

发行名与命令名不同不是笔误。 aifix 这个名字在 PyPI 上已被另一个无关的包占用, 拿不到;而命令、issue 上的 /aifix、产物目录 .aifix/、全部文档都用 aifix 这个 词,改它的代价比换一个发行名大得多。与 scikit-learn → sklearn 同一个形状。

推荐用 uv tool install:aifix 自己依赖 langgraph、pydantic、openai,装进你项目的 环境里可能起冲突 —— 而它本来就是个独立跑的命令行工具,不需要和被修的项目共用一个环境。 (跑测试用哪个解释器是另一个旋钮,见下面那条「最常见的坑」。)

升级:

pip install --upgrade aifix-code      # uv tool upgrade aifix-code

要改 aifix 自己的代码,从源码装:

git clone https://github.com/sumengnan/aifix-code.git
cd aifix-code
uv sync
uv run aifix --help          # 源码树里要带 uv run 前缀

下文所有例子写的都是 aifix ...。如果你是从源码跑的,前面加 uv run。

配模型

两条模型路由,可以是同一个端点,也可以是两家不同的供应商(诊断挑便宜的、修复挑强的):

export AIFIX_FIXER__BASE_URL="https://your-endpoint/v1"
export AIFIX_FIXER__API_KEY="sk-..."
export AIFIX_FIXER__MODEL="qwen3-coder-flash"

export AIFIX_DETECTOR__BASE_URL="https://your-endpoint/v1"
export AIFIX_DETECTOR__API_KEY="sk-..."
export AIFIX_DETECTOR__MODEL="qwen3-coder-flash"

# 价格表:{模型名: [输入价/千token, 输出价/千token]}
# 不配的话成本算出来恒为 0,成本闸永远不会触发
export AIFIX_PRICE_MAP='{"qwen3-coder-flash": [0.0003, 0.0012]}'

注意:两级下划线 __ 是嵌套配置的分隔符,不是笔误。

第一次跑

先空跑一次,看看有多少活、不花一分钱:

aifix run /path/to/your/repo --dry-run

真跑:

aifix run /path/to/your/repo --budget 2.0

跑完屏幕上会有一份报告(进度走 stderr,报告走 stdout,所以 aifix run . > report.md 存出来的文件是干净的):

# aifix run a1b2c3d4

- 适配器:pytest
- 分支:`aifix/a1b2c3d4`
- 修复:**2 / 3**
- 成本:¥1.01(238,070 tokens,按 1 USD = 7.2 CNY 折算)

| 测试用例 | 结果 | 尝试次数 | 中止原因 |
|---|---|---|---|
| `tests/test_cart.py::test_total` | 已修复 | 1 | — |
| `tests/test_cart.py::test_empty` | 已修复 | 2 | — |
| `tests/test_api.py::test_auth` | 未改善 | 3 | max_attempts |

合并:`git merge aifix/a1b2c3d4`

最常见的坑:用哪个 Python 跑测试

目标项目的测试依赖装在它自己的环境里,不在 aifix 的环境里。 aifix 会自动 探测源仓库下的 .venv/bin/python 或 venv/bin/python,探不到才退回自己的解释器。

实测过一次对照:拿 aifix 的 venv 去跑另一个项目的测试 → 11 个 collection error, 一个用例都没跑到;换成它自己的 .venv → 673 passed。

探测不到就显式配:

export AIFIX_TEST_PYTHON=/path/to/目标项目/.venv/bin/python

还有一个更隐蔽的陷阱:目标项目如果把自己可编辑安装(pip install -e .) 进了那个解释器,import <目标包> 可能解析到源仓库而不是打了补丁的 worktree —— 测试照跑照绿,而验证的是没打补丁的代码,结论是假的。aifix 会在 baseline 之前 探测并往 stderr 出声,但那是提醒不是保证。最可靠的自保是在目标项目的 pytest 配置 里设 pythonpath:

[tool.pytest.ini_options]
pythonpath = ["src"]

一次 run 里发生了什么

preflight   探测适配器 / 检查工作区干净 / 校验测试解释器          零 LLM
    ↓       (任一不满足 → 中止,退出码 1)
探针        往模型端点发一次最小调用,确认连得上                 一次极小调用
    ↓       (`--dry-run` 时跳过)
建 worktree 从 HEAD 开一个隔离工作区 + 分支 aifix/<run_id>
    ↓
baseline    跑全量测试 → JUnit XML → 失败集合                    零 LLM
    ↓       (全绿 → 直接出报告,「没活干」)
┌── detect  traceback + 真实源码片段 → 结构化诊断 JSON           单步、无工具
│      ↓
│   fix     读码 / 搜索 / 改代码 / 跑目标用例                     多步、8 个工具
│      ↓    (改动为空或过大 → 带反馈重试,不计入 attempt)
│   verify  再跑全量 → 抖动过滤 → 三态判定 → commit 或 rollback   零 LLM
│      ↓
└──── 没修好且还有轮次 → 回到 detect;修好了或用尽 → 下一个 failure
    ↓
report      渲染 Markdown 报告,落盘到 .aifix/runs/<run_id>/

几个值得知道的细节:

  • 全量测试很贵,所以 baseline 整个 run 只跑一次,之后每轮 verify 各跑一次。
  • 抖动过滤:verify 发现新的失败时,会只重跑那几个用例确认一次。重跑还红的 才算真回归;重跑绿了的算抖动,从判定里剔除。成本近似为零,却挡掉了绝大部分 「把一个本来正确的补丁滚掉」—— 那是这个系统最昂贵的错误。
  • 连续失败熔断:连着 3 个 failure 一个都没修好就整体中止。那多半不是「这些 bug 恰好都难」,而是环境坏了 / 端点不通 / 今天这个模型不行。继续跑只是匀速烧钱。
  • 信息不全时它会停下来问你(ask_user),把问题和 2–4 个选项写进报告, 用 aifix answer <编号> 带着答案重跑。

详见 docs/architecture.md。


命令速查

命令干什么
aifix run [repo]修复当前仓库的失败测试 —— 主命令
aifix answer <编号> [repo]回答上次 run 提出的问题,带着答案重新跑
aifix reproduce [repo] --issue-text FILE把一段缺陷报告译成一条复现测试(只到复现为止,不改产品代码)
aifix mine [repo]从 git history 挖评测任务集(自带 ground truth)
aifix mutate [repo]人造变异生成冒烟任务集
aifix eval <tasks.jsonl>在任务集上跑评测
aifix eval-report <results...>把若干轮结果渲染成跨模型对比表
aifix replay <run_id>回放一次 run 的逐步复盘
aifix ingest把各次 run 的事实灌进 .aifix/trajectory.db
aifix stats跨 run 汇总:适配器、守卫触发、可疑信号
aifix issue handle处理一次 GitHub issue_comment 事件(在 Actions 里被调起)

每个命令的完整参数、退出码语义见 docs/cli.md; 所有环境变量见 docs/configuration.md。

退出码

只有一类情况退非 0:这次 run 没跑成(崩溃、baseline 全是收集错误、模型端点 不通、preflight 拦下)。

预算耗尽不在此列 —— 那是正常收场:活干到钱花完为止,结论仍然可信,所以退 0。


issue 驱动:一个 issue 换一个 PR

在 GitHub 上开一个 issue,正文第一行写 /aifix,其余部分写缺陷描述:

/aifix
购物车为空时调 total() 抛 KeyError,期望返回 0。

然后:

读事件载荷 → 判授权 → 让模型写一条复现测试 → 跑一遍确认它真的红了
    → 提交这条测试 → 跑核心循环 → 推分支 → 开 PR

在已有 issue 下评论 /aifix 也能触发(用来再跑一次),/aifix 2 用来回答它提的问题。

三条交付通路:

情形产出
写不出复现(或复现不成立)只回帖说明缺什么。不建分支、不开 PR
写出了复现但没修好照样开 PR,标题标明「未修复」—— 一条红着的复现测试本身就是产出
修好了开 PR,报告写进正文

这一层的每一个判定都是零 LLM 的:谁有权触发、命令怎么解析、走哪条交付通路, 全部由确定性代码决定。模型只负责写复现测试。

授权只有一条原则:触发权 = 已经能改这个仓库的人(所有者、有写入权限的协作者、 组织仓库的成员,外加一份可选的 AIFIX_ALLOWED_USERS 白名单)。

因为 issue 正文会驱动模型改代码、开 PR —— 一个本来就能直接推代码的人驱动它不增加任何 新风险,而一个没有写入权限的人能驱动它,等于给了他一条间接的写路径。没权限的人打了 /aifix 会收到一条说明,不会被静默丢弃。


它到底行不行:评测读数

aifix 自带一套评测:从 git history 里挖出真实的「红转绿」提交当任务 (aifix mine)。任务自带 ground truth —— 那个提交改过的源文件就是标准答案, 不需要人来标注。

39 个任务(来自两个真实仓库),四轮读数:

模型任务数定位准确率修复成功率平均成本
deepseek-v4-flash3751% (19/37, CI 36%–67%)27% (10/37, CI 15%–43%)$0.4316
qwen3-coder-flash(旧工具面)3990% (35/39, CI 76%–96%)18% (7/39, CI 9%–33%)$0.2345
qwen3-coder-flash(新工具面)3992% (36/39, CI 80%–97%)74% (29/39, CI 59%–85%)$0.1311
qwen3-coder-flash(换补丁重算实现)3990% (35/39, CI 76%–96%)79% (31/39, CI 64%–89%)$0.1241

成本单位是美元 —— 这四轮是换币种之前跑的。现在的报告与对比表一律渲染成 ¥; 这里保留原始数字不折算,折出来的数没有一轮是真跑出来的。

每个百分比都带置信区间,这不是排版讲究:只给比率的话,1/1 的 100% 和 12/20 的 60% 长得一样重,而这个项目已经从噪声里读出过一次假结论并公开更正。

第二行到第三行是一次干净的对照:同一个模型、同一批任务、同一份预算, 只改了工具面,修复成功率 18% → 74%,两个置信区间完全不重叠。起因是一组 读数:apply_patch 被调用 332 次、失败 309 次,而能解析的坏补丁里 247/247 是 @@ -a,b +c,d @@ 里的行数与正文对不上。模型的正文往往是对的,它栽在数数上。

于是加了三个工具、改了一处输入:

  • edit_file —— 给原文和新文,不写 diff、不数行号
  • read_symbol —— 按名字读一个函数/类的完整定义,边界由 AST 算出来
  • read_file 带 offset —— 截断消息里给出下一段从第几行开始
  • 诊断模型现在能看见候选位置的真实源码,而不只是路径和行号

同时 token 降 45%、成本降 44%:省掉的正是「在读文件 / 打补丁的死循环里空转」 那一部分。

完整方法、四轮明细、以及两处作者自己读错然后更正的记录,见 docs/evaluation.md。


跑不起来时先看这里

症状多半是
「没有适配器认领这个项目」目录里既没有 pom.xml、没有依赖 vitest 的 package.json,也没有 pyproject.toml / tests/
「工作区不干净,请先提交或 stash」已跟踪文件有未提交的改动(未跟踪文件不算)
「模型端点不可达」key / base_url 配错,或这台机器出不了网。GitHub runner 上还要确认端点没有 IP 白名单
「本次 baseline 的 N 个失败里有 M 个是整个测试文件没能跑起来」测试解释器里没装目标项目的依赖 —— 配 AIFIX_TEST_PYTHON
「跑目标项目的测试超时了」调大 AIFIX_TEST_TIMEOUT_SECONDS(默认 1800 秒)
「拒绝启动:设置了预算上限,但没有配置价格表」配 AIFIX_PRICE_MAP,或改用 AIFIX_BUDGET_TOKENS
报告说修好了,但分支上什么都没有不该发生 —— 这条路上有守卫,请把 .aifix/runs/<run_id>/ 留下来

跑完想知道模型每一步在干什么:

aifix replay <run_id> --repo /path/to/repo
aifix replay <run_id> --step 7 --full   # 只看第 7 步,不截断

诊断工具的完整用法见 docs/diagnostics.md。


更多文档

文档讲什么
docs/integration.md接入教程:怎么把 aifix 接到你自己的项目上
docs/architecture.md状态图、每个节点在做什么、模块地图、为什么这么分
docs/cli.md全部命令的参数与退出码语义
docs/configuration.md所有环境变量、默认值、以及每个旋钮的取舍
docs/safety.md守卫、四层围栏、三层预算、不可逆动作清单
docs/adapters.md适配器协议、pytest / Maven / vitest 三个实现、怎么加下一个
docs/evaluation.md评测方法、怎么读那张对比表、已知的口径偏差
docs/issue-driven.mdissue → PR 流水线、GitHub Actions 配置、四个 workflow
docs/diagnostics.mdtrace / replay / ingest / stats,以及事实的数据契约
docs/superpowers/specs/原始设计规格(写代码之前的评审稿)
docs/superpowers/plans/六个里程碑的实现计划

项目结构

src/aifix/
├── cli.py            命令行入口 + run_once(真正的主循环)
├── graph.py          状态定义、LangGraph 装配、熔断判据
├── config.py         全部配置项(pydantic-settings,读 AIFIX_ 环境变量)
├── reproduce.py      缺陷报告 → 复现测试 → 红检
├── nodes/            六个节点:preflight / baseline / detect / fix / verify / report
├── agents/           四个 agent 的提示词与输出解析:detector / fixer / reproducer / reviewer
├── checks/           判据:三态判定 / 静态信号 / 必要性反查 / 变形复跑 / 越界计数
├── observe/          可观测:trace / 发布 / 跨 run 汇总 / 回放 / 进度
├── runtime/          基础设施:worktree 交付 / 预算 / 折算 / 解释器 / 片段 / 待答问题
├── tools/            8 个工具 + 共用的写入守卫
├── adapters/         项目适配器:pytest / maven / vitest / JUnit XML 解析
├── eval/             评测:挖任务 / 变异 / 跑批 / 打分 / 区间估计
└── issue/            issue 驱动:授权判定 / GitHub 客户端 / 流水线编排

代码里的注释密度较高,写的大多是为什么而不是「这一行做了什么」:某条守卫 在防什么、换一种写法会以什么方式静默失效。读代码时它们比这份 README 更精确。


依赖

  • ai-harness-framework ≥ 0.0.3 —— 自研的 agent 框架:模型接入、工具循环、打转检测、预算、事件流、沙箱抽象
  • langgraph ≥ 1.2 + langgraph-checkpoint-sqlite
  • pydantic ≥ 2.7

分工边界:harness/ 里不出现 pytest、failure、patch 这些词,它只知道 「模型、工具、循环、预算」;领域知识全在 aifix 这一层。


参与开发

git clone https://github.com/sumengnan/aifix-code.git
cd aifix-code
uv sync
uv run pytest -q -n auto      # 993 个用例,并行约 8 分钟

main 与 PR 上会自动跑 Python 3.11 / 3.12 / 3.13 三个版本 (tests.yml)。 vitest 真跑那条同理,判据是 npm install --offline 装不装得上 vitest。

Maven 那几条用例在没有可用 mvn 的机器上会自己跳过 —— 判据是「mvn -o 跑不跑得 起来」,不是「mvn 在不在」。

发新版本

发布走 PyPI Trusted Publishing(OIDC),仓库里没有、也不需要任何 API 令牌。

# 1. 改 pyproject.toml 里的 version,提交
# 2. 打 Release —— 剩下的全自动
gh release create v0.1.1 --generate-notes

release.yml 会依次做:三个 Python 版本跑全量测试 → 核对 tag 与 pyproject 版本 是否一致 → uv build → twine check → 上传 PyPI。

两处刻意的闸:

  • 版本号对不上就停。 不查的话,标签打成 v0.2.0 而 pyproject 还写着 0.1.0 时, 发出去的是 0.1.0 —— PyPI 报一句「File already exists」,错在标签,消息却指向别处。
  • 传之前 twine check。 构建成功不等于传得上去,而那时 Release 已经发出去了, 看起来像「发布成功了但包没上去」。

PyPI 不允许重传同一个版本号。 发错了只能发下一个补丁号,而错的那个仍然挂在 上面能被装到 —— 这就是为什么测试挡在发布前面,而不是发完再补跑。

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.7.15 Aug 20264

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
sumengnan/aifix-code

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 21 Oct, 10:43 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:7e3fb3503c436060095a0dbea978225b6fd50662a682af5e1d3c983352c61094