给 AI 用的代码索引器-技术篇
在 AI 辅助开发成为常态的今天一个持续出现的工程问题是AI 缺少项目上下文。我为此做了一个工具files-introduction-for-ai并把它改造成可发布的 npm CLI ——ai-file-indexer。这篇文章会从技术实现的角度讲清楚它的设计取舍、核心流程和工程细节。问题定义为什么需要“代码索引器”很多团队都遇到过这些问题代码库较大AI 只能“盲猜”业务结构每次让 AI 介入都要重复解释模块关系变更后没有增量更新索引很快过期提交代码时缺少“上下文产物”的自动更新机制目标很明确把“给 AI 解释项目”从一次性对话变成可持续、可自动化的工程资产。整体架构设计这个 CLI 的核心职责可以拆成三块初始化生成配置和 hooks 模板补齐package.jsonscripts索引扫描文件、提取结构、调用 LLM、生成索引产物Git 集成设置 hooks 路径让提交自动触发索引更新命令入口设计为ai-file-indexer initai-file-indexer index --full|--incremental [--stage-output]ai-file-indexer hooks setup核心流程详解1) 初始化阶段init初始化的目标是把接入成本降到最低。执行逻辑从templates/目录复制配置模板到目标项目根目录.ai-indexer.config.json索引配置.githooks/pre-commit提交前触发索引的脚本确保.githooks/pre-commit可执行chmod 0o755读取package.json若不存在则创建一个最小骨架在package.json.scripts中补齐以下命令若已存在则跳过ai:index:full全量索引ai:index:incremental增量索引ai:hooks:setup设置 Git hooks 路径设计取舍配置文件和 hooks 模板采用“若不存在则复制”的策略避免覆盖用户自定义package.jsonscripts 采用“upsert”而非“覆盖”保证不破坏已有脚本2) 索引阶段index索引是整个工具的核心流程较长这里拆开讲。2.1 配置加载与参数解析从项目根目录读取.ai-indexer.config.json解析命令行参数--full全量模式--incremental增量模式默认--stage-output将索引产物自动加入暂存区2.2 目标文件发现根据模式选择文件来源全量模式调用git ls-files获取所有被 git 跟踪的文件增量模式调用git diff --cached --name-only --diff-filterACMR获取当前 staged 的变更文件过滤逻辑检查文件扩展名是否在includeExtensions白名单中检查文件路径是否匹配excludeGlobs中的任意排除规则如node_modules/、dist/设计取舍使用git ls-files而非文件系统遍历避免扫描到未被跟踪的临时文件增量模式只处理 staged 文件保证“提交即更新”的语义一致性2.3 文件结构提取对每个目标文件提取以下信息语言类型根据扩展名映射支持 JS/TS/TSX/Vue/Python/Java/Go/Markdown函数名使用正则表达式抽取不同语言有不同的模式JS/TSfunction\s([a-zA-Z_$][\w$]*)\s*\(、const\s([a-zA-Z_$][\w$]*)\s*\s*\([^)]*\)\s*等Python^def\s([a-zA-Z_][\w]*)\s*\(Java(public|protected|private)?\s*(static\s)?[\w\[\]]\s([a-zA-Z_][\w]*)\s*\([^)]*\)\s*\{Gofunc\s(\([^)]\)\s)?([A-Za-z_][\w]*)\s*\(依赖信息同样使用正则抽取JS/TSimport\s.?from\s[\]([^\])[\]、require\([\]([^\])[\]\)Python^import\s([\w.])、^from\s([\w.])\simport\sGoimport\s([^])、import\s*\(([^)])\)Java^import\s([\w.*]);设计取舍使用正则而非 AST 解析原因轻量、无额外依赖不需要完整语法树只需要函数名和依赖名对多语言支持更灵活每种语言写一套正则即可限制抽取数量函数最多 100 个依赖最多 50 个避免极端文件导致内存问题2.4 LLM 摘要生成调用大模型生成结构化摘要从环境变量读取QWEN_API_KEY若未设置则走兜底逻辑截取文件内容前maxFileCharsForLlm字符默认 8000控制 token 成本构造 prompt要求输出 JSON 格式包含字段summary1-3 句描述文件整体职责purpose一句话描述业务功能methodNotes数组按函数名给出简短说明每项 ≤ 40 字调用 Qwen API支持自定义baseUrl和model若调用失败或无 API Key使用兜底摘要基于正则抽取的信息设计取舍prompt 强制要求 JSON 输出便于后续解析温度设为 0.1保证输出稳定失败时兜底而非中断保证索引流程的鲁棒性2.5 索引产物生成对每个文件生成一条记录{ path: src/index.js, language: JavaScript, summary: ..., purpose: ..., functions: [init, runIndexer], methodNotes: [init: 初始化配置, runIndexer: 执行索引], dependencies: [fs, path] }然后生成两层索引文件级索引包含所有文件的详细记录输出file-index.json和file-index.md模块级索引按目录聚合统计每个模块的文件数、函数数、依赖数、用途摘要输出module-index.json和module-index.md设计取舍同时输出 JSON 和 MarkdownJSON 便于程序消费Markdown 便于人类阅读模块级索引帮助 AI 先理解业务边界再下钻细节2.6 暂存区更新可选若指定--stage-output调用git add将所有索引产物加入暂存区。设计取舍这个选项适合“提交即更新文档”的场景避免“代码改了文档没跟上”但默认不开启避免用户不熟悉时误提交3) Git 集成阶段hooks setup设置 Git 使用.githooks目录作为 hooks 路径检查当前目录是否为 git 仓库git rev-parse --is-inside-work-tree检查.githooks/pre-commit是否存在设置git config core.hooksPath .githooks设计取舍使用.githooks而非.git/hooks原因.githooks可以被 git 跟踪便于团队共享避免每个开发者手动复制 hooks配置文件设计.ai-indexer.config.json的核心字段includeExtensions纳入索引的文件类型excludeGlobs忽略目录如node_modules、distoutputDir索引产物输出目录默认.ai/jsonOutput、mdOutput、moduleMdOutput、moduleJsonOutput各类输出文件名llm.provider、llm.baseUrl、llm.modelLLM 配置maxFileCharsForLlm控制输入 token 成本设计取舍配置文件放在项目根目录便于版本控制默认值经过实践验证开箱即用支持自定义 LLM provider便于扩展为什么做成 npm CLI从“脚本”升级为“CLI”的核心收益统一入口降低团队认知成本可发布、可复用不依赖单仓库私有脚本便于接入 CI/CD可以在流水线中自动执行对外扩展更自然未来可加 provider、解析器、输出格式技术债务与后续迭代当前实现的一些已知限制函数和依赖抽取使用正则可能在复杂语法下出错如装饰器、泛型LLM 调用是串行的大仓库可能较慢模块级索引只按目录聚合未考虑实际依赖关系后续迭代方向引入轻量 AST 解析如babel/parser、esprima提升抽取准确性支持并行 LLM 调用提升大仓库索引速度增强模块关系图调用链/依赖拓扑支持更多模型提供商与本地模型增加 CI 模式PR 自动更新并校验索引