1. 项目概述一个为开发者定制的轻量级代码统计工具如果你和我一样日常重度依赖 Cursor 这类 AI 驱动的代码编辑器那你肯定有过这样的体验看着编辑器里飞速增长的代码行数心里却有点没底。我到底写了多少行代码这个项目里新增、删除、修改的代码量分别是多少最近一周我的编码活跃度如何这些数据对于个人复盘、团队汇报甚至只是想满足一下“我今天又产出了不少”的成就感都至关重要。然而市面上现成的代码统计工具要么过于笨重需要复杂的配置和服务器要么功能单一无法与 Cursor 这样的现代编辑器深度结合。于是我动手开发了darzhang/cursor-stats-lite。这是一个专为 Cursor 编辑器设计的、轻量级的本地代码统计插件。它的核心目标只有一个在你最熟悉的编码环境里以最无感的方式为你提供最直观的代码贡献数据。它不依赖任何外部服务所有数据都在你本地生成和处理确保了隐私和安全。通过简单的命令你就能快速查看指定时间范围内你在当前项目中的代码变更统计包括新增行数、删除行数、净增行数并能按文件类型进行细分。对于追求效率、注重数据、又不想被复杂工具打扰的开发者来说这无疑是一个趁手的小工具。接下来我将详细拆解这个工具从构思到实现的每一个环节分享其中的技术选型、实现细节以及我踩过的那些坑。2. 核心设计思路为什么选择“轻量级”与“本地化”在决定动手之前我花了些时间思考这个工具应该长什么样。市面上并非没有类似的方案比如gitstats、cloc等命令行工具功能强大GitHub Insights或GitLab自带的统计功能也相当全面。但它们都存在一些让我不太满意的地方要么需要离开编辑器上下文去另一个终端或网页查看要么数据聚合的粒度不够细比如无法区分是我自己写的代码还是 AI 生成的建议要么就是配置过程让人望而却步。因此我为cursor-stats-lite定下了几个核心设计原则这些原则也直接决定了后续的技术选型和架构。2.1 原则一深度集成无感使用工具的价值在于提升效率而不是制造新的负担。我的首要目标是让统计功能成为 Cursor 编辑器工作流的一部分而不是一个独立的外部应用。这意味着用户最好能在 Cursor 的命令面板里直接调用统计功能结果也能直接显示在编辑器内比如输出到内置终端或者一个漂亮的 Webview 面板里。这种“开箱即用、用完即走”的体验才是现代开发者工具应该追求的。为了实现这一点我自然想到了利用 Cursor 对 VS Code 扩展生态的兼容性。VS Code 拥有庞大而成熟的扩展 API允许开发者创建命令、菜单、视图等。通过开发一个 VS Code 扩展就能无缝嵌入到 Cursor 中。用户只需要像安装其他插件一样从市场安装或者本地加载之后通过快捷键或命令面板即可唤醒功能完全不需要切换上下文。2.2 原则二数据隐私与计算本地化代码是开发者的核心资产代码变更数据同样敏感。我不希望用户的任何代码内容或元数据离开其本地环境。因此“所有计算在本地完成”是一条不可妥协的红线。这不仅仅是出于隐私考虑也带来了实实在在的好处零网络延迟响应速度极快。无论项目大小统计都是瞬间完成体验流畅。本地化计算也意味着更简单的部署。用户不需要申请 API 密钥不需要配置数据库更不用担心服务宕机。工具的唯一依赖就是用户本地的 Git 仓库和 Node.js 运行环境对于 VS Code 扩展来说后者是内置的。这极大地降低了使用门槛。2.3 原则三轻量且聚焦这个工具不是为了替代专业的代码分析平台。它的功能应该非常聚焦统计指定时间段内当前用户在当前项目中的代码行变更。不需要复杂的图表渲染不需要团队对比不需要代码质量分析。保持轻量才能保持快速和稳定。基于这个原则我决定核心统计逻辑直接基于git log命令进行解析。Git 本身已经完美记录了每一次提交的变更详情通过git log --numstat可以获取每个文件增加和删除的行数。我们只需要巧妙地过滤和聚合这些数据即可。这避免了引入重量级的代码分析库让整个扩展的体积可以控制在极小的范围。2.4 原则四结果清晰直观输出的数据必须一目了然。一个简单的表格包含总新增、总删除、净增行数以及按文件后缀如.js.ts.py分类的统计就足够了。过于复杂的数据可视化反而会分散注意力。清晰文本格式的输出既可以直接阅读也方便用户复制到报告或笔记中。综合以上四点cursor-stats-lite的形态就清晰了它是一个 VS Code 扩展通过调用本地 Git 命令获取数据在内存中完成聚合计算最后将结果以纯文本或简单格式输出到编辑器的输出通道或终端里。整个过程中用户的代码数据不会以任何形式发送到网络。3. 技术实现拆解从 Git 日志到可读统计明确了设计思路接下来就是动手实现。整个流程可以分解为几个关键步骤获取用户输入时间范围、执行 Git 命令、解析日志数据、聚合统计、格式化输出。下面我们来逐一拆解。3.1 环境准备与项目初始化首先我们需要创建一个标准的 VS Code 扩展项目。使用 Yeoman 生成器和 VS Code 扩展生成器是最快的方式npm install -g yo generator-code yo code在生成器的交互界面中选择“New Extension (TypeScript)”。TypeScript 能提供更好的类型安全和开发体验对于即使这样的小项目也值得使用。项目生成后核心文件是src/extension.ts这是我们功能的入口点。我们需要在package.json中声明扩展的激活事件和提供的命令。这是扩展与编辑器通信的契约。{ activationEvents: [ onStartupFinished ], contributes: { commands: [ { command: cursor-stats-lite.showStats, title: Cursor Stats: Show My Code Statistics } ] } }这里onStartupFinished让扩展在编辑器启动完成后就加载好但不会立即执行代码保持轻量。我们定义了一个命令cursor-stats-lite.showStats用户可以在命令面板中搜索并执行它。3.2 核心统计逻辑解析 Git Numstat统计的核心在于如何从 Git 历史中提取出“我”在“某个时间段”的代码变更。这里用到了 Git 一个强大但不太常用的参数--numstat。git log --numstat的输出格式大致如下commit abcdef123456... Author: Your Name your.emailexample.com Date: Mon Apr 1 12:00:00 2024 0800 feat: add new function 2 1 src/foo.js 0 5 docs/readme.md每一行文件变更包含三列以制表符分隔增加的行数、删除的行数、文件路径。这正是我们需要的原始数据。我们需要对这个命令进行加工添加过滤条件作者过滤--author“Your Name”。确保只统计当前用户的提交。这里有个细节如何动态获取当前 Git 配置的用户名一种方法是读取.git/config文件但更可靠的方式是在扩展启动时尝试执行git config user.name来获取。时间范围过滤--since“2024-03-25” --until“2024-04-01”。支持用户输入起始和结束日期。格式化--prettyformat:“”。我们不需要完整的提交信息用空格式来简化输出让后续解析更专注在--numstat的数据上。因此最终构建的 Git 命令类似git log --since2024-03-25 --until2024-04-01 --author$(git config user.name) --prettyformat: --numstat注意这里有一个潜在的坑。如果用户名包含空格或特殊字符需要正确处理引号。在 Node.js 的child_process中执行命令时使用参数数组形式[‘git’ ‘log’ ‘--since...’ ...]比拼接字符串更安全可以避免 shell 转义问题。3.3 在扩展中执行命令与处理数据在extension.ts中我们需要做以下几件事获取工作区信息通过vscode.workspace.workspaceFolders判断是否打开了文件夹即项目并获取其路径。获取用户输入使用vscode.window.showInputBox弹出输入框让用户输入起始和结束日期。为了提高易用性可以提供默认值比如过去7天。执行 Git 命令使用 Node.js 的child_process.execFile或exec在项目根目录下执行构建好的 Git 命令。解析输出将命令返回的文本按行分割。跳过空行。每一行数据行解析出新增数、删除数和文件路径。这里需要处理可能存在的重命名文件Git 会显示为{old new}我们通常只关心最终的文件名可以进行简单处理提取后面的部分或者直接忽略这种复杂情况专注于简单统计。聚合数据累加所有文件的增加行数、删除行数。根据文件路径的后缀名通过path.extname获取将变更行数累加到对应的文件类型分类中。对于没有后缀或后缀不常见的文件可以归到 “Other” 类别。输出结果将聚合好的数据格式化为一个清晰的 ASCII 表格。我们可以使用console.table的替代方案或者直接用字符串拼接一个简单的表格。然后通过vscode.window.createOutputChannel创建一个专属的输出面板将结果打印进去这样用户就可以在一个固定的面板里查看多次统计的结果而不会打扰到终端。3.4 错误处理与边界情况一个健壮的工具必须考虑各种边界情况非 Git 仓库如果当前打开的不是 Git 仓库应该友好提示而不是让命令执行失败后抛出晦涩的错误。无符合条件的提交在指定时间范围和作者下可能没有提交。此时应输出“未发现变更”之类的提示而不是一个空表格或报错。Git 命令未安装虽然 Cursor/VS Code 用户大概率有 Git但仍需检查。可以尝试执行git --version来探测。用户取消输入当弹出输入框时用户按了 ESC。此时应安静地退出不执行任何操作。大仓库性能对于提交历史非常庞大的仓库git log可能会稍慢。可以考虑在 UI 上显示一个进度提示vscode.window.withProgress提升用户体验。4. 关键代码解析与实操要点让我们深入到部分关键代码看看具体是如何实现的并讨论一些实现细节和选择。4.1 命令注册与激活在extension.ts的activate函数中我们注册命令import * as vscode from ‘vscode’; import { showStats } from ‘./statsCalculator’; export function activate(context: vscode.ExtensionContext) { const disposable vscode.commands.registerCommand(‘cursor-stats-lite.showStats’ () { showStats(); }); context.subscriptions.push(disposable); }showStats函数是我们实现的核心功能入口。将核心逻辑分离到statsCalculator.ts这样的模块中有助于保持extension.ts的简洁和可测试性。4.2 构建安全的 Git 命令在statsCalculator.ts中构建命令参数需要格外小心import { exec } from ‘child_process’; import { promisify } from ‘util’; const execAsync promisify(exec); async function getGitUserName(workspacePath: string): Promisestring { try { const { stdout } await execAsync(‘git config user.name’ { cwd: workspacePath }); return stdout.trim(); } catch (error) { // 如果获取失败可以抛错或返回空字符串由上层处理 throw new Error(‘无法获取 Git 用户名请检查 Git 配置。’); } } async function buildGitLogCommand(workspacePath: string since: string until: string author: string): Promisestring[] { // 使用参数数组避免 shell 注入风险 const cmd [ ‘git’ ‘log’ --since“${since}” --until“${until}” --author“${author}” ‘--prettyformat:“”’ // 注意这里使用空格式 ‘--numstat’ ]; // 注意如果日期或作者名可能包含引号需要更复杂的转义处理。 // 一个更稳妥的做法是不在参数值外加引号而是依赖 exec 的参数数组机制。 // 修正版 const cmdSafe [ ‘git’ ‘log’ --since${since} // 值本身不应包含引号 --until${until} --author${author} ‘--prettyformat:’ ‘--numstat’ ]; return cmdSafe; }实操心得在拼接命令行参数时永远优先使用参数数组[‘git’ ‘log’ ‘--since...’]而不是字符串git log --since“...”。前者由 Node.js 直接处理能安全地传递各种特殊字符后者需要经过系统 shell 解析如果用户输入包含;|等 shell 元字符可能导致命令注入执行非预期的命令这是严重的安全漏洞。4.3 解析与聚合 Numstat 输出解析git log --numstat的输出需要处理一些细节interface FileStats { additions: number; deletions: number; } interface StatsResult { total: FileStats; byExtension: { [ext: string]: FileStats }; } async function parseNumstatOutput(output: string): PromiseStatsResult { const lines output.split(‘\n’); const result: StatsResult { total: { additions: 0 deletions: 0 } byExtension: {} }; for (const line of lines) { // 跳过空行和可能残留的空白行 if (!line.trim()) continue; // Numstat 行格式 “添加数\t删除数\t文件路径” const parts line.split(‘\t’); if (parts.length 3) continue; // 不是有效的 numstat 行 const [addStr delStr filePath] parts; const additions parseInt(addStr 10); const deletions parseInt(delStr 10); // Git 对二进制文件用 “-” 表示parseInt 会得到 NaN if (isNaN(additions) || isNaN(deletions)) { continue; // 跳过二进制文件变更 } // 累加总计 result.total.additions additions; result.total.deletions deletions; // 按文件扩展名分类 const ext path.extname(filePath).toLowerCase() || ‘.no_extension’; if (!result.byExtension[ext]) { result.byExtension[ext] { additions: 0 deletions: 0 }; } result.byExtension[ext].additions additions; result.byExtension[ext].deletions deletions; } return result; }注意事项git log --numstat对于二进制文件如图片的变更会在增加和删除列显示-。我们的parseInt会得到NaN所以必须检查并跳过。同时文件路径可能因为重命名而显示为“old/path new/path”。上述简单实现只取了filePath的最终值对于重命名它会取之后的部分作为路径。如果你需要更精确地处理重命名例如将变更同时计入新旧文件类型则需要更复杂的解析但这通常不是轻量统计的核心需求。4.4 格式化输出与展示将统计结果以友好方式呈现function formatStats(result: StatsResult): string { const { total byExtension } result; const netChange total.additions - total.deletions; let output 代码统计结果 \n; output 时间范围: ${since} 至 ${until}\n; output 作者: ${author}\n\n; output 汇总:\n; output 新增行数: ${total.additions}\n; output 删除行数: ${total.deletions}\n; output 净增行数: ${netChange}\n\n; output 按文件类型细分:\n; // 为了美观可以计算一下最大扩展名长度来对齐 const extensions Object.keys(byExtension).sort(); if (extensions.length 0) { output (无)\n; } else { for (const ext of extensions) { const stat byExtension[ext]; const extDisplay ext ‘.no_extension’ ? ‘(无扩展名)’ : ext; output ${extDisplay.padEnd(12)}: ${stat.additions} / -${stat.deletions} (净${stat.additions - stat.deletions})\n; } } return output; } // 在 showStats 函数中 const outputChannel vscode.window.createOutputChannel(‘Cursor Stats Lite’); outputChannel.show(); // 显示输出面板 outputChannel.appendLine(formatStats(result));这样一个清晰、本地的代码统计功能就实现了。用户只需要在 Cursor 中按下CmdShiftP(Mac) 或CtrlShiftP(Windows/Linux)输入 “Cursor Stats”选择命令输入日期范围就能立刻看到自己的编码产出。5. 开发中的常见问题与排查实录即使是一个小工具开发过程中也难免遇到各种问题。这里记录了几个典型问题及其解决方法希望能帮你避开这些坑。5.1 Git 命令在扩展环境中执行失败问题现象在终端里手动运行git log一切正常但在扩展中通过child_process.exec执行时返回错误“fatal: not a git repository”或命令未找到。排查思路工作目录问题exec默认不在项目根目录运行。你必须通过{ cwd: workspaceFolder.uri.fsPath }选项明确指定命令执行的工作目录。PATH 环境变量VS Code/Cursor 扩展的运行时环境可能与你的终端环境不同可能找不到git命令。可以使用process.env.PATH打印检查或者考虑使用vscode.env.shell来获取系统 shell但更简单的方法是使用which git(Unix) 或where git(Windows) 的绝对路径。不过通常 VS Code 会继承系统的 PATH。Git 未安装极少数情况下用户可能没装 Git。可以在激活扩展时做一次检测并给出友好提示。解决方案async function executeGitCommand(args: string[] workspacePath: string): Promisestring { return new Promise((resolve reject) { const child execFile(‘git’ args { cwd: workspacePath } (error stdout stderr) { if (error) { // 处理特定的错误如非Git仓库 if (stderr.includes(‘not a git repository’)) { reject(new Error(‘当前文件夹不是 Git 仓库。’)); } else { reject(error); } return; } resolve(stdout); }); }); }5.2 时间范围过滤的时区陷阱问题现象用户输入 “2024-04-01” 作为--until日期期望统计包含这一整天的提交但发现当天晚些时候的提交没有被计入。原因分析git log --until“2024-04-01”默认解释为 “2024-04-01 00:00:00”即那一天的开始。因此4月1日白天发生的提交都在这个时间点之后不会被包含。解决方案为了包含截止日期当天的全部提交我们需要将--until日期向后推一天。即如果用户想看到截止到4月1日的提交我们应该传入--until“2024-04-02”。在 UI 上我们需要向用户明确说明时间范围是“包含起始日不包含结束日”左闭右开区间或者在前端处理时自动为结束日期加一天。// 假设用户输入了 since‘2024-03-25’ until‘2024-04-01’ // 我们想让统计包含 4月1日 全天的提交 const effectiveUntil new Date(until); effectiveUntil.setDate(effectiveUntil.getDate() 1); const formattedUntil effectiveUntil.toISOString().split(‘T’)[0]; // 得到 ‘2024-04-02’ // 然后将 formattedUntil 用于 git log --until5.3 处理大型仓库的性能考量问题现象在拥有数万次提交的巨型仓库中执行git log扩展会“卡住”一段时间UI 无响应用户体验差。优化策略进度反馈使用vscode.window.withProgressAPI 显示一个进度条或旋转图标告知用户操作正在进行中避免误以为程序崩溃。await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification title: “正在分析 Git 历史...” cancellable: false // 如果统计时间可能很长可以考虑设为 true } async (progress) { // 执行统计逻辑 const result await calculateStats(); return result; });限制历史范围在 UI 上引导用户输入合理的时间范围避免默认查询全部历史。可以提供“最近一周”、“最近一个月”等快捷选项。异步与非阻塞确保所有耗时的操作如exec都使用异步函数并且不阻塞 VS Code 的主线程。我们的实现本身就在异步函数中这一点是满足的。5.4 扩展的打包与发布问题描述本地测试一切正常但如何分享给其他 Cursor 用户使用解决方案遵循 VS Code 扩展的发布流程。安装 vscenpm install -g vscode/vsce创建发布账号在 Azure DevOps 创建组织获取 Personal Access Token (PAT)。打包在项目根目录运行vsce package。这会生成一个.vsix文件可以直接分发给其他用户让他们通过 Cursor 的“从 VSIX 安装”功能来安装。发布到市场运行vsce publish可以将扩展发布到 VS Code 扩展市场。由于 Cursor 兼容 VS Code 市场发布后用户就可以直接在 Cursor 的扩展商店里搜索 “cursor stats lite” 并安装了。实操心得在package.json中engines.vscode字段非常重要它指定了扩展兼容的 VS Code 版本最低要求。Cursor 基于特定版本的 VS Code确保这个版本号兼容是关键。例如“^1.85.0”表示兼容 1.85.0 及以上版本。发布前务必在 Cursor 中完整测试打包后的.vsix文件。6. 功能扩展与进阶玩法基础功能实现后这个工具还有很大的想象空间。这里分享几个我实践过或认为有价值的扩展方向你可以根据自己的需求进行定制。6.1 支持更灵活的过滤条件目前的过滤只支持作者和时间。可以很容易地扩展按提交信息过滤--grep“feat”只统计包含 “feat” 关键词的提交。排除合并提交--no-merges可以过滤掉合并提交产生的行数变更这些变更通常不是直接编码产出。按文件路径过滤-- “src/”可以只统计src/目录下的文件忽略文档、配置文件等。在 UI 上可以设计一个更高级的表单让用户勾选或输入这些过滤条件让统计维度更加精准。6.2 输出格式的多样化除了简单的文本输出还可以考虑JSON 输出将统计结果以 JSON 格式输出方便被其他脚本或工具如自动化报告生成器消费。可以在命令中增加一个--output-format json的选项。图表预览虽然我们追求轻量但利用 VS Code 的 Webview API可以创建一个简单的 HTML 页面用轻量图表库如 Chart.js渲染一个柱状图直观展示不同文件类型的贡献比例。这对于快速可视化很有吸引力。导出到文件提供一个命令将本次统计结果追加到一个本地的 Markdown 或 CSV 文件中形成个人的编码日志。6.3 定时自动统计与提醒我们可以利用 VS Code 扩展的setInterval或cron表达式通过 node-schedule 包在后台定期比如每天下班时自动运行统计并将结果以信息通知的形式推送给用户。// 在扩展激活时启动一个定时任务注意控制频率避免性能影响 const dailyJob schedule.scheduleJob(‘0 18 * * *’ () { // 每天下午6点 const stats await calculateStatsForLastDay(); vscode.window.showInformationMessage(今日编码报告新增 ${stats.total.additions} 行 删除 ${stats.total.deletions} 行。); }); context.subscriptions.push({ dispose: () dailyJob.cancel() }); // 扩展停用时取消任务这个功能可以很好地帮助开发者培养每日复盘的习惯。6.4 与 Cursor AI 活动的结合设想一个更有趣的方向是尝试区分“人工编写”和“AI 生成”的代码。Cursor 的 AI 功能如Chat和Composer在生成代码时是否会留下特殊的元数据或标记目前公开的 API 可能没有直接暴露这一点。但我们可以做一个近似统计通过分析提交信息中是否包含“Cursor:”或“Composer:”等前缀如果 Cursor 的自动提交信息包含这些来粗略估计 AI 辅助生成的代码量。这需要进一步研究 Cursor 的行为。7. 总结与个人体会开发cursor-stats-lite的过程是一次典型的“工具驱动效率”的实践。它源于一个非常具体且未被很好满足的需求然后用相对简单的技术组合将其实现。整个项目没有用到什么高深的技术核心就是git log命令的灵活运用和 VS Code 扩展 API 的基本操作。但正是这种“简单”让它变得有用。它不试图解决所有问题只专注做好一件事快速、私密地告诉你你在项目中写了多少代码。对于独立开发者、小型团队或者只是希望量化自己工作进度的个人来说这种轻量级的工具往往比功能庞杂的系统更受欢迎。我个人在几个项目中持续使用这个工具它让我对每周的代码产出有了更清晰的认识。有时看着净增行数为负删除多于新增我会反思是不是在做大量的重构和优化有时看到某种文件类型的变更特别活跃我会意识到项目的技术重心正在发生变化。这些数据点成了我复盘和规划的小小依据。最后这个小工具也完全开源。如果你有类似的需求或者对其中某些实现有更好的想法非常欢迎你基于它进行修改和扩展。毕竟最好的工具永远是那个最适合自己工作流的工具。希望我的这次分享和这个简单的工具能给你的开发日常带来一点点不一样的视角和便利。