proj-doc-builder:让 AI 读得懂、能校验的工程文档索引

前言

接手陌生代码库,常见做法是让模型直接扫源码:读得慢,上下文吃紧,每次新会话还要从头再来。另一种做法是让模型顺手写一堆 Markdown,问题是「已同步」没法证伪——没有清单可核对,改了什么全靠写文档那一次输出自己说。

下面介绍的 proj-doc-builder 走第三条路。工具负责结构:目录大纲、内容哈希基线、发布事务;正文由人和模型写;加载时由独立脚本先校验 manifest,再交给模型。作者 walvka-code,仓库在 walvka-code/proj-doc-builder,以 DeepSeek Harness bundle(@walvka-code/proj-doc-builder)和普通 CLI 两种形态发布,归类为工作流插件。

它解决什么

README 把问题拆成两条。

  1. 要快速梳理陌生代码库,还要留下「后续改了什么」的基线。插件为每个源码目录维护一份 Markdown 大纲,内容是文件清单、关键符号、依赖关系、该目录在系统中的角色;同时用 _scan_state.json 记住上次同步时源码的样子,下次同步只处理真正变化的目录。
  2. 要让新起的 AI 对话直接拿到工程结构。加载器 tools/load_docs.py 先校验 manifest 与磁盘文档逐份一致,再输出概览(目录树、元数据、小节名)或单独某份文档。写侧和读侧共用一份契约,不会各写一套解析而漂移。

它的立场写在 README 开头:文档「已同步」必须由工具写下的 manifest 背书,而不是由生成它的模型自述。理由也直接——生成者与校验者必须是两个执行体,否则那句「已同步」永远不能被证伪。

核心保证

README 列了七项机制,针对的是批量更新文档时常见的崩点。

  • 默认零写入:不带 --apply 一律只报告,staging 放在系统临时目录。
  • 单一提交点:project_docs/_manifest.json 的那一次原子替换是文档代际的唯一 commit point。
  • 不覆盖并发写入:发布走 no-replace rename,Windows 是 os.rename,Linux 是 renameat2(RENAME_NOREPLACE)。
  • 失败必须分类:只报 NOT_INSTALLED / INSTALLED / UNKNOWN / PARTIALLY_INSTALLED,不会虚构「已提交」。
  • 不污染工作副本:锁、事务 journal、CAS 产物都在临时目录。
  • 删除是举证而非清理:只有源码目录确认消失、零入站引用、带精确 marker 且已注册时才产生隔离候选,默认只报告。
  • 幂等:零变化运行输出 planned_writes == [],不刷新 last_synced。

安装

插件提供三种安装方式,以下命令照录 README 原文。

方式一,DSH bundle,推荐路径。在 Plugins → More → Custom plugin markets → Discover 里加入市场索引地址:

https://walvka-code.github.io/proj-doc-builder/index.json

同一份索引也发布在 https://raw.githubusercontent.com/walvka-code/proj-doc-builder/main/index.json,但部分网络无法直连该域名,以 GitHub Pages 地址为准。不想用市场可以钉住某个 commit,bundle 是仓库里的一个包目录:

git+https://github.com/walvka-code/proj-doc-builder.git#<40 位 commit>

也可以用 plugin_manager 装本地检出:

{ "action": "install_bundle", "target": "C:/path/to/proj-doc-builder" }

方式二,当项目技能用。把 skills/proj-doc-builder/ 复制到 <project>/.dsh/skills/proj-doc-builder/,SKILL.md 按宿主报告的技能目录解析工具路径,两种布局都成立。

方式三,当普通 CLI 用,不依赖 agent 框架:

git clone <this-repo> proj-doc-builder
SKILL="$(pwd)/proj-doc-builder/skills/proj-doc-builder"
python "$SKILL/tools/sync_project_docs.py" /path/to/your/project --dry-run --json

运行条件来自 README:Python 3.12+,运行时只用标准库;测试需要 pytest,见 requirements-dev.txt。bundle 声明来自 package.json:node >= 20,dsh >= 0.2.1-rc.5。项目侧前置条件是根目录有 project_docs/ 和 CLAUDE.md 两个入口。

典型用法

两个脚本分工明确:sync_project_docs.py 生成和发布索引,load_docs.py 给新对话提供入口。先 dry-run 看变更、确认后 --apply,是 README 给出的固定姿势:

# 技能目录:SKILL.md 和 tools/ 都在这里
SKILL="$(pwd)/skills/proj-doc-builder"

# 同步文档索引:先看变更,确认后再发布
python "$SKILL/tools/sync_project_docs.py" . --dry-run --json
python "$SKILL/tools/sync_project_docs.py" . --apply --json

# 给新对话一个入口:先概览,再按源码目录定位
python "$SKILL/tools/load_docs.py" . --json
python "$SKILL/tools/load_docs.py" . --mode target --target src/core --json

加载器的取用粒度更细:

# 全部文档、只要元数据
python "$SKILL/tools/load_docs.py" . --limit 0 --sections 0 --json

# 定位到一份文档(源码目录、文档路径、工程相对路径都可)
python "$SKILL/tools/load_docs.py" . --mode target --target project_docs/src/core.md --json

# 只取某一份里的某一节
python "$SKILL/tools/load_docs.py" . --mode target --target src/core --section 依赖关系 --json

加载器的核心是先校验再加载:manifest 与磁盘文档不一致,比如被改动过、多出未注册文档,就以退出码 1 报 manifest-stale,不会拿陈旧索引去服务模型。生成侧退出码:0 全过,1 闸门失败(含存在待恢复事务),2 输入或运行错误,3 发布失败。3 不等于未发布,以 publish.disposition 为准。

工作流与数据模型

生成过程分六个阶段,协调器不生成自然语言,正文由生成方写,它只做校验和事务发布。

  1. 预检:确认项目根有 project_docs/ 和 CLAUDE.md,无待恢复事务。
  2. --dry-run 得到变化分类和工作清单,键是 scan.worklist.scaffold。
  3. 生成方按清单逐份写正文,各份过 --strict 校验。
  4. 全局 dry-run 闭环,做断链、阻断项和 manifest 覆盖面检查,要求 validation.blocking_failures == []。
  5. 退役报告:列出源码已消失且零引用的文档,默认只报告。
  6. --apply 发布,产出 manifest、CLAUDE 文档树和 scan state,终验要求 planned_writes == []。

数据落在三个位置。project_docs/_manifest.json 是 schema v2,每份文档记录 path、depth、title、status、bytes、sha256、synced_at,另给 generation_id。project_docs/_scan_state.json 是源码目录快照,README 直接称它为「改动跟踪基线」。CLAUDE.md 有六组 marker,工具只替换第一组 doc-tree-instruction,其余只校验 marker 唯一、成对、不交叠。

平台与测试

平台支持按 README 表格:Windows 已验证,说明提到重解析点与 junction 识别、句柄级删除、msvcrt 锁;Linux 已验证,说明提到 fcntl 锁、逐级 O_NOFOLLOW、renameat2 和目录 fsync;macOS 未验证,有 renamex_np 分支但没有 CI。两处细节在原文里被单独强调。

Linux 上 renameat2(RENAME_NOREPLACE) 是硬依赖,不可用时抛 NoReplaceRenameUnsupported,明确拒绝回退到会覆盖的 rename。目录 fsync 被拒时降级但不中断,报告暴露 durability-degraded,真实 I/O 错误仍上抛。

测试运行方式:

python -m pytest skills/proj-doc-builder/tests -q

README 写有 360 个用例,Windows 上 354 passed、6 skipped,全部用 tmp_path,不触碰真实 project_docs/。用例锁定的不是功能而是崩溃一致性:manifest 之前失败必须完整回滚,之后失败只准报 installed / partial / unknown;覆盖伪造 journal 槽位、ABA 竞态、staging 期间文档被并发修改。

取舍也写在 README 里:事务 journal 和 CAS 产物目录必须与项目根同一文件系统,跨盘会让 --apply 直接拒绝;路径只接受相对 POSIX、小写 .md,拒绝绝对路径、盘符、反斜杠、..、ADS、保留设备名和大小写冲突;不跟随 symlink、junction、reparse point;--full 只是扫描器的 force_full 开关,不等于逐份盘点文件清单。

适用场景与注意

适合这类场景:接手陌生代码库,需要快速建立结构认知;希望新起的 AI 会话直接加载工程画像;希望文档同步能进 CI,且结论由 manifest 提供,不靠模型自述。

几条提醒。插件以当前 DSH 进程权限运行,发布文档和替换 CLAUDE.md marker 都是写盘动作,安装前建议打开源码和许可证核对行为。许可证是 MIT。需要钉版本或离线分发时,用 git+https://... #<commit> 或本地 checkout,不要依赖市场地址的可用性。macOS 上行为未经验证。文档退役是举证流程而非清理工具,默认只报告。

结尾

proj-doc-builder 解决的不是文档不够多,而是文档无法被证伪。它给出的三样东西都写在文件里:一份工具写入的 manifest、一份内容哈希基线、一次原子发布的事务边界。换来的是每一次「文档已同步」都能被独立复核,而不需要信任写文档的那一方。

  • 插件目录页:https://www.skillhub.cn/plugins/walvka-code/proj-doc-builder
  • GitHub:https://github.com/walvka-code/proj-doc-builder
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜