资讯动态

用Python将项目目录合并为AI知识库文件:一个实用脚本解析

发布时间:2026/9/9 11:56:11 来源:尧图企业网站定制
作为一个经常跟代码仓库打交道的开发者我几乎每天都会做同一件看起来很笨的事把项目里散落在不同目录下的代码文件、配置文件和文档片段逐个打开、复制、粘贴最后拼接成一个完整的长文件丢给 AI 当背景资料。最开始只是图省事后来我发现“把指定目录下的代码文件和文本文件合并成一个知识库文件”这个操作远比大多数人想的有价值——它不仅是给 AI 喂资料的捷径也是做代码审查、知识沉淀、跨项目迁移前的标准动作。这篇文章就把这个工具从零写一遍我会拆解它的设计思路讲清楚文件过滤、编码处理、合并格式这几个核心环节该怎么定然后给出一份可以直接跑的 Python 实现最后整理我实际使用中踩过的坑。不管你是想给 AI 建一个项目上下文还是想快速整理一份可读的代码归档这套方案都能直接用。1. 为什么我需要一个“目录转知识库”工具1.1 它解决的现实痛点先说说我自己最典型的使用场景。做 AI 辅助开发的时候Cursor、Claude 这类工具虽然能直接读项目目录但上下文窗口始终是有限的项目一大了它根本看不全。以前我的习惯是手动把关键代码贴进对话里贴到一半经常手忙脚乱尤其是跨多个目录的关联代码比如一个 Vue 组件引用了十几个 utils 函数逐个文件找再复制费时费力且容易漏。合并成知识库文件之后一切就变简单了我只要把生成的单个文件拖进 AI 对话或者粘贴过去它就拥有了一份带完整路径、带文件边界、带修改信息的“项目快照”。AI 能一次性理解项目结构回答问题时给出的建议也更贴近实际代码而不是凭空猜测。同样的思路还能用在很多地方代码审查把某个模块的全部源码合并成一个文件评审时从头扫到尾不用在 IDE 里来回切 tab想看上下文直接全局搜关键词。交接与归档项目要交接给同事或者重构前做基线备份把散落的.md、.txt、源码抽出来形成单文件快照将来对比变更非常方便。全局检索合并文件后用编辑器或 grep 一次搜完所有代码和文档里的关键词比你从一个目录跳到另一个目录找文件快得多。AI 知识库构建把公司的公共库、脚手架代码、项目文档定期合并就是一个可以持续喂给 AI 的“私人知识库”。1.2 为什么不推荐手动复制粘贴有人可能会说项目也就几十个文件手动复制不就行了我在早期也是这么想的直到有次漏掉了一个关键配置文件导致 AI 给出的方案一直不对排查了半天才发现是背景资料不全。手动复制至少有三个硬伤第一容易漏文件尤其是深层嵌套目录和隐藏文件人眼扫目录树时很容易忽略。第二路径信息会丢失。你在编辑器里看到的代码有明确的目录归属但复制到纯文本后所有的文件边界都消失了。AI 只知道代码内容不知道它属于哪个模块回答“改哪个文件”的时候往往给错位置。第三不可重复。项目每天都在变手动合并一次要十几分钟第二次改动后又要重来。脚本不一样跑一次是几十毫秒跑一百次也不会累。所以我对这个工具的核心定位是一次写好随时重跑输出稳定且可预期。它不是帮你做一次性转换而是给你一个可持久使用的工作流。2. 核心细节解析与设计要点2.1 文件收集规则扩展名过滤与目录排除合并工具最难的部分不是“合并”本身而是“该合并哪些文件”。如果不过滤一个正常的 Node 项目光node_modules就有几万个文件合并出来的知识库文件既臃肿又充满噪音AI 和人都没法看。我的做法是采用白名单加黑名单的组合策略。白名单是指定要收集的扩展名。代码文件通常包括.py、.js、.ts、.java、.c、.cpp、.go、.rs等文本文件则包括.md、.txt、.json、.yaml、.yml、.toml、.ini、.xml等。默认我会内置一份常用扩展名集合同时允许你通过命令行参数自定义比如只扫.vue和.js。黑名单是排除明显不该进去的目录。我在不同项目里踩过很多坑后总结了一份常见的排除目录清单目录名说明.gitGit 元数据全是二进制和压缩对象必须排除node_modules、bower_components前端依赖目录文件多且与项目逻辑无关dist、build、out、target构建产物通常可随时重新生成.venv、venv、__pycache__Python 虚拟环境和缓存目录.idea、.vscodeIDE 个人配置不同机器上各不相同.next、.nuxt、coverage框架缓存和测试覆盖率报告实现时利用os.walk的一个重要特性在遍历过程中原地修改dirnames列表就可以让os.walk压根不进入这些目录。这样不仅结果干净遍历速度也会快很多。2.2 合并格式让 AI 和人都能读合并文件的格式设计直接影响使用体验。早期我试过最简单的做法——把所有文件内容直接拼接成一个.txt结果文件边界完全消失AI 经常把 A 文件的函数当成 B 文件的非常难受。后来我改成 Markdown 格式的输出每个文件都带清晰的元信息头# 文件src/utils/format.js # 类型.js | 大小2.4 KB | 修改时间2025-01-15 10:30 export function formatDate(date) { // ... } 每个文件块由四部分组成路径行使用相对路径并且统一用/分隔符方便 AI 识别归属。元信息行类型、大小、修改时间这些信息在判断“该不该改这个文件”时很管用。分隔线清楚标记一个文件的结束。文件内容原样保留不强行重排缩进避免破坏代码格式。在文件头部我还会写一个文件清单相当于目录索引。AI 读完清单就能理解整个项目的结构再往下翻具体内容时定位准确率会明显提升。这个细节对于长上下文特别有价值——它相当于给了 AI 一张“地图”而不是一摞乱序的纸。2.3 编码与二进制文件的处理编码问题是我落地这个工具时遇到的第一大坑。现实项目里源码编码五花八门UTF-8 无 BOM、UTF-8 带 BOM、GBK、GB2312、ISO-8859-1Windows 下尤其常见。如果写代码时只用encodingutf-8去读遇到 GBK 编码的文件就会直接抛UnicodeDecodeError整个脚本中断。我的处理策略是分级容错默认用 UTF-8 读取并加上errorsreplace遇到识别不了的字节就替换成占位符保证程序不中断。如果希望更精准可以用chardet做编码探测或者先尝试 UTF-8失败后回退到 GBK 再读一次。输出统一写成 UTF-8保证最终知识库文件自身不带编码问题在任何系统上打开都正常。二进制文件的识别同样不能漏。图片、字体、压缩包、PDF 如果混入合并结果轻则输出乱码重则让生成的 知识库文件 体积暴涨。我的方案是在扩展名白名单之外再加一道保险读取文件开头 4096 字节如果发现\x00这样的空字节就判定为二进制并跳过。这样就算某个文件扩展名不在白名单里也不会带坏整个输出。3. 实操用 Python 写一个完整可用的合并工具3.1 基础实现遍历、过滤、合并这里给出一个可以直接保存运行的版本。我把核心逻辑拆成两个函数collect_files负责收集文件merge_files负责生成知识库文件。import os import datetime DEFAULT_EXCLUDE_DIRS { .git, node_modules, dist, build, out, target, .venv, venv, __pycache__, .idea, .vscode, .next, .nuxt, coverage, .svn, .hg, } DEFAULT_INCLUDE_EXTS { .py, .js, .ts, .jsx, .tsx, .vue, .html, .css, .scss, .java, .c, .cpp, .h, .hpp, .go, .rs, .rb, .php, .swift, .kt, .sh, .bat, .ps1, .md, .markdown, .txt, .json, .yaml, .yml, .xml, .toml, .ini, .cfg, .conf, .sql, .graphql, .proto, .dockerfile, .env, .properties, } def collect_files(root_dir, include_exts, exclude_dirs, max_file_size_mb0): files [] for dirpath, dirnames, filenames in os.walk(root_dir): # 原地修改 dirnames让 os.walk 不进入排除目录 dirnames[:] [ d for d in dirnames if d not in exclude_dirs and not d.startswith(.) ] for filename in filenames: filepath os.path.join(dirpath, filename) if filename.startswith(.): continue ext os.path.splitext(filename)[1].lower() if ext not in include_exts: continue if max_file_size_mb 0: size os.path.getsize(filepath) if size max_file_size_mb * 1024 * 1024: continue files.append(filepath) files.sort(keylambda p: os.path.relpath(p, root_dir).lower()) return files def merge_files(root_dir, files, output_path): lines [] total_size 0 for filepath in files: relpath os.path.relpath(filepath, root_dir).replace(os.sep, /) ext os.path.splitext(filepath)[1].lower() size os.path.getsize(filepath) total_size size mtime datetime.datetime.fromtimestamp( os.path.getmtime(filepath) ).strftime(%Y-%m-%d %H:%M) lines.append(f# 文件{relpath}) lines.append(f# 类型{ext or 无扩展名} | 大小{size} B | 修改时间{mtime}) lines.append(- * 80) try: with open(filepath, r, encodingutf-8, errorsreplace) as f: text f.read() lines.append(text) except Exception as e: lines.append(f[读取失败] {e}) lines.extend([, * 80, ]) with open(output_path, w, encodingutf-8, errorsreplace) as out: out.write(# 项目知识库合并文件\n\n) out.write(f- 根目录{os.path.abspath(root_dir)}\n) out.write(f- 生成时间{datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S)}\n) out.write(f- 文件数量{len(files)}\n) out.write(f- 总大小{total_size / 1024:.1f} KB\n\n) out.write(## 文件清单\n\n) for filepath in files: relpath os.path.relpath(filepath, root_dir).replace(os.sep, /) out.write(f- {relpath}\n) out.write(\n---\n\n) out.write(\n.join(lines)) if __name__ __main__: files collect_files(., DEFAULT_INCLUDE_EXTS, DEFAULT_EXCLUDE_DIRS) merge_files(., files, knowledge_base.md) print(f合并完成共 {len(files)} 个文件 - knowledge_base.md)这个版本已经能解决 90% 的场景。核心逻辑说明如下os.walk遍历目录时我通过给dirnames赋值的方式实现目录过滤这样遍历过程本身就不会进入黑名单目录性能也好。文件排序使用相对路径保证每次生成的 知识库文件 里文件顺序一致不会因文件系统返回顺序不同而乱掉方便 diff 对比。合并部分使用errorsreplace即使某个文件编码特殊也不会中断整个流程。3.2 命令行化让工具随手可用脚本直接改参数当然也行但每次都改代码不太优雅。我给工具加上argparse参数解析让它变成一个标准的命令行工具import argparse def main(): parser argparse.ArgumentParser( description把指定目录下的代码文件和文本文件合并成一个知识库文件 ) parser.add_argument(root_dir, nargs?, default., help要扫描的根目录默认当前目录) parser.add_argument(-o, --output, defaultknowledge_base.md, help输出文件路径默认 knowledge_base.md) parser.add_argument(-e, --ext, nargs*, help要包含的扩展名如 py js md不传则用内置白名单) parser.add_argument(-x, --exclude-dir, nargs*, help额外排除的目录名) parser.add_argument(--max-file-size, typefloat, default0, help跳过超过该大小MB的文件0 表示不限) args parser.parse_args() if args.ext: include_exts {e if e.startswith(.) else f.{e} for e in args.ext} else: include_exts DEFAULT_INCLUDE_EXTS exclude_dirs DEFAULT_EXCLUDE_DIRS | set(args.exclude_dir or []) files collect_files(args.root_dir, include_exts, exclude_dirs, args.max_file_size) if not files: print(没有找到符合条件的文件请检查扩展名和目录。) return merge_files(args.root_dir, files, args.output) print(f合并完成共 {len(files)} 个文件 - {args.output}) if __name__ __main__: main()实际使用时比如要扫描当前目录下 Vue 项目的源码和文档只保留核心文件python merge_kb.py . -o vue_project.md -e vue js ts md json css要排除额外的test目录并且跳过超过 1MB 的大文件python merge_kb.py . -o vue_project.md -x test --max-file-size 1命令行化之后的优势是你可以把它固化到脚本、alias甚至 CI 流程里每天定时生成项目知识库快照完全不用手动干预。3.3 实测效果一次真实运行记录我在一个真实的中型 Vue 项目上测试了这套工具。项目结构是标准的srcdocspublic但根目录下还有node_modules和dist。命令python merge_kb.py . -o vue_project.md -e vue js ts md json css -x dist运行结果扫描了src、docs、public三个目录排除掉node_modules、dist、.git之后实际收集了 38 个文件生成的知识库文件约 500 KB整个过程在 1 秒内完成。打开生成的vue_project.md文件清单里能看到完整的项目结构# 项目知识库合并文件 - 根目录/Users/xxx/projects/vue-demo - 生成时间2025-01-15 14:02:31 - 文件数量38 - 总大小486.2 KB ## 文件清单 - docs/README.md - src/App.vue - src/api/user.js - src/components/HelloWorld.vue - src/utils/format.ts - ...接下来把这个文件丢给 AI或者在编辑器里全局搜索某个函数体验比想象中顺畅得多。看到这里你应该已经明白它本质上不是什么高深技术但确实能极大提升日常开发效率。4. 常见问题与排查技巧实录4.1 输出文件反被扫描内容越来越膨胀这个问题我刚开始用的时候天天踩生成的知识库文件放在项目根目录第二次运行脚本时它自己作为.md文件被扫描进去了导致输出文件里包含上一版的完整内容再跑一次又叠一层文件越来越大。解决思路有两个。最简单的办法是把输出文件放到项目外比如/tmp/kb.md或家目录这样扫描时根本不会碰到它。如果必须放在项目内就在collect_files里显式跳过输出文件名或者在默认排除目录中加上输出文件名。4.2 中文乱码与编码炸弹有次合并一个老项目里面有几个 C 语言源文件是用 GBK 编码写的脚本在读取时直接抛异常。后来我加上了errorsreplace但输出的中文全变成了“”这样的知识库文件对 AI 和人都没啥用。更好的做法是分级探测编码def read_text_with_fallback(filepath): # 先按 UTF-8 读取 try: with open(filepath, r, encodingutf-8) as f: return f.read() except UnicodeDecodeError: # UTF-8 失败再回退 GBK / GB2312 try: with open(filepath, r, encodinggbk) as f: return f.read() except UnicodeDecodeError: with open(filepath, r, encodingutf-8, errorsreplace) as f: return f.read()这个回退逻辑虽然比单次读取多一点代码但能保证绝大多数中文项目的中文内容不会变成乱码。实际你可以根据自己公司团队的编码习惯来调整回退顺序。4.3 文件太多、太大生成结果没法用有朋友把工具拿回去扫一个老项目结果从根目录一跑把所有历史代码和备份目录都算进去生成了几百 MB 的文件编辑器打开都卡。这不是工具的问题是范围控制的问题。我的一般建议是从子目录开始扫只扫src、docs、lib这类核心目录别动不动就从仓库根目录开始。扩展名白名单要精确不要图省事直接扫所有文本文件否则日志、锁文件、临时文件都进来了。设置--max-file-size比如 1MB 以上的文件单独看不放进合并结果。我自己通常把上限设在 1~2MB超过这个体量对 AI 上下文来说收益已经很低。按模块拆多个知识库文件比如src.md、tests.md、docs.md分场景使用比一个超大的合并文件更灵活。4.4 合并结果怎么喂给 AI 最高效很多人以为把合并文件丢给 AI 就完事了其实不是。直接粘贴一个 500 KB 的文本到对话框大多数模型要么报错要么“忘掉”前面的内容。我的经验是分情况处理如果用的是 Claude 这类支持附件或项目知识库的工具直接把文件上传让系统自己切片。这类工具对大文件的处理机制更完善不需要你手动压缩。如果只能复制粘贴先只贴“文件清单”部分让 AI 了解项目结构然后让它点名要哪几个文件的内容你再按需粘贴对应代码块。这样对话上下文消耗小AI 的回答也更聚焦。如果你定期更新知识库文件建议给文件带日期如kb-2025-01-15.md方便回溯版本。4.5 路径分隔符在 Windows 上的坑Windows 下os.path.relpath返回的是反斜杠路径比如src\utils\format.ts。把这种路径写进 Markdown 知识库文件在多数编辑器和 AI 眼里都容易出问题——Markdown 里反斜杠有特殊语义而且跨平台分享时路径风格不统一。我在代码里用.replace(os.sep, /)统一转成斜杠这样在 Windows、macOS、Linux 下生成的 知识库文件 路径风格一致AI 识别得也更准。这个细节不贵但很值得加。如果后续还想增强可以给工具加上文件内容行数统计、最近修改时间排序、按目录分组输出、生成 JSON 格式的知识库索引甚至配合 Git 只合并当前分支改动过的文件。技术实现都不复杂按自己的需求慢慢扩展就好。我个人在实际操作中的体会是工具本身越简单越不容易坏真正有用的是里面那几条朴素的规则——排除该排除的、保留路径边界、兼容各种编码。把这个工具跑通一次之后每次给 AI 传项目背景你就再也不用手动复制了直接一条命令生成干净利落。最后再分享一个小技巧合并出来的知识库文件我经常直接用编辑器打开做全局搜索整个项目的关键词一次就能搜完比在 IDE 里逐个目录找文件快得多甚至能搜出很多你都快忘了的注释和配置项。这个小惊喜是我当初写这个工具时完全没想到的意外收获。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价