主题
知识库与 Wiki 协编
本章目标:
- 理解 a-cdm 知识库为什么不自建存储,而是把 GitLab 单仓
a-cdm/llm-wiki(raw/+wiki/两层 Karpathy 模式)当唯一真源,以及 acdm-backend 如何做一层薄代理- 看懂
PUT /wiki/file的乐观锁双层防御(预检 sha 比对 + GitLablast_commit_id原子 CAS)如何把多人协编冲突收敛成业务码 40901- 弄清 AI 改写/续写 SSE 端点、
[human]/[ai-assist]/[kc]服务端强制 commit 前缀、index.json全局类目 CRUD 与前端 WikiEditor 协作组件的全链路
TL;DR
a-cdm 知识库没有自己的数据库表,真源是 GitLab 仓 a-cdm/llm-wiki,目录分 raw/(原始材料,compiler 写)和 wiki/(结构化知识,人 + AI 编辑)两层。wiki_router.py 通过 GitLabClient 代理 GitLab v4 REST 做五件事:列树/读文件/历史/写文件/改名删除。多人编辑同一文件靠乐观锁:前端打开时拿 last_commit.sha,保存时回传 expected_sha,服务端先预检比对当前 HEAD,再把它作为 last_commit_id 交给 GitLab 做原子 CAS,任一层不一致都翻译成 40901 冲突码,前端弹"已被他人修改"对话框。commit message 前缀([human]/[ai-assist])由服务端强制,不信任前端。AI 协编是两个 SSE 流式端点,走 ai_registry 的 wiki_ai_rewrite/wiki_ai_extend role,只生成建议、用户点"采用"才回写编辑器、不自动 commit。全局知识库分类卡通过 knowledge_router 对仓根 index.json 做 CRUD,同样用 CAS。
Overview(为什么知识库要"寄生"在 GitLab 而不是自建)
知识库要回答一个问题:一个企业级 PMO 知识库,如何同时满足版本历史、多人协编冲突检测、AI 自动写入、署名审计、可被 Agent 检索这五件事,又不让 a-cdm 自己背一套内容存储 + 版本引擎的债?
直觉做法是建张 wiki_doc 表,字段塞 markdown,加个 version 列做乐观锁。但这条路有四个硬伤:
- 版本历史要重造轮子:diff、blame、回滚、按文件查 commit 历史,全得自己写,而 git 天生就有
- AI 写入与人写入混在一起无法审计:谁改的、人改还是 AI 改、是会议编译器灌进来的还是 BA 报告回写的,需要一个统一的"署名"机制
- Agent 检索要再建索引:deer-flow 的 lead_agent 要能搜知识库,自建存储还得再挂一套全文搜索
- 多写入源:不只是前端编辑器,还有 BA 报告回写(
[ai-assist])、会议/资料编译器([kc]/[material]),它们都要往同一个地方写
答案是把 GitLab 单仓 gitlab.r7.chinasoftinc.com/a-cdm/llm-wiki 当唯一真源,采用 Karpathy LLM-Wiki 模式的多类型扩展——raw/(原始材料层,编译器写)+ wiki/(结构化知识层,人 + AI + 编译器写)两层目录(openspec/specs/llm-wiki/spec.md:37-60)。git 自带版本历史/diff/blame;commit message 前缀解决署名审计;GitLab 的 last_commit_id 参数天生是个原子 CAS,解决多人冲突;GitLab Search API 让 Agent 经 MCP 直接检索(见第 26 章)。acdm-backend 只做一层薄代理:把前端友好的 REST 形状翻译成 GitLab v4 调用,加一层路径校验和前缀强制。
Architecture:薄代理 + 单一 GitLab 客户端
整个知识库子系统的代码量很小,因为重活全交给 GitLab。三个 router 都在 acdm-backend/app/main.py:456-463 挂载,共享同一个 GitLabClient,凭据从 settings 三个字段读(config.py:104-114)。
| 组件 | 职责 | 入口文件 | Source |
|---|---|---|---|
wiki_router | tree/file/history/PUT/DELETE/rename/meta/ai 端点(挂 /wiki 前缀) | app/api/wiki_router.py | acdm-backend/app/api/wiki_router.py:48 |
knowledge_router | 全局类目卡 CRUD(端点自带 /knowledge 前缀) | app/api/knowledge_router.py | acdm-backend/app/api/knowledge_router.py:28 |
GitLabClient | GitLab v4 REST 封装(列树/读文件/commits/commit/search) | app/util/gitlab_client.py | acdm-backend/app/util/gitlab_client.py:30 |
KnowledgeService | index.json 读写 + 类目文档数计算(CAS) | app/services/knowledge_service.py | acdm-backend/app/services/knowledge_service.py:28 |
LLMClient | AI 改写/续写的 SSE 流式 chat(按 role 选 provider) | app/util/llm_client.py | acdm-backend/app/api/wiki_router.py:726 |
WikiSyncService | BA 报告 P1-P5 内容回写 wiki([ai-assist]) | app/services/ba_report/wiki.py | acdm-backend/app/services/ba_report/wiki.py:24 |
KnowledgeWorkspace | 前端中央工作区容器,Viewer/Editor/History 切换 | components/.../wiki/KnowledgeWorkspace.tsx | deer-flow/frontend/src/components/workspace/acdm/wiki/KnowledgeWorkspace.tsx:63 |
WikiEditor | Monaco 编辑器 + 乐观锁保存 + AI 面板宿主 | components/.../wiki/WikiEditor.tsx | deer-flow/frontend/src/components/workspace/acdm/wiki/WikiEditor.tsx:43 |
GitLabClient.__init__ 是 fail-fast 的:GITLAB_WIKI_PAT 或 GITLAB_WIKI_PROJECT_ID 没配就抛 RuntimeError,由 router 的 _get_client() 转成业务码 50301(wiki_router.py:60-65)。Project ID 支持三种形式——numeric 123 / slug a-cdm/llm-wiki(构造时自动 URL-encode)/ 已编码 a-cdm%2Fllm-wiki(gitlab_client.py:37-42),这让同一份代码既能跑在 numeric-id 的 GitLab 也能跑在 slug 寻址的环境。
Components / Subsystems
GitLabClient — 唯一的 GitLab 出口
职责:把 GitLab v4 REST 封装成 5 类异步方法,所有写入源(router / KnowledgeService / WikiSyncService / MCP wiki tool)共用它。
关键类:GitLabClient 在 acdm-backend/app/util/gitlab_client.py:30
关键方法:
list_tree(path, ref, recursive)(gitlab_client.py:47):GET /repository/tree,自动翻页(读x-next-pageheader 循环直到取完),recursive=true时递归。中文/特殊字符路径直接传原始串,httpx 对 params 自动 URL-encode。get_file_raw(path, ref, timeout)(gitlab_client.py:86):GET /repository/files/:path/raw,path 用quote(path, safe="")编码;timeout可被调用方覆盖(/wiki/meta用 5s 短超时防慢文件拖垮批量)。get_file_commits(path, per_page, ref)(gitlab_client.py:108):取单文件最近 N 条 commit,乐观锁的 sha 来源就是commits[0]["id"]。commit_file(...)(gitlab_client.py:196):POST /repository/commits单文件 create/update/delete,核心参数last_commit_id即 GitLab 原子 CAS——传了它且当前 HEAD 上该文件 last commit id 不等于此值,GitLab 返回 400 带"changed since"/"last_commit_id"。commit_actions(actions, ...)(gitlab_client.py:150):通用多动作 commit,delete/move/批量都走它。search_wiki_blobs(query, limit, path_prefix)(gitlab_client.py:121):GET /search?scope=blobs全仓内容搜索,path_prefix是客户端侧过滤(GitLab API 本身不支持 path 参数),供 knowledge-sources MCP 用(见第 26 章)。
实现要点:web_url_base(gitlab_client.py:179)在 PROJECT_ID 为 numeric 时返回空串——拼不出可读 UI URL,这种情况让前端不显示 commit 链接,而不是给个坏链接。
wiki_router — 路径校验是第一道闸
职责:把 GitLab tree/file/commits 响应投影成 ApiResponse 形状,并在写入前强制路径与前缀规则。
关键方法:三层路径校验体现了"读宽写严":
_validate_read_path(wiki_router.py:68):GET 用,只拒路径穿越(../ 以/开头)_validate_write_path(wiki_router.py:78):PUT 用,要求落在wiki/或raw/前缀下且以.md结尾(Phase 1 仅允许 markdown,防 PMO 写到仓根搞乱)_validate_wiki_only_path(wiki_router.py:91):DELETE / rename 专用,更严——只允许wiki/前缀,明确不允许删raw/或仓根(spec 强约束)
前缀提取:_extract_prefix(wiki_router.py:54)用正则 ^\[([a-zA-Z][a-zA-Z-]*)\] 从 commit message 提取首个 [xxx],投到 WikiCommitInfo.prefix,前端据此显示 human/ai-assist/kc/material/bot 的 badge。
DELETE 与 rename 都用 require_admin 依赖(wiki_router.py:443,498)——破坏性操作只有管理员能做,普通成员只能 PUT(创建/更新)。rename 当前期只支持同目录原子改名(_parent_dir 相等),跨目录走 50304 让用户去 GitLab UI 操作(wiki_router.py:510-514)。
KnowledgeService — 全局类目卡的 index.json
职责:维护仓根 index.json(全局知识库的分类卡元信息),并实时算每个类目下的 .md 文档数。
关键方法:
get_index(ref)(knowledge_service.py:32):读index.json→json.loads→IndexJson.model_validate;404 视为空 categories(返回(空 IndexJson, ""));sha 单独用get_file_commits取。格式/结构错抛50101。put_index(...)(knowledge_service.py:64):last_commit_id=""走 create(GitLab 对 create 忽略此参),否则 update + CAS;GitLab 400 含exist/changed/stale翻译成40901冲突。count_docs(path)(knowledge_service.py:107):两段策略——先非递归取当前层数.md(快);当前层无.md但有子目录才递归兜底;超大目录(如 allmeta-manual 25585 节点)递归超时返回(None, 错误)而不是显示 0,这样前端能 tooltip 提示而非误导用户"这个类目没文档"。count_docs_many(paths)(knowledge_service.py:175):asyncio.gather并发数多个类目。
KnowledgeCategory(schemas/knowledge.py:13)有强约束:slug 必须匹配 ^[a-z][a-z0-9-]{1,30}$、color 是 6 个枚举值之一、tags 最多 5 个。
前端 KnowledgeWorkspace / WikiEditor / WikiAIPanel
职责:KnowledgeWorkspace 是项目级 KB 和全局 KB 双头复用的中央工作区容器(KnowledgeWorkspace.tsx:63)——项目级传 rootPath="wiki/by-project/${id}" + canEdit=true,全局 KB 传 rootPath={category.path} + canEdit={user.is_admin}。注意 canEdit 只控按钮显隐,真鉴权闸门是后端 PUT /wiki/file(KnowledgeWorkspace.tsx:46),前端绕开按钮也写不进去。
WikiEditor(WikiEditor.tsx:43)用动态导入的 Monaco(ssr:false),baseSha prop 即打开时的 last_commit.sha,是乐观锁的客户端起点。EditorBridge(WikiEditor.tsx:119-158)是 editor 与 AI 面板之间的桥:getSelection/replaceSelection/appendToEnd/getFullContent,任何 AI 回写都置 hasAIEdit=true,保存时 source 自动变 "ai-assist"(WikiEditor.tsx:82)。
WikiAIPanel(WikiAIPanel.tsx:33)两种模式:rewrite 改写选区(无选区则整文)、extend 续写到文末。SSE 流式预览在面板内,用户点"采用"才经 bridge 回写 Monaco,不自动 commit——这是有意的:AI 产出要人确认,符合 spec "前端在 diff 预览 → 用户点应用后插入编辑器,不自动 commit"(openspec/specs/llm-wiki/spec.md:256)。
Data Flow:乐观锁保存的双层防御
这是本章最关键的算法。多人编辑同一 wiki 文件,如何在不引入分布式锁的前提下,既不丢别人的改动,又给用户清晰的错误?
PUT /wiki/file(wiki_router.py:283)的设计是预检 + GitLab CAS 双层防御,把"读到写之间被别人改了"这个 race 收敛成业务码 40901(WIKI_CONFLICT_CODE,wiki_router.py:264)。
逐步拆解:
- 路径与 message 校验(
wiki_router.py:304-307):写路径必须wiki//raw/前缀 +.md,message 非空。 - 预检层(
wiki_router.py:317-346):只有传了expected_sha才走。get_file_commits(per_page=1)取当前 HEAD 上该文件的 last commit id,与客户端的expected_sha比对。不一致直接40901,错误信息带current_sha=X, expected_sha=Y让前端能拉 diff。文件不存在(404)但用户传了expected_sha,说明他以为文件还在 → 也判冲突(被删了)。 - GitLab CAS 层(
wiki_router.py:348-358):_do_commit把expected_sha作为last_commit_id传给 GitLab。为什么预检过了还要这一层? 代码注释明确写了"预检→提交间有 race"(wiki_router.py:296):预检到提交之间存在窗口,另一并发提交在此窗口内落地后预检的结论就失效了。GitLab 的last_commit_id是服务端原子 CAS,把这个窗口堵死——这是预检层无法覆盖的部分。 - 冲突翻译(
wiki_router.py:362-398):GitLab 返 400 分三种——"doesn't exist"(文件不存在:有 expected_sha → 被删冲突40901;无 → fallback create);_looks_like_cas_conflict命中last_commit_id/changed since/conflict等关键词(wiki_router.py:269-280)且有 expected_sha → race 冲突40901;其它 →50305运维错。
前端对 40901 的处理(WikiEditor.tsx:88-100):doSave 捕获 WikiError.code === WIKI_CONFLICT_CODE 时弹 window.confirm,选"确定"= doSave(false) 这次不带 sha 强制覆盖;选"取消"= 放弃,提示退出重开合并。withLock 参数(WikiEditor.tsx:74)即是否带 baseSha,这就是"强制覆盖"的实现:退化回旧的"后写覆盖前"行为。
Data Flow:AI 协编 SSE 流
AI 改写/续写不走任何写入,纯生成。POST /wiki/ai/rewrite(wiki_router.py:744)和 /wiki/ai/extend(wiki_router.py:761)都返回 EventSourceResponse,经 _stream_llm 包装 LLMClient.chat_stream。
后端 _stream_llm(wiki_router.py:712)的协议:LLMClient(role_id) 构造失败(ai_registry 抛 50401-50404 或 RuntimeError)→ yield event=error;正常时逐 chunk yield event=delta,结束 yield event=done;HTTP 异常 yield event=error。role_id 是 wiki_ai_rewrite / wiki_ai_extend,在 ai_roles.yaml:63-72 注册——provider jointpilot、model kimi-k2.6,且 force_overrides.thinking 关掉(ai_roles.yaml:33-39,工具型短任务 thinking 没价值还会挤光 content)。
_AI_SYSTEM_PROMPT(wiki_router.py:688)四条硬约束:只输出结果文本、不加解释/前言、不要代码块围栏包裹、保持中英文与 markdown 语法。/wiki/ai/extend 还会保守截断光标前文本到后 2000 字符防上下文爆(wiki_router.py:765)。
前端 parseSseStream(wiki-api.ts:164)是个 AsyncGenerator,逐行解析 data: 前缀、跳过 [DONE],WikiAIPanel 用 for await 消费并累积到 preview,AbortController 支持中途"停止"(WikiAIPanel.tsx:63-67)。
速查表:Wiki / Knowledge API 端点
| 端点 | 方法 | 鉴权 | 作用 | Source |
|---|---|---|---|---|
/wiki/tree | GET | 登录 | 列单层目录(懒加载) | acdm-backend/app/api/wiki_router.py:111 |
/wiki/validate-path | GET | 登录 | 探测路径在仓中是否可达(项目设置弹窗用) | acdm-backend/app/api/wiki_router.py:131 |
/wiki/list | GET | 登录 | 递归列所有 .md(前端搜索建索引) | acdm-backend/app/api/wiki_router.py:189 |
/wiki/file | GET | 登录 | 读单文件原文 + 最近 commit | acdm-backend/app/api/wiki_router.py:210 |
/wiki/history | GET | 登录 | 文件最近 N 条 commit 历史 | acdm-backend/app/api/wiki_router.py:240 |
/wiki/file | PUT | 登录 | 创建/更新(乐观锁 + [source] 前缀) | acdm-backend/app/api/wiki_router.py:283 |
/wiki/file | DELETE | admin | 删除单文件([human] 前缀) | acdm-backend/app/api/wiki_router.py:440 |
/wiki/file/rename | PUT | admin | 同目录原子改名(GitLab move) | acdm-backend/app/api/wiki_router.py:495 |
/wiki/meta | GET | 登录 | 批量取 frontmatter/H1/首段(≤50 路径) | acdm-backend/app/api/wiki_router.py:660 |
/wiki/ai/rewrite | POST | 登录 | 选区改写 SSE 流 | acdm-backend/app/api/wiki_router.py:744 |
/wiki/ai/extend | POST | 登录 | 文末续写 SSE 流 | acdm-backend/app/api/wiki_router.py:761 |
/knowledge/categories | GET | 登录 | 读 index.json + 实时算 doc_count | acdm-backend/app/api/knowledge_router.py:39 |
/knowledge/categories | POST | admin | 新增类目(CAS) | acdm-backend/app/api/knowledge_router.py:61 |
/knowledge/categories/{slug} | PUT | admin | 改类目(CAS,slug 不可改) | acdm-backend/app/api/knowledge_router.py:86 |
/knowledge/categories/{slug} | DELETE | admin | 删类目(CAS) | acdm-backend/app/api/knowledge_router.py:122 |
速查表:commit message 前缀(署名审计)
| 前缀 | 写入源 | 谁写 | Source |
|---|---|---|---|
[human] | PUT(source=human)/ DELETE / rename | 人工编辑 | acdm-backend/app/api/wiki_router.py:311,469,540 |
[ai-assist] | PUT(source=ai-assist)/ BA 报告回写 | 编辑器内 AI 协编 / BA 报告 P1-P5 回写 | acdm-backend/app/services/ba_report/wiki.py:114 |
[kc] | knowledge-compiler | 会议 → raw/meetings/ | openspec/specs/llm-wiki/spec.md:9 |
[material] | material-compiler | 资料/模板 → raw/materials/ | openspec/specs/llm-wiki/spec.md:9 |
关键不变量:前缀服务端强制,不信任前端。put_file 里 full_message = f"[{req.source}] {req.message}"(wiki_router.py:311),即便前端 message 里已含 [xxx] 也再包一层,req.source 被 schema 限定为 Literal["human","ai-assist"](schemas/wiki.py:73)。这就是审计可信的根:谁、人/AI、何来源,全在 git log 里且无法伪造。
Implementation Details:frontmatter 解析的降级链
/wiki/meta 给类目卡片视图批量取标题/描述/标签,_parse_md_meta(wiki_router.py:583)有三级降级,且只读首 100 行防大文件 OOM:
python
# 摘自 acdm-backend/app/api/wiki_router.py:589-630(节选)
head = "\n".join(content.splitlines()[:100]) + "\n"
# 1) YAML frontmatter(--- ... --- 块,yaml.safe_load 取 title/description/tags)
m = _FRONTMATTER_RE.match(head)
# 2) fallback: 没 title → 找首个 H1(# 标题)
if title is None:
h1 = _H1_RE.search(body)
# 3) fallback: 没 description → 找首个非空非标题段落首 80 字符_fetch_meta_one(wiki_router.py:633)对单文件用 5s 短超时,失败填 error 字段不阻塞其它;get_meta 用 asyncio.gather 并发(wiki_router.py:680),paths > 50 直接 50301。这套设计让一个慢文件或坏文件不会拖垮整个类目卡片渲染。
扩展指南:新增一个全局知识类目 / 新知识类型
加一张全局类目卡(经 API,需 admin):
jsonc
// POST /knowledge/categories body
{
"category": {
"slug": "hr-policy", // ^[a-z][a-z0-9-]{1,30}$
"path": "wiki/产品知识/HR", // GitLab 仓内相对路径
"title": "HR 制度",
"description": "公司人力资源制度文档",
"icon": "users",
"color": "blue", // blue|orange|purple|emerald|red|slate
"tags": ["制度", "HR"] // ≤ 5
},
"expected_sha": "<GET 时拿到的 index.json sha>", // "" = 创建第一份
"message": "新增 HR 制度类目"
}约束(从代码校验读出):
slug不可与现有重复(knowledge_router.py:69→40001),PUT 时 slug 不可改(路径参数与 body 不一致 →40001,knowledge_router.py:94)。expected_sha必须等于当前index.jsonsha,否则40901(knowledge_router.py:72-73,100-101),这是和 wiki 文件同源的乐观锁。- 类目卡只是元信息;实际文档放在
category.path指向的 GitLab 目录下,doc_count运行时实时算。
加一种新原始知识类型(不动架构):按 spec(openspec/specs/llm-wiki/spec.md:71-74),在 raw/ 下新增子目录(如 raw/trainings/),wiki/ 按需加 by-domain 分类,不需要改 Architecture 或 API——这正是两层 Karpathy 模式的扩展性所在:raw/ 子目录对应资料来源类型,wiki/ 子目录对应检索维度。
程序化回写 wiki(如自定义报告):参照 WikiSyncService.sync_to_wiki(ba_report/wiki.py:30)——直接构造 GitLabClient,用 [ai-assist] 前缀 commit_file;注意它做章节级幂等(## 章节名 已存在则跳过,ba_report/wiki.py:97-100),且只回写 P1-P5 来源、P6 LLM 推断不写入 wiki(ba_report/wiki.py:62-65)。
Configuration
| Config | 默认值 | 含义 | Source |
|---|---|---|---|
GITLAB_WIKI_BASE_URL | https://gitlab.r7.chinasoftinc.com | GitLab 实例地址 | acdm-backend/app/config.py:104 |
GITLAB_WIKI_PROJECT_ID | "" | numeric / slug / URL-encoded 三形式 | acdm-backend/app/config.py:109 |
GITLAB_WIKI_PAT | ""(敏感,需 api+write_repository scope) | Personal Access Token,不进库 | acdm-backend/app/config.py:113 |
WIKI_CONFLICT_CODE | 40901 | 乐观锁冲突业务码 | acdm-backend/app/api/wiki_router.py:264 |
WIKI_META_MAX_PATHS | 50 | /wiki/meta 单次最多路径数 | acdm-backend/app/api/wiki_router.py:578 |
STALE_FILE / STALE_TREE | 60s / 5min | 前端 React Query 缓存 staleTime | deer-flow/frontend/src/core/acdm/wiki-hooks.ts:16-17 |
wiki_ai_rewrite / wiki_ai_extend role | jointpilot / kimi-k2.6 | AI 协编 LLM provider/model | acdm-backend/app/ai_roles.yaml:63-72 |
GITLAB_WIKI_PAT 是安全相关配置:scope 需含 api + write_repository,绝不进代码库(项目红线),dev 在 acdm-backend/.env、生产 /data/apps/*/.env 手 scp。PAT 失效时 validate-path 等返 50305 运维异常而非误判用户输入错。
Common Pitfalls / 实战 Tips
/wiki/list对超大目录会超时:count_docs注释明确点名 allmeta-manual 25585 节点递归会超时(knowledge_service.py:119),设计上超时返回None而非 0,前端要把doc_count_error渲染成 tooltip,别把 None 当 0 显示。- 不传 expected_sha = 无冲突检测:新建文件场景本就不该传(无历史 sha);但老调用方/前端漏传会退化成"后写覆盖前",静默丢改动。WikiEditor 的
baseShaprop 没传时就是这个旧行为(WikiEditor.tsx:33-38)。 - rename 不支持跨目录:本期
_parent_dir不等就50304,要去 GitLab UI 手动操作(wiki_router.py:510-514)。 - AI 协编不自动 commit:
WikiAIPanel点"采用"只回写 Monaco 内存,真正落库还要在 WikiEditor 填提交说明点保存(走乐观锁)。两步是有意设计,别期望 AI 直接改 GitLab。 get_file的 commit 元信息是非阻断的:GitLab commits 查询失败时content仍返回、last_commit=null(wiki_router.py:228-237),此时前端拿不到baseSha,乐观锁会退化——内容能渲染但保存无冲突保护。- 路径含中文不要预编码:
list_tree直接传原始中文串,httpx 自动 encode;手动quote反而双重编码导致 404(gitlab_client.py:55-58注释)。
References
acdm-backend/app/api/wiki_router.py:1-777— wiki 全部端点(本章主源:路径校验/乐观锁/AI SSE/meta 解析)acdm-backend/app/util/gitlab_client.py:30-235— GitLab v4 REST 封装(列树/读/commits/commit/search)acdm-backend/app/api/knowledge_router.py:28-150— 全局类目卡 CRUD(CAS)acdm-backend/app/services/knowledge_service.py:28-179— index.json 读写 + count_docs 两段策略acdm-backend/app/schemas/wiki.py:1-155— Wiki 请求/响应 schema(WikiFilePutRequest 的 expected_sha 语义)acdm-backend/app/schemas/knowledge.py:1-71— KnowledgeCategory / IndexJson 约束acdm-backend/app/services/ba_report/wiki.py:24-148— BA 报告 P1-P5 回写 wiki([ai-assist]+ 章节幂等)acdm-backend/app/ai_roles.yaml:33-72— wiki_ai_rewrite/extend role(jointpilot kimi-k2.6,关 thinking)deer-flow/frontend/src/components/workspace/acdm/wiki/WikiEditor.tsx:43-247— Monaco + 乐观锁保存 + 冲突对话框deer-flow/frontend/src/components/workspace/acdm/wiki/WikiAIPanel.tsx:33-203— AI 改写/续写 SSE 面板deer-flow/frontend/src/core/acdm/wiki-api.ts:78-184— wiki API client + SSE 解析deer-flow/frontend/src/core/acdm/wiki-hooks.ts:55-130— React Query 读写 hooks + 缓存失效openspec/specs/llm-wiki/spec.md:37-74— raw/+wiki/ 两层 Karpathy 模式仓结构 + 扩展原则
Related Pages
| Page | Relationship |
|---|---|
| BA 专家报告工作流 | 该章 8 步骤的 P1-P5 内容经本章 WikiSyncService 回写 wiki([ai-assist]) |
| 知识源 MCP 与 AgentToolsBFF | 本章 GitLabClient.search_wiki_blobs 是该章 knowledge-sources MCP 的检索底座 |
| AI 角色注册表与模型矩阵 | 本章 AI 协编经该章 ai_registry 的 wiki_ai_rewrite/extend role 选 provider |
| acdm-backend 控制面架构 | 本章三个 router 在该章统一挂载,共享 auth 依赖与 ApiResponse 形状 |
| 项目管理与资源授权 | 项目级 KB 的 rootPath 来自该章 project.gitlab_wiki_path 字段 |
| 环境变量凭据与降级开关 | 本章 GITLAB_WIKI_PAT 等凭据在该章统一说明 |