AI代码统计插件 - 完整技术文档 v1.0.0
📋 项目概述
AI代码统计插件是一个功能强大的VSCode扩展,通过智能Git作者切换和精确diff算法,实时统计AI生成代码与人工编写代码的占比。插件采用最后修改者原则,只统计未提交的代码修改,提供准确的代码贡献度分析。
🎯 核心价值
- 代码审查依据:精确区分AI与人工代码,为代码审查提供数据支撑
- 开发效率量化:实时量化AI工具对开发效率的实际提升
- 团队协作透明:清晰展示不同开发者和AI工具的代码贡献
- 质量分析基础:为AI生成代码vs人工代码的质量分析提供数据基础
- 开发习惯洞察:通过详细日志了解AI辅助编程的使用模式
🚀 功能特性
✨ 实时监听与统计
- 文档变化监听:基于VSCode文档变化事件的实时监听
- Git作者检测:自动获取当前Git作者,智能区分AI与人工身份
- 精确Diff算法:采用Myers编辑距离算法,精确计算代码变化
- 行级追踪:精确到行的代码归属和修改历史跟踪
🌐 网络请求功能(新增)
- 仓库信息获取:自动获取Git远程仓库地址和解析仓库组名、仓库名
- API接口上报:支持将仓库信息和统计数据上报到指定API接口
- 智能地址解析:支持HTTPS、SSH、阿里内部GitLab等多种Git地址格式
- 配置化管理:通过VSCode设置配置API接口地址,灵活可控
🧠 核心算法特性
- 最后修改者原则:每行代码归属于最后修改它的人
- 智能还原检测:代码还原到Git HEAD状态时自动移除修改标记
- 净贡献计算:正确处理删除操作,计算真实的代码贡献度
- 移动检测算法:识别代码行移动和重构,避免错误统计
💾 数据管理
- 工作区隔离:不同工作区的统计数据完全独立
- 跨会话持久化:插件重启后自动恢复统计状态
- 缓存优化:Git操作缓存和防抖机制,提升性能
- 数据完整性验证:自动验证和清理无效数据
🔧 智能Husky检测与安装
插件现在支持智能检测项目的husky状态,并提供用户友好的安装选项:
检测场景
非Node.js项目 (无package.json)
- 询问是否直接设置Git钩子
- 提供"是"、"否"、"不再询问"选项
Node.js项目未安装husky
- 自动检测包管理器(npm/yarn/pnpm)
- 询问是否安装husky并设置钩子
- 提供"是"、"否"、"总是自动安装"、"不再询问"选项
已安装husky的项目
- 显示当前husky版本
- 询问是否设置AI代码统计钩子
- 提供"是"、"否"、"不再询问"选项
配置选项
{
"aiCodeStats.autoSetupGitHooks": true, // 是否启用自动设置Git钩子
"aiCodeStats.autoInstallHusky": false, // 是否自动安装husky而不询问
"aiCodeStats.showDetailedInstallLogs": true // 是否显示详细的安装日志
}
📊 实时统计功能
- 状态栏显示:实时显示当前AI生码率
- 文件级统计:每个文件的AI vs 人工代码比例
- 项目级汇总:整个项目的代码统计信息
🔄 Git集成功能
- commit消息增强:自动在commit消息中添加统计信息
- 作者智能切换:AI编码时自动切换到AI作者
- 状态栏重置:commit后自动重置统计信息
🎮 操作检测
- 粘贴检测:Ctrl+V粘贴操作检测
- 退格检测:Backspace删除操作检测
- 输入检测:键盘输入实时检测
- 文件切换:自动检测文件切换并初始化统计
🎛️ 实时监听与拦截系统
核心拦截机制
插件通过拦截VSCode的原始操作命令,实现对人工编辑行为的精确检测:
拦截的关键操作
操作类型 |
拦截命令 |
检测目的 |
处理逻辑 |
键盘输入 |
type |
检测人工打字 |
先切换到人工作者,再执行输入 |
粘贴操作 |
editor.action.clipboardPasteAction |
检测剪贴板粘贴 |
先切换到人工作者,再执行粘贴 |
回车换行 |
editor.action.insertLineAfter |
检测换行操作 |
先切换到人工作者,再插入换行 |
删除操作 |
deleteLeft , deleteRight |
检测删除字符 |
先切换到人工作者,再执行删除 |
文件切换 |
onDidChangeActiveTextEditor |
检测文件焦点 |
更新编辑上下文状态 |
人工操作检测算法
// 人工编辑检测的核心逻辑
interface EditingContext {
isKeyboardActive: boolean; // 键盘活动状态
cursorInFile: boolean; // 光标在文件内
lastActivity: number; // 最后活动时间
preparingEdit: boolean; // 准备编辑状态
}
// 检测条件:同时满足以下三个条件 = 人工操作
const isManualEdit = context.cursorInFile &&
context.isKeyboardActive &&
!context.preparingEdit;
防抖机制
为避免重复拦截,实现了50ms防抖:
function shouldSkipCommand(commandName: string, fileName: string): boolean {
const key = `${commandName}:${fileName}`;
const now = Date.now();
const lastTime = commandDebounce.get(key) || 0;
if (now - lastTime < 50) { // 50ms防抖
return true;
}
commandDebounce.set(key, now);
return false;
}
实时切换机制
自动作者切换逻辑
// 检测到人工操作时的处理流程
async function handleManualEditingIntent(fileName: string): Promise<void> {
const manualAuthor = getManualGitAuthor();
if (!manualAuthor) return;
// 1. 强制获取最新Git作者
clearGitCache(fileName);
const currentAuthor = await getCurrentGitAuthor(fileName);
// 2. 如果不是手动作者,立即切换
if (currentAuthor !== manualAuthor) {
const success = await switchGitAuthor(fileName, manualAuthor);
if (success) {
outputChannel.appendLine(`✅ 检测到人工操作,已切换到: ${manualAuthor}`);
}
}
}
事件监听架构
┌─────────────────────────────────────────────────────────────┐
│ VSCode原始事件 │
│ type │ paste │ deleteLeft │ deleteRight │ insertLineAfter │
└─────────────────────┬───────────────────────────────────────┘
│ 拦截
┌─────────────────────▼───────────────────────────────────────┐
│ 拦截处理层 │
│ • 防抖检查 • 人工检测 • Git作者切换 • 命令转发 │
└─────────────────────┬───────────────────────────────────────┘
│ 处理完成
┌─────────────────────▼───────────────────────────────────────┐
│ 原始命令执行 │
│ 手动执行原始操作逻辑,确保编辑正常进行 │
└─────────────────────────────────────────────────────────────┘
🔬 技术架构
核心数据结构
// 行状态定义
interface LineState {
content: string; // 行内容
author: string; // 作者
isModified: boolean; // 是否被修改
lastModified?: number; // 最后修改时间
}
// 文件状态定义
interface FileState {
originalContent: string; // Git HEAD的原始内容
currentLineStates: Map<number, LineState>; // 当前行状态映射
lastKnownContent: string; // 最后已知内容
lastGitCommitHash: string; // 最后Git提交哈希
lastModified: number; // 最后修改时间
isProcessing: boolean; // 是否正在处理中
}
// Diff变化类型
interface DiffChange {
type: 'added' | 'deleted' | 'modified' | 'unchanged';
originalLine?: number; // 原始行号
currentLine?: number; // 当前行号
content: string; // 行内容
}
系统架构图
┌─────────────────────────────────────────────────────────────┐
│ VSCode 扩展入口 │
└─────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────────────┐
│ 事件监听层 │
│ • 文档变化监听 • 编辑上下文检测 • 防抖处理 │
└─────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────────────┐
│ Git集成层 │
│ • 作者检测 • 原始内容获取 • 缓存管理 • 状态验证 │
└─────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────────────┐
│ 核心算法层 │
│ • Myers Diff算法 • 行状态管理 • 智能归属 • 边界检测 │
└─────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────────────┐
│ 数据持久化层 │
│ • 工作区隔离 • 状态保存 • 数据恢复 • 完整性验证 │
└─────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────────────┐
│ UI展示层 │
│ • 状态栏显示 • 详细报告 • 实时日志 • 命令面板 │
└─────────────────────────────────────────────────────────────┘
🔧 精确Diff算法详解
Myers编辑距离算法
插件采用Myers算法计算两个版本之间的最小编辑距离,这是Git和大多数版本控制系统使用的核心算法。
算法复杂度
- 时间复杂度:O(ND),其中N为文件行数,D为编辑距离
- 空间复杂度:O(N)
- 最优性:保证找到最小编辑距离的最优路径
核心实现逻辑
function computeAccurateDiff(originalLines: string[], currentLines: string[]): DiffChange[] {
const m = originalLines.length;
const n = currentLines.length;
const MAX = m + n;
// Myers算法的核心数据结构
const v = new Array(2 * MAX + 1).fill(-1);
const trace: number[][] = [];
// 前向搜索找到最短编辑路径
for (let d = 0; d <= MAX; d++) {
const currentV = [...v];
trace.push(currentV);
for (let k = -d; k <= d; k += 2) {
let x: number;
if (k === -d || (k !== d && v[k - 1 + MAX] < v[k + 1 + MAX])) {
x = v[k + 1 + MAX];
} else {
x = v[k - 1 + MAX] + 1;
}
let y = x - k;
// 处理相同行的情况
while (x < m && y < n && originalLines[x] === currentLines[y]) {
x++;
y++;
}
v[k + MAX] = x;
if (x >= m && y >= n) {
return backtrackDiff(originalLines, currentLines, trace, x, y, d);
}
}
}
return [];
}
智能行匹配算法
为了处理代码移动和重构,插件实现了智能行匹配算法:
function findPreviousLineState(fileState: FileState, content: string, preferredLineNum: number): LineState | undefined {
// 1. 优先匹配相同位置的行
const exactMatch = fileState.currentLineStates.get(preferredLineNum);
if (exactMatch && exactMatch.content === content) {
return exactMatch;
}
// 2. 在附近行中查找相同内容
const nearbyRange = 3;
for (let i = 1; i <= nearbyRange; i++) {
const above = fileState.currentLineStates.get(preferredLineNum - i);
const below = fileState.currentLineStates.get(preferredLineNum + i);
if (above && above.content === content) return above;
if (below && below.content === content) return below;
}
// 3. 全文搜索相同内容
for (const [lineNum, lineState] of fileState.currentLineStates) {
if (lineState.content === content) {
return lineState;
}
}
return undefined;
}
还原检测机制
三重比较算法确保准确检测代码还原:
// 还原检测:当前内容 vs 上次内容 vs Git HEAD原始内容
function detectReversion(current: string, lastKnown: string, original: string): boolean {
return current === original && lastKnown !== original;
}
🎯 极端边界情况处理
场景分类与处理策略
1. 基础修改删除场景
场景 |
描述 |
处理逻辑 |
期望结果 |
场景1 |
AI添加1行,人工删除1行 |
删除不计入统计,只看最终状态 |
0% AI,0% 人工 |
场景2 |
AI添加1行,人工添加1行,AI删除人工的1行 |
最终AI有1行,人工0行 |
100% AI,0% 人工 |
场景3 |
修改后完全还原到原始状态 |
检测到还原,清除所有标记 |
0% AI,0% 人工 |
2. 复杂编辑场景
场景 |
描述 |
核心算法 |
处理结果 |
场景4 |
批量删除操作 |
净贡献计算:只统计现存修改行 |
AI 3行,人工5行,总共8行 |
场景5 |
复制粘贴10行重复内容 |
不进行重复检测,按实际行数统计 |
AI贡献10行 |
场景6 |
格式化:1行变2行 |
格式化算正常代码贡献 |
AI贡献2行 |
场景7 |
重构:函数内容变化 |
基于diff算法识别实际修改行 |
AI修改1行 |
3. 操作撤销场景
场景 |
描述 |
检测机制 |
最终统计 |
场景8 |
撤销重做操作 |
监听最终状态变化 |
按最终结果统计 |
场景11 |
覆盖修改同一行 |
最后修改者原则 |
归属最后修改者 |
场景12 |
部分还原到原始状态 |
三重比较检测 |
还原行不算修改 |
4. 特殊编辑场景
场景 |
描述 |
算法处理 |
统计规则 |
场景23 |
回车换行插入空行 |
行级diff算法 |
算1行新增 |
场景24 |
删除换行合并两行 |
Myers算法识别 |
算1行修改 |
场景25 |
空行处理 |
空行算代码行 |
计入统计 |
场景26 |
自动格式化操作 |
触发者归属 |
算触发者贡献 |
5. Git状态变化场景
场景 |
描述 |
检测机制 |
处理策略 |
Git Stash |
代码被暂存 |
监听Git状态变化 |
重置统计为0 |
Git Reset |
硬重置到HEAD |
检测commit hash变化 |
重新初始化 |
Git Pull |
拉取远程更新 |
比较原始内容变化 |
只统计新修改 |
边界情况算法实现
// 场景检测与处理
async function handleEdgeCase(fileName: string, document: vscode.TextDocument): Promise<void> {
const fileState = fileStates.get(fileName);
if (!fileState) return;
const currentContent = document.getText();
const originalContent = fileState.originalContent;
const lastKnownContent = fileState.lastKnownContent;
// 1. 检测完全还原场景
if (currentContent === originalContent) {
if (lastKnownContent !== originalContent) {
outputChannel.appendLine(`🔄 文件 ${fileName} 已还原到原始状态`);
// 清除所有修改标记
fileState.currentLineStates.clear();
fileState.lastKnownContent = currentContent;
return;
}
}
// 2. 检测Git状态变化
const currentGitHash = await getGitCommitHash(fileName);
if (currentGitHash !== fileState.lastGitCommitHash) {
outputChannel.appendLine(`🔄 检测到Git状态变化: ${fileName}`);
await reinitializeFileState(fileName, document);
return;
}
// 3. 处理正常diff场景
await processNormalDiff(fileName, document);
}
📊 统计算法设计
净贡献计算公式
净贡献 = 新增行数 + 修改行数 - 被删除行数
占比计算算法
function calculatePercentages(): { ai: number, human: number } {
let totalLines = 0;
let aiLines = 0;
const aiAuthors = getAIAuthors();
// 统计所有文件的净贡献
for (const [fileName, fileState] of fileStates) {
for (const [lineNum, lineState] of fileState.currentLineStates) {
if (lineState.isModified) {
totalLines++;
if (aiAuthors.has(lineState.author)) {
aiLines++;
}
}
}
}
if (totalLines === 0) {
return { ai: 0, human: 0 };
}
const aiPercentage = (aiLines / totalLines) * 100;
const humanPercentage = 100 - aiPercentage;
return {
ai: Math.round(aiPercentage * 10) / 10,
human: Math.round(humanPercentage * 10) / 10
};
}
删除处理逻辑
// 删除操作不增加删除者的贡献,只移除被删除行的统计
function handleDeletedLines(deletedLines: DiffChange[], fileName: string): void {
const fileState = fileStates.get(fileName);
if (!fileState) return;
for (const change of deletedLines) {
if (change.type === 'deleted' && change.originalLine) {
// 从统计中移除被删除的行
fileState.currentLineStates.delete(change.originalLine);
outputChannel.appendLine(
`🗑️ 删除了第${change.originalLine}行: "${change.content.substring(0, 50)}..."`
);
}
}
}
🔧 V5性能优化设计
多层级缓存系统
L1:函数级缓存(withCache装饰器)
interface CacheEntry<T> {
value: T;
timestamp: number;
ttl: number; // 生存时间(默认30秒)
}
// 高阶函数包装,为任何异步函数添加缓存
const getCurrentGitAuthor = withCache(_getCurrentGitAuthor, getCacheTimeout());
const getGitOriginalContent = withCache(_getGitOriginalContent, getCacheTimeout());
L2:Git操作缓存
// Git命令结果缓存,避免重复执行相同操作
let gitCache = new Map<string, CacheEntry<any>>();
// 自动过期清理(每分钟执行)
function clearExpiredCache(): void {
const now = Date.now();
let cleared = 0;
for (const [key, entry] of gitCache.entries()) {
if (now - entry.timestamp > entry.ttl) {
gitCache.delete(key);
cleared++;
}
}
outputChannel.appendLine(`🧹 清理过期缓存: ${cleared}个`);
}
L3:文档变化防抖
// 100ms防抖处理,批量处理文档变化
let documentChangeTimer: NodeJS.Timeout | undefined;
let pendingChanges = new Map<string, vscode.TextDocument>();
function debounceDocumentChange(document: vscode.TextDocument): void {
const fileName = document.fileName;
if (documentChangeTimer) {
clearTimeout(documentChangeTimer);
}
pendingChanges.set(fileName, document);
documentChangeTimer = setTimeout(async () => {
// 批量处理所有待处理的文档变化
for (const [fileName, doc] of pendingChanges) {
await processDocumentChangeV5(fileName, doc);
}
pendingChanges.clear();
documentChangeTimer = undefined;
}, 100);
}
并发控制机制
Gate函数(防重复执行)
// 同一参数的异步操作只执行一次,其他调用等待结果
function gateAsync<T extends (...args: any[]) => Promise<any>>(fn: T): T {
return ((...args: any[]) => {
const key = JSON.stringify(args);
// 如果已有相同操作在执行,直接返回Promise
if (pendingGitOperations.has(key)) {
return pendingGitOperations.get(key)!;
}
const promise = fn(...args);
pendingGitOperations.set(key, promise);
// 操作完成后清理
promise.finally(() => pendingGitOperations.delete(key));
return promise;
}) as T;
}
操作队列管理
// 防止同时处理同一文件的多个操作
let processingQueue = new Set<string>();
async function processDocumentChangeV5(fileName: string, document: vscode.TextDocument): Promise<number[]> {
// 防止重复处理
if (processingQueue.has(fileName)) {
return [];
}
processingQueue.add(fileName);
try {
// 处理逻辑...
return changedLines;
} finally {
processingQueue.delete(fileName);
}
}
内存优化策略
文件大小限制
function getMaxFileSize(): number {
return getConfiguration().get<number>('maxFileSize', 5000);
}
// 跳过大文件处理,避免内存溢出
if (document.lineCount > getMaxFileSize()) {
outputChannel.appendLine(`⏭️ 跳过大文件 (${document.lineCount}行): ${fileName}`);
return;
}
定期清理机制
// 每分钟自动清理过期缓存
const cacheCleanupInterval = setInterval(clearExpiredCache, 60000);
// 插件停用时清理所有资源
export function deactivate() {
clearInterval(cacheCleanupInterval);
gitCache.clear();
fileStates.clear();
editingContexts.clear();
}
性能监控
操作耗时统计
async function processDocumentChangeV5(fileName: string, document: vscode.TextDocument): Promise<number[]> {
const startTime = Date.now();
try {
// 处理逻辑...
return changedLines;
} finally {
const duration = Date.now() - startTime;
if (duration > 100) {
outputChannel.appendLine(`⚠️ 处理耗时较长: ${fileName} - ${duration}ms`);
}
}
}
---
## ⚙️ 详细配置指南
### 必要配置检查清单
#### ✅ 第一步:AI作者列表配置
```json
{
"aiCodeStats.aiAuthors": [
"ai111", // 推荐:统一的AI标识
"AI",
"ChatGPT",
"Claude",
"Copilot",
"cursor-ai", // Cursor的AI助手
"ai"
]
}
重要提醒:
- 确保你在prompt中使用的AI作者名称在此列表中
- 建议团队统一使用
ai111
作为标准AI标识
- 可以随时添加新的AI工具名称
✅ 第二步:手动作者配置
{
"aiCodeStats.manualGitAuthor": "张三"
}
配置说明:
- 设置为你的真实Git用户名
- 插件检测到人工操作时会自动切换到此作者
- 留空则不进行自动切换
✅ 第三步:验证配置生效
- 打开输出面板:
查看
→ 输出
→ AI Code Stats V5
- 查看状态栏显示当前Git作者
- 进行一次编辑操作,观察日志输出
✅ 第四步:配置网络请求功能(可选)
{
"aiCodeStats.apiUrl": "https://your-api-endpoint.com/api/repository"
}
配置说明:
- 设置你的API接口地址用于接收仓库信息
- 支持HTTPS和HTTP协议
- 留空或使用默认值则不启用网络请求功能
🌐 网络请求功能详解
支持的Git地址格式:
- HTTPS格式:
https://github.com/user/repo.git
- SSH格式:
git@github.com:user/repo.git
- 阿里内部:
http://gitlab.alibaba-inc.com/group/repo.git
发送的数据格式:
{
"group": "message-social-front",
"repo": "ai-code-plugin",
"timestamp": "2024-01-01T12:00:00.000Z",
"currentAuthor": "ai-鹤傲",
"fileCount": 15,
"pluginVersion": "1.0.0"
}
使用方法:
- 使用命令面板 (
Ctrl+Shift+P
)
- 搜索并执行
AI Code Stats: 发送仓库信息
- 插件会自动获取Git仓库信息并发送到配置的API接口
错误处理:
- 如果未配置API地址,会提示配置
- 如果不是Git仓库,会提示错误
- 网络请求失败会显示详细错误信息
配置验证方法
快速测试流程
- 创建测试文件:新建一个
.js
文件
- AI生成代码:使用AI助手生成几行代码
- 检查状态栏:应显示AI占比
- 人工编辑:手动添加一行代码
- 验证切换:检查日志是否显示作者切换
故障排查
# 检查当前Git配置
git config user.name
git config --global user.name
# 验证Git仓库状态
git status
git log --oneline -n 5
🔍 完整配置选项
{
"aiCodeStats.aiAuthors": {
"type": "array",
"default": ["ai111", "AI", "ChatGPT", "Claude", "Copilot", "ai"],
"description": "AI作者名称列表,用于识别AI生成的代码"
},
"aiCodeStats.enabled": {
"type": "boolean",
"default": true,
"description": "是否启用AI代码统计功能"
},
"aiCodeStats.autoExport": {
"type": "boolean",
"default": false,
"description": "是否自动导出统计数据到项目根目录"
},
"aiCodeStats.preCommitHook": {
"type": "boolean",
"default": true,
"description": "是否启用pre-commit hook,将统计信息添加到commit消息"
},
"aiCodeStats.manualGitAuthor": {
"type": "string",
"default": "",
"description": "手动编辑时的Git作者名称。当检测到手动编辑时,会自动切换到此作者"
},
"aiCodeStats.maxFileSize": {
"type": "number",
"default": 5000,
"description": "监控文件的最大行数限制,超过此限制的文件将被跳过"
},
"aiCodeStats.cacheTimeout": {
"type": "number",
"default": 30000,
"description": "Git操作缓存超时时间(毫秒),用于优化性能"
},
"aiCodeStats.apiUrl": {
"type": "string",
"default": "https://your-api-endpoint.com/api/repository",
"description": "仓库信息上报接口地址,用于网络请求功能"
}
}
命令系统
命令 |
功能 |
说明 |
Toggle AI Code Stats |
启用/禁用插件 |
可以临时关闭统计功能 |
Show AI Code Report |
显示详细报告 |
展示完整的统计数据和分析 |
Reset AI Code Stats |
重置统计 |
清空当前工作区的所有统计数据 |
Export AI Code Stats |
导出数据 |
将统计数据导出为JSON格式 |
Send Repository Info |
发送仓库信息 |
获取Git仓库信息并发送到配置的API接口 |
Verify Git Status Consistency |
验证一致性 |
检查Git状态与插件状态的一致性 |
Clear Memory Cache |
清空缓存 |
清除所有内存缓存,强制重新计算 |
🚀 完整使用指南
安装和配置流程
步骤1:插件安装
VSCode安装:
# 下载项目中的 .vsix 文件
code --install-extension ai-code-stats-x.x.x.vsix
导入到Cursor:
- 打开Cursor
- 进入
Settings
→ General
→ Import from VSCode
- 选择导入扩展和配置
步骤2:配置AI全局Prompt
在Cursor的User Rules中添加以下prompt模板:
# AI代码生成时自动切换Git作者身份
每当你要生成代码时,请先执行以下命令切换Git作者身份:
```bash
git config user.name "ai111"
[此处为你的具体AI使用规则和要求]
重要提醒:
- 生成代码前必须先切换Git作者为ai111
- 这样可以准确统计AI生成的代码占比
- 请确保每次都执行此命令
### 完整工作流程
#### AI生成代码链路
1. **用户发起AI请求** → 触发全局prompt
2. **AI自动执行** `git config user.name "ai111"`
3. **AI生成代码** → 插件检测到代码变化
4. **插件识别AI作者** → 统计AI贡献
5. **状态栏实时更新** → 显示AI占比
#### 人工编辑代码链路
1. **用户开始编辑** → 插件检测到人工操作
2. **插件自动切换** → `git config user.name "张三"`
3. **人工编写代码** → 插件统计人工贡献
4. **状态栏实时更新** → 显示人工占比
### ⚠️ 重要注意事项
- **避免并发编辑**:AI生成代码过程中,人工最好不要同时修改代码
- **配置手动作者**:在插件设置中配置`manualGitAuthor`为你的Git用户名
- **AI作者列表可配置**:在`aiCodeStats.aiAuthors`中添加你使用的AI工具名称
- **Git仓库要求**:项目必须初始化Git仓库(`git init`即可,无需远程仓库)
### Git作者切换工作流
```bash
# 使用AI工具时
git config user.name "ai111"
# 人工编写代码时
git config user.name "张三"
# 查看当前作者
git config user.name
高级使用技巧
1. 自动化Git Hooks
# 安装husky
npm install husky --save-dev
# 设置pre-commit hook
npx husky add .husky/pre-commit "code --command aiCodeStats.export"
2. 团队协作配置
// .vscode/settings.json
{
"aiCodeStats.aiAuthors": [
"ai111",
"team-ai-tool",
"copilot-team"
],
"aiCodeStats.autoExport": true,
"aiCodeStats.preCommitHook": true
}
3. CI/CD集成
# .github/workflows/ai-stats.yml
name: AI Code Statistics
on: [push, pull_request]
jobs:
ai-stats:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Analyze AI Code Stats
run: |
if [ -f .ai-code-stats.json ]; then
echo "AI Code Statistics:"
cat .ai-code-stats.json | jq '.summary'
fi
💾 数据存储与状态管理
存储机制详解
数据存储位置
// VSCode GlobalState存储
const workspaceKey = `aiCodeStats.fileStates.${workspaceRoot}`;
await context.globalState.update(workspaceKey, data);
// 存储格式
interface StoredData {
fileStates: Map<string, FileState>; // 文件状态映射
lastSaved: number; // 最后保存时间
version: string; // 数据版本
}
存储内容
- 只存储状态信息:行号、作者、修改标记等元数据
- 不存储代码内容:确保代码安全,不会泄露
- 工作区隔离:不同项目的数据完全分离
- 无跨设备同步:数据存储在本地VSCode配置中
状态监控方式
1. 状态栏实时显示
$(person) 张三 | AI:65.3% 人工:34.7% (124行)
2. 输出面板详细日志
打开方式:查看
→ 输出
→ 选择 AI Code Stats V5
关键日志示例:
📋 V5初始化文件: main.ts
当前作者: ai111
发现15行已修改,等待编辑时归属
⌨️ 检测到文件修改输入: "console.log"
✅ 检测到人工操作,已切换到: 张三
🔍 增量diff分析: 3个变化
➕ 检测到第25行新增
📊 当前统计: AI:65.3% 人工:34.7% (124行)
Git状态处理策略
初始化时Git状态不Clean的处理
现有机制:
// V5处理逻辑:标记为已修改但不归属
if (isAlreadyModified) {
lineStates.set(lineNum, {
content: currentLine,
author: '', // 不归属给任何人
isModified: true, // 标记为已修改
lastModified: Date.now()
});
}
建议改进方案:
检测到未提交修改时弹出提示:
"检测到当前文件有未提交的修改,插件将从下一次编辑开始统计。
建议先提交当前修改或使用'Reset AI Code Stats'重新开始统计。"
提供快速操作选项:
继续使用
- 采用现有逻辑,不归属已有修改
重置统计
- 清空所有统计,从0开始
归属当前作者
- 将所有未提交修改归属给当前Git作者
🔧 Git操作详解
插件执行的Git命令列表
Git命令 |
用途 |
执行时机 |
错误处理 |
git config user.name |
获取当前作者 |
每次文件变化 |
回退到全局配置 |
git config --global user.name |
获取全局作者 |
本地配置失败时 |
设为'Unknown' |
git config user.name "author" |
切换Git作者 |
检测到人工编辑 |
静默失败,记录日志 |
git show HEAD:"file" |
获取原始内容 |
文件初始化时 |
当作新文件处理 |
Git仓库要求
最低要求
- ✅ 执行过
git init
- ✅ 文件被Git跟踪(至少add过一次)
- ❌ 不需要远程仓库
- ❌ 不需要提交历史
推荐状态
- 🎯 工作区相对干净(便于准确统计)
- 🎯 定期提交代码(重置统计基线)
🐛 故障排除
常见问题诊断
问题1:统计数据不准确
症状:显示的AI/人工占比与实际情况不符
排查步骤:
- 检查输出面板的详细日志
- 验证Git作者配置:
git config user.name
- 确认文件在Git仓库中:
git status
- 使用
Verify Git Status Consistency
命令检查
问题2:插件性能问题
症状:VSCode响应缓慢,插件占用资源过高
解决方案:
- 调整
maxFileSize
配置,跳过大文件
- 增加
cacheTimeout
时间,减少Git操作频率
- 使用
Clear Memory Cache
清空缓存
问题3:数据持久化失败
症状:重启VSCode后统计数据丢失
排查步骤:
- 检查VSCode权限,确保可以写入globalState
- 查看输出面板的保存/加载日志
- 验证工作区路径是否正确
调试模式
启用详细日志输出:
// 在输出面板查看详细日志
outputChannel.show();
// 关键日志示例
outputChannel.appendLine(`📂 初始化文件: ${fileName}`);
outputChannel.appendLine(`🔄 检测到${changes.length}行变化`);
outputChannel.appendLine(`📊 当前统计: AI:${aiPercentage}% 人工:${humanPercentage}%`);
📈 数据导出格式
JSON导出结构
{
"timestamp": "2024-01-01T00:00:00.000Z",
"workspaceRoot": "/path/to/project",
"totalModifiedLines": 150,
"totalFiles": 5,
"summary": {
"ai": 98,
"human": 52,
"aiPercentage": "65.33",
"humanPercentage": "34.67"
},
"authors": {
"ai111": {
"lines": 98,
"files": 3,
"isAI": true,
"percentage": "65.33"
},
"张三": {
"lines": 52,
"files": 4,
"isAI": false,
"percentage": "34.67"
}
},
"fileDetails": {
"src/main.ts": {
"totalLines": 45,
"authors": {
"ai111": 30,
"张三": 15
}
}
}
}
🔬 技术深度分析
Myers算法的优势
- 最优性保证:保证找到最小编辑距离
- 性能优秀:对于大多数实际代码场景,性能表现良好
- 广泛应用:Git、SVN等版本控制系统的标准算法
- 稳定可靠:经过长期验证,算法稳定性高
拦截实现技术细节
命令拦截的完整实现
// 1. 键盘输入拦截
const typeCommandDisposable = vscode.commands.registerCommand('type', async (args) => {
const editor = vscode.window.activeTextEditor;
if (editor && isCodeFile(editor.document.fileName)) {
const fileName = editor.document.fileName;
// 防抖检查,避免重复处理
if (shouldSkipCommand('type', fileName)) {
return await vscode.commands.executeCommand('default:type', args);
}
// 检测是否会修改文件
const text = args?.text || '';
if (text && text.length > 0) {
outputChannel.appendLine(`⌨️ 检测到文件修改输入: "${text}"`);
// 先切换到人工作者,等待完成
await handleManualEditingIntent(fileName);
}
}
// 执行原始的type命令
return await vscode.commands.executeCommand('default:type', args);
});
// 2. 粘贴操作拦截
const pasteCommandDisposable = vscode.commands.registerCommand('editor.action.clipboardPasteAction', async () => {
const editor = vscode.window.activeTextEditor;
if (editor && isCodeFile(editor.document.fileName)) {
const fileName = editor.document.fileName;
if (!shouldSkipCommand('paste', fileName)) {
outputChannel.appendLine(`📋 检测到粘贴操作: ${path.basename(fileName)}`);
await handleManualEditingIntent(fileName);
}
}
// 手动执行粘贴逻辑
const clipboardText = await vscode.env.clipboard.readText();
if (clipboardText && editor) {
await editor.edit(editBuilder => {
const selection = editor.selection;
editBuilder.replace(selection, clipboardText);
});
}
});
// 3. 删除操作拦截(支持左删除和右删除)
const deleteLeftDisposable = vscode.commands.registerCommand('deleteLeft', async () => {
const editor = vscode.window.activeTextEditor;
if (editor && isCodeFile(editor.document.fileName)) {
const fileName = editor.document.fileName;
if (!shouldSkipCommand('deleteLeft', fileName)) {
outputChannel.appendLine(`⌫ 检测到删除操作: ${path.basename(fileName)}`);
await handleManualEditingIntent(fileName);
}
}
// 手动执行删除逻辑
if (editor) {
await editor.edit(editBuilder => {
const selection = editor.selection;
if (selection.isEmpty) {
// 删除光标前一个字符或合并行
const position = selection.active;
if (position.character > 0) {
const deleteRange = new vscode.Range(
position.line, position.character - 1,
position.line, position.character
);
editBuilder.delete(deleteRange);
} else if (position.line > 0) {
const prevLineEnd = editor.document.lineAt(position.line - 1).range.end;
editBuilder.delete(new vscode.Range(prevLineEnd, position));
}
} else {
editBuilder.delete(selection);
}
});
}
});
编辑上下文状态管理
// 编辑上下文状态跟踪
interface EditingContext {
isKeyboardActive: boolean; // 键盘是否活跃
cursorInFile: boolean; // 光标在文件内
lastActivity: number; // 最后活动时间戳
preparingEdit: boolean; // 是否正在准备编辑
}
// 状态更新和日志记录
function updateEditingContext(fileName: string, updates: Partial<EditingContext>): void {
const context = getEditingContext(fileName);
Object.assign(context, updates, { lastActivity: Date.now() });
editingContexts.set(fileName, context);
outputChannel.appendLine(
`📝 更新编辑上下文: ${path.basename(fileName)} - ` +
`光标:${context.cursorInFile}, 键盘:${context.isKeyboardActive}, 准备:${context.preparingEdit}`
);
}
边界情况处理的哲学
插件采用**"最终状态导向"**的设计哲学:
- 简单归属:每行代码归属于最后修改者
- 忽略过程:不关注修改的中间过程,只看最终结果
- 净贡献原则:删除操作不算贡献,只统计实际存在的修改
- 工程实用性:在算法复杂度和实用性之间找到平衡
- 实时响应:拦截操作确保Git作者切换在编辑前完成
性能优化策略
缓存层次化:
- L1缓存:内存缓存(函数级)
- L2缓存:操作缓存(Git命令级)
- L3缓存:会话缓存(文件状态级)
并发控制:
- Gate函数防止重复操作
- 队列管理避免资源竞争
- 防抖机制减少触发频率
内存管理:
- 定期清理过期缓存
- 限制文件大小避免内存溢出
- 工作区隔离防止数据混合
🎯 最佳实践
团队使用建议
- 统一AI作者命名:团队统一使用如"ai111"的AI作者名称
- 及时切换身份:根据代码来源及时切换Git作者
- 定期导出数据:在重要里程碑导出统计数据留档
- 结合代码审查:将AI生成代码作为审查重点
个人使用技巧
- 养成切换习惯:使用AI工具前后及时切换Git作者
- 监控实时数据:通过状态栏随时了解AI使用情况
- 定期重置统计:项目阶段性重置避免数据累积
- 合理配置参数:根据团队需求调整插件配置
📊 版本演进历史
版本 |
主要特性 |
技术改进 |
v1.x |
基础统计功能 |
简单行匹配算法 |
v2.x |
Git集成 |
添加Git diff支持 |
v3.x |
智能检测 |
引入Myers算法 |
v4.x |
性能优化 |
缓存系统和防抖 |
v5.x |
完整边界处理 |
28种极端场景覆盖 |
🤝 贡献指南
开发环境搭建
# 克隆项目
git clone <repository-url>
cd ai-code-stats
# 安装依赖
npm install
# 编译TypeScript
npm run compile
# 监听文件变化
npm run watch
测试用例添加
// 添加新的边界情况测试
describe('Edge Case: New Scenario', () => {
it('should handle specific edge case correctly', async () => {
// 测试实现
});
});
算法改进
如果发现新的边界情况或算法问题,请:
- 在
test-extreme-cases.md
中记录场景
- 添加对应的测试用例
- 修改核心算法逻辑
- 验证所有现有测试通过
💬 联系方式
- 问题反馈:通过 GitHub Issues 提交
- 功能建议:欢迎提交 Pull Request
- 技术讨论:查看插件输出面板的详细日志
AI代码统计插件 - 让AI代码贡献度透明化,让开发过程数据化!