1、开发环境:MacOS v14.2、VSCode v1.98.1
2、开发工具:Flutter v3.7.6、Dart v2.19.3
3、统计项目:Dream App v1.5.0
一、定义AI生成代码的特定标记(注释)
1. 代码块标记:// @AI_START
、// @AI_END
// @AI_START
@override
void dispose() {
ScanCodeUtils().clear();
super.dispose();
}
// @AI_END
说明
:被 // @AI_START
和 // @AI_END
包裹的代码块,将被统计为AI生成代码。
2、单行代码标记:// @AI_GENERATE
@override
void dispose() {
ScanCodeUtils().clear(); // @AI_GENERATE
super.dispose();
}
说明
:被 // @AI_GENERATE
标记的代码行,将被统计为AI生成代码。
3、如何快速使用特定标记?
主流的开发工具都有代码片段(Code Snippets)
,开发者可以通过设定自定义的Snippets
来快速完成相关代码的填充工作,以下介绍下VSCode
的相关设置:
1、打开指令面板,搜索Snippets
;
2、选择Snippets: Insert Snippet
;
3、编写配置文件:
{
"@AI_START": {
"prefix": "@AI_START",
"body": [
"// @AI_START",
"",
"// @AI_END"
],
"description": "快速生成AI代码块首尾标记(代码块)"
},
"@AI_GENERATE": {
"prefix": "@AI_GENERATE",
"body": [
" // @AI_GENERATE",
],
"description": "快速生成AI代码标记(单行)"
}
}
4、使用效果:
二、统计代码覆盖率
此处主要统计新增代码
的AI覆盖率,所以,采取的方案是:获取指定日期区间内,git提交记录中,AI代码总行数占提交总行数的比重
。
以下是完整扫描脚本(python):
import subprocess # 进程管理模块
import re # 正则表达式模块
import csv # CSV 文件处理模块
import os # 操作系统接口模块
from datetime import datetime # 日期和时间处理模块
# 配置
start_date = "2025-03-12" # 开始日期(包含)
end_date = "2025-03-14" # 结束日期(不包含)
csv_path = "./ai_coverage.csv" # 输出文件
# 正则表达式
ai_start_pattern = re.compile(r'^\s*//\s*@AI_START')
ai_end_pattern = re.compile(r'^\s*//\s*@AI_END')
ai_generated_pattern = re.compile(r'//\s*@AI_GENERATED')
# 数据存储
total_ai = 0 # AI代码总行数
total_code = 0 # 总行数
# 以写入模式打开一个 CSV 文件,并确保文件以 UTF-8 编码保存,同时避免出现额外的空行。
with open(csv_path, 'w', newline='', encoding='utf-8-sig') as csvfile:
# 创建一个 CSV 写入器对象。这个对象可以将数据以 CSV 格式写入到文件中。
writer = csv.writer(csvfile)
# 写入中文表头(新增提交者列)
writer.writerow(["提交时间", "提交哈希", "提交者", "AI代码行", "总代码行", "覆盖率"])
# 执行外部命令,并捕获命令的标准输出(stdout)
commits = subprocess.check_output(
['git', 'log', '--since', start_date, '--until', end_date, '--pretty=format:%H|%cd|%an'],
stderr=subprocess.STDOUT
).decode().split('\n')
# git log:查看 Git 提交记录。
# --since start_date:指定开始时间,获取从该时间之后的提交。
# --until end_date:指定结束时间,获取到该时间之前的提交。
# --pretty=format:%H|%cd|%an:自定义输出格式:
# %H:提交的完整哈希值。
# %cd:提交日期。
# %an:作者名称。
# 使用 | 作为字段分隔符。
for commit_line in commits:
# 跳过空行或仅包含空白字符的行
if not commit_line.strip():
continue
# commit_line 格式如下:
# f30bea9c145dffd7c041b31bb2d43b06312fd9f0|Thu Mar 13 09:33:13 2025 +0800|liuyongsheng
# 解析新增的提交者字段
commit_hash, raw_date, author = commit_line.split('|', 2)
commit_time = datetime.strptime(raw_date.strip(), '%a %b %d %H:%M:%S %Y %z')
formatted_time = commit_time.strftime('%Y-%m-%d %H:%M:%S')
# 获取指定提交的详细信息,并将输出解析为 Python 列表。
diff = subprocess.check_output(['git', 'show', commit_hash]).decode().split('\n')
# 代码行格式如下
# diff 是一个数组,包含了该时间区间内,提交记录详细信息
ai_lines = 0
code_lines = 0
in_ai_block = False
for line in diff:
# line 格式如下:
# " class ConstValue {",
# " /// 环境标识位(1:测试 2:预发 3:正式)",
# "- static int environment = 2;",
# "+ static int environment = 3;",
# " ",
# "--- a/lib/common/values/values.dart",
# "+++ b/lib/common/values/values.dart",
if line.startswith('+') and not line.startswith('+++'):
code_lines += 1
content = line[1:]
if ai_start_pattern.search(content):
ai_lines += 1
in_ai_block = True
elif ai_end_pattern.search(content):
ai_lines += 1
in_ai_block = False
elif ai_generated_pattern.search(content):
ai_lines += 1
elif in_ai_block:
ai_lines += 1
# 写入数据
coverage = (ai_lines / code_lines * 100) if code_lines > 0 else 0
writer.writerow([
formatted_time,
commit_hash[:7],
author.strip(), # 新增的提交者列
ai_lines,
code_lines,
f"{coverage:.2f}%"
])
# 累计统计
total_ai += ai_lines
total_code += code_lines
# 添加统计行(提交者列留空)
total_coverage = (total_ai / total_code * 100) if total_code > 0 else 0
writer.writerow([
f"统计时段: {start_date} 至 {end_date}",
"汇总数据",
"", # 提交者列留空
total_ai,
total_code,
f"{total_coverage:.2f}%"
])
# 自动打开Finder
csv_abs_path = os.path.abspath(csv_path)
if os.path.exists(csv_abs_path):
subprocess.call(['open', '-R', csv_abs_path])
输出的文件如下:
三、校验提交内容AI标记不成对的问题
步骤 1:创建钩子文件
1、进入 Git 项目的 .git/hooks 目录:
cd your_project/.git/hooks
2、创建 pre-commit 文件(无后缀):
touch pre-commit
步骤 2:编辑钩子内容
#!/bin/bash
# 定义需要检查的标记
START_MARKER='// @AI_START'
END_MARKER='// @AI_END'
ERROR=0
SEPARATOR="-------------------------"
# 遍历暂存区中的每个文件
while read -r file; do
# 跳过二进制文件
if grep -q "^Binary file" <<< "$(git diff --cached -- "$file")"; then
continue
fi
# 统计新增行中的标记数量(自动去除前导空格)
start_count=$(git diff --cached -- "$file" | grep -E '^\+' | grep -F -o "$START_MARKER" | wc -l | tr -d ' ')
end_count=$(git diff --cached -- "$file" | grep -E '^\+' | grep -F -o "$END_MARKER" | wc -l | tr -d ' ')
# 检查标记是否成对
if [ "$start_count" -ne "$end_count" ]; then
echo "$SEPARATOR"
echo -e "错误: 文件 '$file' 存在未闭合的标记!"
echo -e "新增的【$START_MARKER】数量: $start_count"
echo -e "新增的【$END_MARKER】数量: $end_count"
ERROR=1
fi
done < <(git diff --cached --name-only)
# 阻止提交若存在错误
if [ $ERROR -ne 0 ]; then
echo "$SEPARATOR"
echo -e "\n提交被阻止,请修复标记不匹配的问题。"
exit 1
fi
exit 0
步骤 3:赋予执行权限
chmod +x pre-commit
步骤 4:测试提交效果
存在的问题:
1、单次提交不要嵌套使用 // @AI_START
、 // @AI_START
,以下是错误示例:
// @AI_START
class TestClass1 {
void test() {
print('test');
}
// @AI_START
void test() {
print('test');
}
// @AI_END
}
// @AI_END