你盯着Git提交记录的时候,有没有一种感觉:有些代码“干净”得不像人写的?异常分支处理得比规范还规范,注释却少得可怜,变量命名统一到仿佛出自同一个大脑。最近大半年,AI生成代码大量混入日常开发,这已经不再是一个要不要接受的问题,而是一个怎么区分、怎么追溯、怎么管理的问题。我花了几周业余时间捣鼓了一个小工具链,取名 Git-AI。它的作用不是阻止AI写代码,而是在Git历史里追踪AI生成代码的占比、位置和演变过程,方便评审、维护和复盘。这篇内容把整套设计和实操写出来,适合被“AI代码泡泡”困扰的技术管理者、团队负责人和独立开发者参考。
1. AI生成代码混进了协作流程,为什么必须盯着
1.1 团队协作里三个绕不开的痛点
先说最直接的痛点:代码评审。过去评审人重点看业务逻辑、边界条件、性能隐患,现在多了一个前置问题:这段代码到底是人写的,还是AI生成的?如果是AI生成的,评审重点会完全不同。AI生成代码最容易出问题的点不是“写得乱”,而是“写得太顺”,边界条件常常处理得很漂亮,但整体缺乏对业务上下文的真正理解。评审人如果不知道这段代码的来路,就只能用老思路看,反而容易漏掉“看起来合理但实际上是错误假设”的坑。
第二个痛点是维护溯源。三个月后某个线上问题要排查,你打开一个函数,它的实现很完整,但你不知道是谁在什么意图下写的,也不确定当时有没有经过人工确认。如果能在Git提交历史里标记出“这一段是AI生成的,当时人工做了哪些修改”,排查效率会高很多。没有追踪机制的话,所有代码在时间线上都是平的,AI生成的代码和人写的代码混在一起,等到出事再想查就晚了。
第三个痛点是质量度量失真。团队如果开始统计代码缺陷率、返工次数、模块复杂度,那么AI生成代码占比不同会导致数字完全没有横向可比性。某个人一个月写了大量AI辅助代码,表面产出很高,但后续Bug修复和逻辑返工可能都发生在这些代码上。没有来源标记,管理者看到的只是“这个人产出高”或者“这个模块Bug率异常”,无法进一步拆分原因。追踪AI生成代码,本质上是在给研发数据增加一个观察维度。
1.2 为什么选择“追踪”而不是“检测”
最开始我的想法很简单:写一个工具,对每段代码做一次“是不是AI生成”的判断,然后打标签。但真正用了几天就发现这个思路有问题。检测是静态的、一次性的,判定完就结束了;而代码是不断演进的。一段AI生成的代码可能被人工改过三轮,早已面目全非,但它的“出生属性”仍然影响着后续维护。只检测当前快照,无法回答最核心的问题:它从哪里来,中间经过多少次人工修正,现在的可信度是多少。
追踪的含义是把AI生成当作一个变更事件,记录在版本历史里。Git天生就是这个场景的载体。每一次提交都是一次变更,每条提交记录都带着作者、时间、父提交、文件差异,只要在这一层附加“是否AI生成”和“人工介入程度”两个字段,就能持续追踪。这个思路和代码本身是否被完全改写无关,它追踪的是变更来源,而不是内容指纹。
所以Git-AI的设计目标不是做一个魔法鉴定器,而是搭建一套围绕Git历史的“来源标注系统”。它能做到的极限是:告诉你这个提交里有多大比例的内容疑似AI生成,以及这个比例在后续提交里如何变化。在这个基础上,人的判断才有地方落脚。
1.3 谁最需要这套机制
如果你是独立开发者,自己写自己维护,这套机制用处相对有限,但如果你做开源项目,或者在一个三人以上的团队里协作,它的价值立刻显现。团队里通常存在三种角色:写代码的人、评审代码的人、对质量负责的人。写代码的人需要知道自己提交的AI含量,避免过度依赖生成结果;评审代码的人需要按风险分级分配精力;质量负责人需要从整体数据里发现趋势。Git-AI的产出是给这三种人共享的,它不是一个安全审计工具,而是一个协作基础设施。
2. 整体设计拆解:数据从哪来,标签怎么打,判定怎么做
2.1 设计原则:先记录,后判断,少打扰
工具链落地最怕的就是增加流程负担。如果要求开发者在每次提交时手动回答“这次用了AI吗”,前两次还会填,后面就变成机械勾选,数据完全失真。设计上应该尽量自动采集信号,把人工操作降到最低。
Git-AI的第一原则是“先记录”:用Git本身的钩子机制在提交时自动留下标记,不管后续判断准不准,原始证据先留下来。第二原则是“后判断”:不在提交时立刻给结论,而是定期离线分析,把评分结果和人工标记放在一起校准。第三原则是“少打扰”:平时开发者无感,只有在合并请求或专项复盘时才展示追踪报告。这样工具才不会成为一个被嫌弃的流程枷锁。
2.2 采集哪些信号:提交行为特征、代码文本特征、变更结构特征
要判断一次提交是否疑似AI生成,不能只盯代码文本。我把信号分成三组。
第一组是提交行为特征。AI编程助手介入后,提交节奏往往会发生变化。常见的表现有:一次提交包含大量文件且主题分散;commit message写得非常模板化,和代码内容相关性低;同一作者短时间内提交频率明显高于平时。这些特征可以用Git日志直接统计,不需要解析代码。
第二组是代码文本特征。AI生成的代码通常有几类可量化的痕迹:注释密度偏低,特别是文件头注释和复杂逻辑的段落注释少;变量命名过于规范但缺少缩写和业务痕迹;空行使用高度规律;异常处理模式重复度高,比如每个函数都用几乎一模一样的try-catch结构;注释风格像教科书,比如“检查参数是否合法,如果非法则抛出异常”这种废话式注释。这些信号没有单一是决定性的,但组合起来可以做初步评分。
第三组是变更结构特征。AI生成代码一次性写入时,新增代码的行数、圈复杂度、跨文件依赖度,和人工迭代代码有明显差异。人工改代码通常是增量式修改,每次只动一小块;AI生成往往是整块插入。通过对比新增代码的diff块大小和函数边界命中情况,可以判断这次变更是一次性生成还是渐进修改。
2.3 技术栈选择:轻脚本加SQLite,不用重模型
技术栈没有选择炫酷的方案,就是Python脚本加SQLite。 دلیل很直接:第一,不需要部署服务,开发机上本地跑就能完成扫描;第二,Git历史数据本身就是结构化的,日志和diff解析出来后直接入表;第三,规则评分可以先跑起来,后续想上更复杂的模型,表结构不需要大改。
代码结构大概是这样:
git-ai/ ├── collect.py # 拉取git log/diff并解析 ├── detect.py # 特征提取和评分 ├── report.py # 生成HTML/Markdown报告 ├── rules.json # 规则和阈值配置 └── data/ └── git_ai.db # SQLite事件库这个结构足够个人使用,也足够支撑几十个仓库的扫描任务。采集和分析分离的好处是,采集脚本可以挂在定时任务里每天跑,分析脚本只负责对数据库做查询和评分。
3. 核心实现:从Git提交记录到一份可读的AI代码追踪报告
3.1 从Git历史里提取变更单元
第一步是把仓库的提交历史变成结构化数据。用git log命令列出提交批次,然后用git diff拿每一个提交的详细变更。这里有一个容易踩的坑:不要直接用git diff对比所有提交的两两差异,要按提交的父提交来取增量,否则重复计算会非常严重。
我用的核心命令大概长这样:
git log --since="2025-01-01" --pretty=format:"%H|%an|%ad|%s" --date=iso > commits.txt # 对某个具体commit,取它相对父提交的diff git diff <parent_hash> <commit_hash> --stat git diff <parent_hash> <commit_hash> --numstat解析时重点关注三类字段:作者信息、提交时间、变更文件列表和每个文件的增删行数。这些字段最终会落到一张提交事件表里。需要特别处理的是合并提交,合并提交的diff可能是大量文件的汇总,直接纳入分析会产生很多误报,我选择默认跳过合并提交,只分析普通提交。
提取过程中最耗时的操作是git diff的计算,仓库大的时候很慢。优化办法是分批处理:按月份拉取提交列表,每次只解析200个提交,解析完存SQLite再处理下一批。这样中断了也能续跑。
3.2 用SQLite保存提交事件和代码特征
数据库设计不要搞复杂,核心两张表就够了。第一张表保存提交基本信息,第二张表保存每个文件维度的特征打分。
CREATE TABLE commits ( commit_hash TEXT PRIMARY KEY, author TEXT, author_email TEXT, commit_time TEXT, subject TEXT, ai_flag INTEGER DEFAULT 0, ai_score REAL DEFAULT 0, human_reviewed INTEGER DEFAULT 0 ); CREATE TABLE file_features ( id INTEGER PRIMARY KEY AUTOINCREMENT, commit_hash TEXT, file_path TEXT, added_lines INTEGER, deleted_lines INTEGER, comment_density REAL, avg_line_length REAL, exception_pattern_score REAL, brace_style_score REAL, ai_likelihood REAL );写入时需要注意保持幂等:同一个commit被重复扫描时,应该先删除旧记录再插入,否则重复运行脚本会导致数据翻倍。我给commit_hash加了唯一索引,写入前先INSERT OR IGNORE,如果发现已存在就更新特征字段,而不是追加记录。
这个表结构还有一个好处:未来的机器学习模型可以直接把file_features当作输入表,新增特征列不影响现有逻辑。
3.3 规则评分模型:先让规则说话,再让数据纠正规则
第一版判定我不建议直接训练模型,先用一组可解释的规则跑通流程。规则的意义在于:每个得分都能说清楚为什么,团队才能基于“为什么”去调整。一个可解释的低分,远胜于一个高置信度但没人看懂的黑盒结论。
我的初始规则表长这样:
| 特征 | 规则 | 分值 |
|---|---|---|
| AI标记 | 提交说明包含[ai]或[ai-generated]标记 | +40 |
| 注释密度 | 新增代码注释占比低于5% | +20 |
| 平均行长度 | 新增代码平均行长超过120字符 | +10 |
| 异常处理 | 超过70%新函数有完全相同的异常处理模式 | +15 |
| 提交规模 | 单次提交新增超过500行且文件数少于3个 | +15 |
| 人写标记 | 提交说明包含[human]标记 | -50 |
单次提交的最终得分就是把命中的规则分加起来。超过70分就标记为“疑似AI生成”,40到70分为“疑似辅助生成”,低于40分默认为人工编写。
举个例子。某次提交说明为“feat: 增加导出功能”,20个文件,新增1200行,注释只有4处,所有新增函数都有的try-catch段落,那么它命中AI标记0分、注释密度+20、提交规模+15、异常处理+15,总分50分。虽然没到70分,但已经处于“疑似辅助生成”区间,应该要求作者在评审时说明生成来源和人工修改范围。
评分公式很简单,真正的难点在于阈值校准。不同团队、不同语言、不同开发习惯,同一套阈值效果完全不一样。我建议先拿过去一两个月的人工标记提交做回放,算出分位点,再决定阈值。不要在第一天就把阈值定死。
3.4 生成追踪报告:从数据表到人话
评分完成后,最终要产出一份让不写代码的人也能看懂的追踪报告。我用Python脚本直接把SQLite里的数据渲染成Markdown和HTML。
报告包含四块内容:
- 整体概览:统计周期内提交总数、疑似AI生成提交数、AI生成代码行数占比。
- 文件维度排名:哪些文件或目录累计新增的疑似AI代码最多。
- 作者维度分布:每位开发者被标记为AI生成的提交数量和趋势。
- 和评审绑定的风险列表:高风险提交按分数倒序排列,推荐重点评审。
生成报告的核心查询是所有统计的核心:
SELECT file_path, SUM(added_lines) AS ai_added_lines, SUM(CASE WHEN ai_score >= 70 THEN added_lines ELSE 0 END) AS risky_lines FROM file_features WHERE commit_time >= ? GROUP BY file_path ORDER BY risky_lines DESC LIMIT 50;报告不要只展示“是不是AI生成”这个二元结论,一定要附上命中的规则明细。这样开发者在被标记后能第一时间明白为什么,也方便调整自己的提交方式。
4. 一轮真实复盘:追踪数据怎么真正用起来
4.1 先把最近30个提交人工标记一遍
规则跑之前,我先做了一个最笨但最有用的动作:从仓库里挑最近30个稳定提交,由我和另一个熟悉项目的老开发者各自独立标记“AI生成”“AI辅助”“人工编写”,然后对比结果。两个人标记不一致的地方,就当面讨论确认。
这30个标记结果不是为了训练模型,而是为了给规则阈值做标尺。比如我们团队历史提交的平均注释密度是9%,AI生成代码平均注释密度只有4%,那么“低于5%加20分”这个规则就比较合理。如果团队本身就不写注释,这个规则就得去掉或换一条。
这一步看起来费时间,实际一两个小时就够,但能省掉后面大量调参时间。没有任何一套公开的AI代码检测阈值可以直接套到你的项目上,项目历史基线才是最可靠的参考。
4.2 第一版规则跑出来的数据长什么样
这里展示一组脱敏后的模拟数据,是我在一个内部模拟项目X上跑出来的结果。统计周期为一个月,共分析了461次提交,结果如下:
| 指标 | 数值 |
|---|---|
| 总提交数 | 461 |
| 疑似AI生成提交 | 87 |
| 疑似AI辅助提交 | 119 |
| 纯人工提交 | 255 |
| AI生成代码占比 | 31.4% |
| 工具脚本目录疑似占比 | 52.8% |
| 业务核心代码疑似占比 | 12.6% |
| 需要重点评审的高风险提交 | 23 |
第一眼看到31.4%的时候,我还是有点意外。但更值得注意的是分布:工具脚本、测试数据生成脚本这类“低风险高重复”的代码,AI生成占比很高;核心业务代码占比反而不高。这说明团队的用法其实比较理性:用AI写外围代码,把核心逻辑留给人。如果这个比例反过来,AI生成集中出现在核心业务里,那才是真正需要警觉的信号。
另一个值得留意的现象是,很多被标记为“疑似AI生成”的提交,在后续一两天内都会跟着一个小的修复提交,修复内容通常是边界条件或参数校验。这是AI生成代码“能跑但缺细节”的直接证据。把这两类提交关联起来看,就可以计算AI代码的“追加修复率”,这个指标对质量评估非常有用。
4.3 和现有代码评审流程怎么衔接
追踪报告只有落到评审流程里才有意义。我是这样做的:
在合并请求创建阶段,要求提交者勾选一个来源声明:“本次变更中是否包含AI生成代码?如果有,请说明哪些文件、哪些部分经过人工修改。”这个字段不阻断提交,但会显示在评审页面的最前面。评审人看到“AI生成 + 人工未修改”的文件,优先看边界条件和上下文假设;看到“AI生成 + 人工已修改”的文件,重点看修改是否到位;看到“纯人工”的文件,按正常流程审。
实际运行时还加了一个轻量自动提醒:当某个合并请求里的高风险文件数超过三个,就自动在评审评论里发出提醒,列出具体文件路径和命中的规则。这个提醒不替代人工评审,只帮助评审人分配注意力。
4.4 反模式:刻意绕过追踪的几种做法
跑了几个月之后,我发现总有一些提交能“骗过”规则。最常见的是拆分提交:把AI生成的一大段代码拆成七八个小提交,每个提交都构造出合理的中间状态。这种拆分在行为特征上就很接近人工迭代,规则很难抓。
第二种常见反模式是改写法:把AI生成的注释删掉,变量名全局替换一遍,空行重新打乱,再人工加几条看似专业的业务注释。经过这种处理,文本特征基本失效,只能靠AST语义级别的方法才能发现模式相似性。
第三种是无感知反模式:开发者并不想隐瞒,但AI编程助手自动补全的代码和人工写的代码交织在一个提交里,连作者自己都分不清哪些是AI生成的。这种情况追踪工具会给出一个“混合得分”,但无法精确到行。
面对这些反模式,我的态度是:追踪工具的目的不是抓作弊,而是提高透明度。它有一个检测上限,这是所有基于统计特征工具的天然边界。如果团队文化本身不信任,再多规则也会被绕过。Git-AI真正适用的场景,是团队愿意公开讨论AI代码来源,而不是把追踪当追责工具。
5. 实操中的高频问题与排查笔记
5.1 误报率太高,怎么收敛
第一个版本跑下来,最容易被误报的是两类提交:批量重构提交和自动格式化提交。比如用某个自动重构工具统一调整全项目导入顺序,这类提交在“单次提交大量文件”“新增行数多”这两个特征上非常像AI生成,实际是完全的人工操作。
解决办法是加白名单机制:把自动化工具产生的提交类型单独打一个[tool]标记,比如“style: format”,在规则里直接给这类标记减分。同时把重构类提交的关键词(refactor、format、style、chore)作为豁免条件,命中这些词且没有其他明显AI文本特征时,不参与评分。
更长期的做法是分语言建模。业务代码、脚本、配置文件、测试代码的特征差异太大,统一阈值一定会误报。我把文件按扩展名分成五组,分别是py、js/ts、java、c/cpp、others,每组单独计算基线阈值。例如Python项目通常注释率较高,JavaScript项目平均行长度差异大,直接用同一套参数就违背了设计初衷。
5.2 多语言项目怎么处理差异
多语言仓库是规则系统最大的挑战。同一个项目的不同目录往往使用不同语言,语言之间注释习惯、行宽习惯、函数组织方式完全不同。
我建议至少分三档配置:
| 语言类型 | 注释密度阈值 | 平均行长度阈值 | 异常处理特征 |
|---|---|---|---|
| Python/脚本 | 低于5%加分 | 100字符 | 弱相关 |
| Java/C# | 低于8%加分 | 120字符 | 强相关 |
| 前端JS/TS | 低于3%加分 | 100字符 | 中相关 |
这个表格不是标准答案,只是参考。实际配置时,用前面提到的人工标记回放法,在每组代码上分别计算特征分布,然后设置各自的阈值。宁可每组样本少一点,也不要混在一起,因为混合数据产生的阈值往往对任何一组都不合适。
5.3 团队不愿意配合打标记,怎么办
要求每个人在提交说明里写[ai]标记,刚开始是行得通的,两周后就会开始漏。人的惰性不是靠自觉解决的,要把标记动作变成默认行为。
技术上有几个办法。在提交钩子里加一个交互提示:检测到提交信息里没有AI来源标记时,弹出问题“本次变更是否包含AI生成代码?是/否/不确定”,开发者必须选一个才能继续提交。把选择结果自动写入Git消息或者Git Notes。这样不增加多少负担,又能保证数据完整性。
如果团队抵触情绪严重,就先把工具定位从“门禁”改成“记录”。只把AI标记和评分写入数据库,不阻断任何流程,也不在评审里默认展示。等跑出第一个月报告,再用数据说话。大部分用数据说服人的场景,比用流程强制要顺利得多。
5.4 仓库很大,扫描速度慢怎么办
扫描一个大仓库,最容易耗时的不是规则计算,而是git diff和文本解析。我踩过最明显的坑是循环里对每个文件单独调用git blame,几千次调用下去,一个仓库能跑一整夜。
优化思路是减少Git命令调用次数。每一次提交只调用一次git diff,拿到所有文件的完整diff后再在内存里按文件拆分。按文件处理的代码在Python里用字符串分割就能做到,不要为了省事把每个文件再交给Git重新拿一遍改动。实践下来,原本一小时跑完的仓库,优化后十分钟左右就能完成。
另一个优化是增量扫描。git log的--since参数只回溯最近的时间窗口,配合SQLite里记录的上次扫描时间,每次只处理新增提交。历史仓库第一次全量扫描后,日常增量的开销几乎可以忽略。
5.5 代码隐私和出网边界
这里必须特别强调安全。Git-AI的所有特征提取都在本地完成,diff文本不会上传到任何外部服务。文本特征统计、规则评分、SQLite存储,全部离线可跑。很多团队对这个工具的第一个顾虑就是“代码会不会外传”,我把采集和存储做成完全本地化的,从架构上排除这个风险。
如果你后续想引入更复杂的AI分类模型,建议使用本地部署的开源模型,并且只把提取后的特征向量(不是原始代码)用于推理。不要用任何需要上传代码的在线检测服务来处理私有仓库,这是红线。规则版的优势恰恰在于它不需要任何外部依赖,一个Python环境就能在离线网络里跑完整套流程。
6. 后续扩展:从代码追踪走向研发改进
6.1 从文本特征升级到AST语义特征
文本特征有天然的盲区,只要变量名、注释、空行被刻意修改,所有统计信号都会失效。要提升检测能力,下一步应该从文本层走向AST语义层。
AST特征关注的是代码结构本身,而不是表面写法。AI生成代码在AST层面有几个特点:函数的嵌套深度相对均匀,分支条件的组合模式高度相似,循环内部缺乏对上下文的依赖。具体到实现,可以对新增代码解析成AST,统计每个函数的圈复杂度、分支节点类型分布、函数参数数量的方差。这些特征不随变量名变化而改变,鲁棒性比文本特征强很多。
但AST解析也有代价:每个语言都需要对应的解析器,工程量大很多。我的建议是先对项目里占比最高的两三种语言做,其他语言继续用文本规则兜底。不要追求所有语言一步到位。
6.2 建立AI代码健康分
追踪数据积累到一定量之后,可以做一个比“是不是AI生成”更有意义的指标:AI代码健康分。这个分数综合AI生成占比、人工修改比例、后续修复率、测试覆盖变化四个维度,衡量的是“AI代码在本项目的融入质量”。
比如某个功能模块,AI生成代码占60%,但提交时人工修改了其中30%的逻辑,之后三周只出现一次轻微修复,那它的AI代码健康分就可以很高。反之,如果模块里AI生成代码只占20%,但一个月内被返工三次,健康分反而是低的。健康分不是为了表扬或者批评某个模块,而是为了找出“AI代码在什么场景下好用、什么场景下不好用”的规律。
实际操作中,健康分可以用简单的加权公式,也可以后续接上更复杂的机器学习模型。关键不在算法,而在数据口径。要先把“AI来源标记”“人工修改痕迹”“后续修复记录”三块数据打通,健康分才是有意义的。
6.3 和研发度量挂钩,给管理者一个决策视角
追踪系统沉淀的数据,最终可以为研发管理提供决策支持。比如统计不同目录的AI代码占比与线上缺陷密度的关系,可以发现某些业务模块不适合AI高频介入;统计不同开发者的AI生成代码返工率,可以发现哪些人适合做AI代码的终审角色。
这里要强调的是:这类数据只能用来辅助决策,不能变成绩效指标。一旦“AI生成代码占比低”被当作目标,开发者就会想尽办法隐藏AI的使用痕迹,数据立刻失真,工具也就失去意义。我坚持的定位是:Git-AI是一面镜子,不是一把尺子。它让团队看清自己正在怎么使用AI,而不是告诉团队谁做得好、谁做得差。
用了一段时间之后,我最大的体会是:AI生成代码本身不是风险,不可见才是风险。任何工具都做不到100%精准识别AI代码,但Git-AI这种“把AI来源摊开在阳光下”的做法,比禁止AI写代码或者假装AI没写过代码都现实得多。如果团队能迈出第一步——在Git历史里诚实地为AI代码留下标记,后续的所有管理和质量动作,都会变得简单很多。