Repository navigation
[Enhancement] sources:用 @name/ 前缀挂载远程团队知识库与 submodule 文档,不新增子命令 #63
Description
背景
种子用户反馈希望 llmdoc 支持团队级别的知识结构:跨仓库共用的领域知识、服务间契约、团队规约,尤其在微服务多仓库场景下价值明显。用户目前的做法是 fork V2 自行实现,并依赖 V2 的 must/ + hooks 让规约始终在上下文里。
V3 已经具备承接这件事的原语:startup.preload 是 must/ 的替代物,llmdoc.config.json 是知识控制面,relations / code.paths 是路由层。缺的只是“把仓库之外的文档挂进来”这一层。
目标与非目标
目标:
- 用一个概念
sources覆盖两类来源:远程团队知识库(git 地址,仓库内可有多个 folder)和仓库内路径(典型是 git submodule)。 - 既有读取命令(
tree/index/search/show/context)和startup.preload通过@name/前缀寻址 source 文档,不新增任何子命令。 - 远程文档采用松散契约:任意
.md/.mdx,只要 YAML front matter 带description即可,不要求遵循完整 V3 结构。 - 本地缓存目录直接 clone 远程地址,按固定间隔 fast-forward pull。
非目标(本 issue 不覆盖):
- 门禁 / CI 阻断 / PreToolUse 拦截。
- 团队级“行为规范”的执行。这部分仍建议以 team Skill 或 agent plugin 承载;llmdoc 只负责让规约正文可检索、可 preload。
- 对 source 文档的写入。source 文档只在它自己的仓库里维护。
设计
配置
llmdoc.config.json 新增 sources 字段。一个 source 要么是 git,要么是 path,二者互斥:
{
"schema": "llmdoc.config/v2",
"sources": {
"team": { "git": "git@github.com:acme/team-knowledge.git", "ref": "main", "dir": "conventions", "refresh": "24h" },
"shared": { "path": "libs/shared-proto" }
},
"startup": {
"preload": ["architecture.mdx", "@team/service-baseline.md"]
}
}dir:远程仓库内的 folder,省略则挂整个仓库。同一 git 地址挂多个 folder 时只 clone 一份,name 只是视图。ref:分支、tag 或 commit;给 commit 即锁版本。refresh:pull 间隔,缺省值待定(建议24h)。- 配置 schema 当前是封闭对象(
additionalProperties: false),新增字段必须升到llmdoc.config/v2。旧版 CLI 读到 v2 应明确报“版本过低”,而不是像现在一样整份判无效再静默退回默认 reminder。
文档根与松散契约
- 挂载目录下存在
llmdoc/就以它为文档根(标准 V3 仓库挂进来后 ID 里没有多余的llmdoc/段);不存在则以挂载目录本身为根,目录深度不限。 - source 内任意
.md/.mdx,front matter 有description即为一篇文档。kind缺省reference;code.paths、relations可选。无 front matter 的文件直接忽略,不报错。 - source 自带的
meta.json消费方不解析。 - source 文档的
code.paths只做语法检查,不校验文件存在——团队规范的 glob 在某个具体仓库里匹配不到文件是常态。
寻址与只读
- 文档 ID =
@name/+ 相对文档根的路径。show/search/index/context/tree/startup.preload全部识别该前缀。 index --topic @team列整个 source,--topic @team/proto列其子目录,不加新 flag。- source 文档只参与读取命令;
new/mv/adopt/fingerprint/commit/prune遇到@前缀一律拒绝。 - 本地文档可以
requires一篇@team/文档,反向不允许。 @成为保留字符,本地 topic 名不允许以它开头。
团队级配置只开一个口
source 目录里如果有 llmdoc.config.json,其 startup.preload 默认被消费方继承(路径相对 source 文档根)。消费方在 source 条目上写 "preload": false 可关闭继承。这就是“团队规约一定在上下文里”的团队侧开关,复用同一个 schema,不引入新文件格式。其他行为类配置不接。
路径匹配规则
context 用消费仓库的文件路径匹配 source 文档的 code.paths 时,同时尝试仓库相对路径与 source 相对路径,任一命中即算。团队规范写 **/Dockerfile 走前者,submodule 自己的文档写 proto/** 走后者,不需要两套语义。
同步、展示与新鲜度
缓存目录与同步
- 缓存目录建议
.llmdoc-tmp/sources/<name>/:该前缀已在 implementation surface 排除列表中,也已约定 gitignore,零新增代码。 - 同步规则:读取命令首次遇到缺失缓存时同步 clone 一次并提示;之后只有 SessionStart hook 和
status会触碰网络,超过refresh间隔就跑一次 fast-forward pull(带超时),失败则继续用旧缓存并在信号里说明。search/show永远不在网络上等待。 - 这保持了 hook 只读且 fail-open 的既有不变量:pull 写的是缓存,不是知识。
tree 直接列出 sources
llmdoc/ (8 docs, ~2863 tokens)
...
sources (2 configured)
@team git acme/team-knowledge#main:conventions 5 docs, synced 3h ago, at 1a2b3c4
@shared path libs/shared-proto 3 docs, submodule at 9f8e7d6
hint: `index --topic @team` 查看某个 source;`status` 查看同步状态与上游落后数
- 未配置任何 source 时,
tree末尾只加一行提示可在配置里加sources挂载团队或共享知识。 - 缓存缺失或 pull 失败时对应行显示原因与 remedy。
status追加一段 source 状态:分支型 ref 显示上游是否有新 commit,commit 型 ref 显示 pinned。
新鲜度耦合沿用现有模型
- 消费方
llmdoc/meta.json增加sources.<name>.validatedRevision,记录本地文档上次验证时该 source 的 commit。 delta在 source 目录里 diff 记录值到当前 HEAD,变化的 source 文档沿 reverserequires把本地文档标为 needs-review。fingerprint/commit顺带推进该记录。source 目录不是 git 仓库时 revision 为空,按现有规则保守抬到 deep。- source 文档本身永远不出现在
impacts里——消费方改不了它们。
submodule:不需要专门命令
submodule 就是一个 path 类 source。已核对:submodule 的 .git 是文件,但 findNearestGitRootOrNull 用 existsSync 判定,所以在 submodule 目录内运行 CLI 已经把它当作独立 root,update / commit 直接可跑。
更新共享库文档的流程写进 skill reference 即可:进入 submodule 目录 → 建分支 → 普通 update + commit → 推送开 PR → 回到外层 bump gitlink。
顺手需要修两处:
commit当前不检查 detached HEAD;submodule 检出后默认就是 detached,直接提交会产生游离 commit,外层一次git submodule update就丢。应拒绝并提示先建分支(主仓库同样受益)。- gitlink 路径不会命中
dir/**映射(已用 minimatch 实测),bump 一次就多一条 unmapped 噪音。把已配置的 source 路径排除出 implementation surface 即可。
安全提醒(需写进文档)
preload 会把远程正文原样注入每个消费仓库的会话,团队库被篡改等于对全团队做 prompt 注入。v1 不做签名校验,但文档中应建议对外部团队库用 tag 或 commit 锁定,status 始终显示当前 commit。
实现落点
- source 文档放在
WorkspaceData.sourceDocuments,不混进documents。维护类命令按构造就看不到它们,只有五个读取命令需要合并输出。 - 新增文件只有
cli/src/lib/sources.ts:解析配置、保证缓存存在、按松散契约扫描并产出带@name/的ParsedDocument。 - 其余改动:schema 升 v2、config 解析
@前缀、state.ts排除 source 路径、hook 与status加同步与展示。
建议分三步交付:
- 只读挂载:配置 v2、loader、五个读取命令的
@寻址、preload 继承、tree提示、clone 与定时 pull。这一步即可覆盖核心诉求。 - 新鲜度耦合:meta 里的 source revision、
delta的 needs-review 传播、status的上游落后数。 - submodule 顺手项:detached HEAD 拒绝、gitlink 排除、skill 里的共享库更新流程。
待拍板
- 缓存目录用
.llmdoc-tmp/sources/还是独立可见的.mdoc/。推荐前者(零新增排除规则);若选后者,代价只是多一个排除前缀。 - 首次 clone 是否允许在
search/show这类读取命令里同步发生。推荐允许,仅限首次。 context输出里 source 文档与本地文档混排还是单独一段。推荐混排,靠@前缀区分。commit遇到 detached HEAD 是拒绝还是只警告。推荐拒绝。refresh缺省值与网络超时值。
已核对的现有实现约束
cli/schemas/config.schema.json:封闭对象,schema为const "llmdoc.config/v1"。cli/src/lib/fs.tsfindProjectRootOrNull:只接受最近 Git 根直属的llmdoc/;.git用existsSync判定,submodule 的.git文件同样命中。cli/src/lib/state.tsisImplementationSurfacePath:llmdoc.config.json、llmdoc/、.llmdoc-tmp/已排除。cli/src/lib/workspace.ts:llmdoc/内只允许.mdx与根级meta.json,topic 不允许嵌套,code.paths精确路径不存在或 glob 空匹配为阻塞错误——因此 source 不能挂在llmdoc/内部,且 source 文档的路径校验必须走独立分支。cli/src/commands/commit.ts:不检查 detached HEAD。
Activity
Disdjj commentedon Sep 4, 2026