docs: add design document (Phase 2)
- 架构设计:MCP Server + Wiki Skills 混合架构 - 模块设计:4 层架构(Protocol/Tool/Service/Storage) - 数据模型:WikiPage 和 WikiIndex - 接口设计:MCP Tool 接口规范 - 核心算法:增量更新、查询、cross_linker - 配置设计:YAML 配置 + 环境变量 - 错误处理:5 类错误处理策略 - 性能优化:FTS5、缓存、WAL 并发优化 - Wiki Skills 设计:11 个 Skills 优先级划分 - 测试设计:单元/集成/E2E 三层测试 - 部署设计:开发 + 生产环境配置 Phase 2: 设计评审 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
## 状态
|
||||
|
||||
🚧 开发中 - Phase 1: 需求评审
|
||||
🚧 开发中 - Phase 2: 设计评审
|
||||
|
||||
## 项目概述
|
||||
|
||||
@@ -52,8 +52,8 @@ python -m mcp_server.main
|
||||
|
||||
## 文档
|
||||
|
||||
- [需求文档](docs/01-requirements.md)
|
||||
- [设计文档](docs/02-design.md) - 待完成
|
||||
- [需求文档 v1.1](docs/01-requirements.md) ✅ 评审通过
|
||||
- [设计文档 v1.0](docs/02-design.md) 🚧 评审中
|
||||
|
||||
## 开发流程
|
||||
|
||||
|
||||
@@ -0,0 +1,552 @@
|
||||
# sanguo_llmwiki 设计文档
|
||||
|
||||
## 1. 架构设计
|
||||
|
||||
### 1.1 系统架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Claude Code │
|
||||
│ │
|
||||
│ ┌─────────────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ Wiki Skills (11) │ │ Wiki MCP Server │ │
|
||||
│ │ (一次性操作) │ │ (Python 服务) │ │
|
||||
│ └─────────────────────┘ └─────────────────────────┘ │
|
||||
│ │ │
|
||||
└───────────────────────────────────────┼─────────────────────┘
|
||||
│ MCP 协议
|
||||
│ (stdio/SSE)
|
||||
┌───────────────────────────────────────▼─────────────────────┐
|
||||
│ Wiki MCP Server │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ MCP Protocol Layer │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────────────────▼───────────────────────────────┐ │
|
||||
│ │ Tool Layer │ │
|
||||
│ │ query │ status │ lint │ linker │ taxonomy │ synthesize│ │
|
||||
│ └───────────────────────┬───────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────────────────▼───────────────────────────────┐ │
|
||||
│ │ Service Layer │ │
|
||||
│ │ indexer │ cache │ graph │ parser │ │
|
||||
│ └───────────────────────┬───────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────────────────▼───────────────────────────────┐ │
|
||||
│ │ Storage Layer │ │
|
||||
│ │ SQLite (WAL 模式) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└───────────────────────────────────────┼─────────────────────┘
|
||||
│
|
||||
┌───────────────────────────────────────▼─────────────────────┐
|
||||
│ Obsidian Wiki Vault │
|
||||
│ /Volumes/KnowledgeBase/wiki-vault │
|
||||
│ │
|
||||
│ practices/ │ concepts/ │ entities/ │ projects/ │ skills/ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.2 部署架构
|
||||
|
||||
**开发阶段:**
|
||||
```
|
||||
Claude Code --stdio--> Wiki MCP Server (手动启动)
|
||||
```
|
||||
|
||||
**生产阶段:**
|
||||
```
|
||||
PM2 --> Wiki MCP Server (SSE 模式)
|
||||
│
|
||||
└──> Claude Code --SSE--> Wiki MCP Server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 模块设计
|
||||
|
||||
### 2.1 MCP Protocol Layer
|
||||
|
||||
**职责:**
|
||||
- MCP 协议解析和封装
|
||||
- 工具注册和路由
|
||||
- 错误处理和日志
|
||||
|
||||
**接口:**
|
||||
```python
|
||||
class MCPServer:
|
||||
def register_tool(self, name: str, handler: Callable)
|
||||
def handle_call(self, name: str, params: dict) -> dict
|
||||
def log(self, level: str, message: str)
|
||||
```
|
||||
|
||||
### 2.2 Tool Layer
|
||||
|
||||
**工具列表:**
|
||||
|
||||
| 工具 | 输入 | 输出 | 实现模块 |
|
||||
|------|------|------|----------|
|
||||
| wiki_query | query, tags, limit | results, citations | QueryTool |
|
||||
| memory_bridge | tool_name, date_range | entries | MemoryTool |
|
||||
| wiki_status | - | stats, pending | StatusTool |
|
||||
| wiki_lint | path, level | issues, fixes | LintTool |
|
||||
| cross_linker | path, dry_run | missing_links | LinkerTool |
|
||||
| tag_taxonomy | path, enforce | conflicts | TaxonomyTool |
|
||||
| wiki_synthesize | concepts, threshold | synthesis | SynthesizeTool |
|
||||
| daily_update | - | updated, new | DailyTool |
|
||||
|
||||
### 2.3 Service Layer
|
||||
|
||||
**IndexerService(索引服务):**
|
||||
```python
|
||||
class IndexerService:
|
||||
def index_page(self, path: str) -> None
|
||||
def index_batch(self, paths: List[str]) -> None
|
||||
def rebuild_index(self) -> None
|
||||
def get_dirty_pages(self) -> List[str] # 增量更新
|
||||
```
|
||||
|
||||
**CacheService(缓存服务):**
|
||||
```python
|
||||
class CacheService:
|
||||
def get(self, key: str) -> Optional[Any]
|
||||
def set(self, key: str, value: Any, ttl: int)
|
||||
def invalidate(self, pattern: str)
|
||||
```
|
||||
|
||||
**GraphService(图服务):**
|
||||
```python
|
||||
class GraphService:
|
||||
def get_links(self, path: str) -> Set[str]
|
||||
def get_backlinks(self, path: str) -> Set[str]
|
||||
def find_orphans(self) -> Set[str]
|
||||
def find_missing_links(self) -> List[Tuple[str, str]]
|
||||
```
|
||||
|
||||
**ParserService(解析服务):**
|
||||
```python
|
||||
class ParserService:
|
||||
def parse_frontmatter(self, content: str) -> dict
|
||||
def extract_links(self, content: str) -> List[str]
|
||||
def validate_page(self, path: str) -> List[str] # 返回问题列表
|
||||
```
|
||||
|
||||
### 2.4 Storage Layer
|
||||
|
||||
**数据库连接(带并发保护):**
|
||||
```python
|
||||
class Database:
|
||||
def __init__(self, path: str):
|
||||
self.conn = sqlite3.connect(path, check_same_thread=False)
|
||||
self.conn.execute("PRAGMA journal_mode=WAL")
|
||||
self.conn.execute("PRAGMA busy_timeout=10000") # 10s
|
||||
self.lock = asyncio.Lock()
|
||||
|
||||
async def execute(self, sql: str, params: tuple):
|
||||
async with self.lock:
|
||||
return self.conn.execute(sql, params)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据模型
|
||||
|
||||
### 3.1 WikiPage(页面模型)
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class WikiPage:
|
||||
path: str # wiki 相对路径
|
||||
title: str # 标题
|
||||
category: str # 分类(practices/concepts/...)
|
||||
tags: List[str] # 标签列表
|
||||
summary: str # 摘要(≤200 字符)
|
||||
content_hash: str # MD5 哈希
|
||||
lifecycle: str # draft/verified/archived/disputed
|
||||
sources: List[str] # 来源页面
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
indexed_at: datetime
|
||||
|
||||
def is_stale(self, days: int = 90) -> bool:
|
||||
return (datetime.now() - self.updated_at).days > days
|
||||
```
|
||||
|
||||
### 3.2 WikiIndex(索引模型)
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class WikiIndex:
|
||||
pages: Dict[str, WikiPage]
|
||||
links: Dict[str, Set[str]] # source -> {targets}
|
||||
backlinks: Dict[str, Set[str]] # target -> {sources}
|
||||
tags: Dict[str, Set[str]] # tag -> {pages}
|
||||
orphans: Set[str] # 无反向链接的页面
|
||||
stats: IndexStats
|
||||
|
||||
@dataclass
|
||||
class IndexStats:
|
||||
total_pages: int
|
||||
total_links: int
|
||||
total_tags: int
|
||||
last_indexed: datetime
|
||||
dirty_pages: int
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口设计
|
||||
|
||||
### 4.1 MCP Tool 接口
|
||||
|
||||
**wiki_query:**
|
||||
```json
|
||||
{
|
||||
"name": "wiki_query",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string"},
|
||||
"tags": {"type": "array", "items": {"type": "string"}},
|
||||
"limit": {"type": "integer", "default": 10}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**wiki_status:**
|
||||
```json
|
||||
{
|
||||
"name": "wiki_status",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 内部服务接口
|
||||
|
||||
```python
|
||||
# IndexerService
|
||||
async def index_page(path: str) -> IndexResult
|
||||
async def get_dirty_pages() -> List[str]
|
||||
|
||||
# QueryService
|
||||
async def search(query: str, limit: int) -> List[WikiPage]
|
||||
async def search_by_tags(tags: List[str]) -> List[WikiPage]
|
||||
|
||||
# GraphService
|
||||
async def get_links(path: str) -> Set[str]
|
||||
async def find_orphans() -> Set[str]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 核心算法
|
||||
|
||||
### 5.1 增量更新算法
|
||||
|
||||
```python
|
||||
async def incremental_update():
|
||||
# 1. 获取所有 wiki 页面
|
||||
all_pages = scan_wiki_vault()
|
||||
|
||||
# 2. 检查每个页面的哈希
|
||||
for page in all_pages:
|
||||
current_hash = md5(page.content)
|
||||
stored = db.get_page_hash(page.path)
|
||||
|
||||
if stored != current_hash:
|
||||
# 3. 只重索引变化的页面
|
||||
await index_page(page)
|
||||
|
||||
# 4. 处理删除的页面
|
||||
indexed_paths = db.get_all_indexed_paths()
|
||||
for path in indexed_paths:
|
||||
if path not in all_pages:
|
||||
db.delete_page(path)
|
||||
```
|
||||
|
||||
### 5.2 查询算法
|
||||
|
||||
```python
|
||||
async def wiki_query(query: str, tags: List[str], limit: int):
|
||||
# 1. FTS5 全文搜索
|
||||
if query:
|
||||
results = fts_search(query, limit)
|
||||
|
||||
# 2. 标签过滤
|
||||
if tags:
|
||||
results = filter_by_tags(results, tags)
|
||||
|
||||
# 3. 按相关性排序
|
||||
sorted_results = rank_by_relevance(results, query)
|
||||
|
||||
# 4. 返回带 [[wikilink]] 的结果
|
||||
return format_results(sorted_results[:limit])
|
||||
```
|
||||
|
||||
### 5.3 cross_linker 算法
|
||||
|
||||
```python
|
||||
async def find_missing_links():
|
||||
# 1. 获取所有页面内容
|
||||
pages = load_all_pages()
|
||||
|
||||
# 2. 提取所有 [[wikilinks]]
|
||||
all_links = extract_all_links(pages)
|
||||
|
||||
# 3. 找出缺失的链接
|
||||
missing = []
|
||||
for source, targets in all_links.items():
|
||||
for target in targets:
|
||||
if not page_exists(target):
|
||||
missing.append((source, target))
|
||||
|
||||
return missing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 配置设计
|
||||
|
||||
### 6.1 配置文件结构
|
||||
|
||||
```yaml
|
||||
# ~/.sanguo-llmwiki/config.yaml
|
||||
wiki:
|
||||
vault_path: "/Volumes/KnowledgeBase/wiki-vault"
|
||||
index_path: "~/.sanguo-llmwiki/index.db"
|
||||
max_page_size_kb: 500
|
||||
|
||||
mcp:
|
||||
mode: "stdio" # stdio 或 SSE
|
||||
host: "localhost"
|
||||
port: 8080
|
||||
|
||||
performance:
|
||||
query_timeout_ms: 5000
|
||||
cache_ttl_seconds: 3600
|
||||
fts_cache_size_mb: 100
|
||||
|
||||
logging:
|
||||
level: "INFO"
|
||||
file: "~/.sanguo-llmwiki/wiki-mcp.log"
|
||||
```
|
||||
|
||||
### 6.2 环境变量
|
||||
|
||||
```bash
|
||||
# 环境变量优先级高于配置文件
|
||||
WIKI_VAULT_PATH=/custom/path
|
||||
WIKI_INDEX_PATH=/custom/index.db
|
||||
MCP_MODE=sse
|
||||
LOG_LEVEL=DEBUG
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误处理
|
||||
|
||||
### 7.1 错误分类
|
||||
|
||||
| 错误类型 | 处理方式 |
|
||||
|----------|----------|
|
||||
| Wiki 路径不存在 | 启动失败,返回友好错误 |
|
||||
| 索引文件损坏 | 自动重建 + WARN 日志 |
|
||||
| SQLite 写入失败 | 回滚事务 + 重试 1 次 |
|
||||
| 页面解析失败 | 记录日志 + 跳过该页面 |
|
||||
| MCP 协议错误 | 返回标准错误格式 |
|
||||
| 查询超时 | 返回部分结果 + WARN |
|
||||
|
||||
### 7.2 错误响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "INDEX_CORRUPTED",
|
||||
"message": "索引文件损坏,正在自动重建",
|
||||
"details": {"rebuilding": true}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 安全考虑
|
||||
|
||||
虽然是本地系统,但仍需考虑:
|
||||
|
||||
1. **路径安全**:验证路径在 wiki vault 范围内(防止路径遍历)
|
||||
2. **资源限制**:限制查询返回数量、内存使用
|
||||
3. **日志脱敏**:日志中不记录敏感内容
|
||||
|
||||
---
|
||||
|
||||
## 9. 性能优化
|
||||
|
||||
### 9.1 索引优化
|
||||
|
||||
- 使用 FTS5 全文搜索索引
|
||||
- 标签单独建立索引
|
||||
- 定期 VACUUM(每周)
|
||||
|
||||
### 9.2 查询优化
|
||||
|
||||
- 查询结果缓存(TTL 1 小时)
|
||||
- 限制返回数量(默认 10)
|
||||
- 使用 prepared statements
|
||||
|
||||
### 9.3 并发优化
|
||||
|
||||
- SQLite WAL 模式
|
||||
- asyncio.Lock 写入串行化
|
||||
- busy_timeout=10s
|
||||
|
||||
---
|
||||
|
||||
## 10. Wiki Skills 设计
|
||||
|
||||
### 10.1 Skill 模板
|
||||
|
||||
每个 Skill 遵循统一结构:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: wiki-xxx
|
||||
description: >
|
||||
简短描述(1-2 句)
|
||||
触发条件
|
||||
---
|
||||
|
||||
# Wiki XXX
|
||||
|
||||
## 使用场景
|
||||
用户何时触发这个 Skill
|
||||
|
||||
## 操作步骤
|
||||
1. ...
|
||||
2. ...
|
||||
|
||||
## 输出格式
|
||||
...
|
||||
```
|
||||
|
||||
### 10.2 Skills 列表
|
||||
|
||||
**优先级 P0(核心):**
|
||||
1. wiki-setup - 初始化 wiki
|
||||
2. wiki-ingest - 蒸馏文档
|
||||
3. wiki-capture - 保存对话
|
||||
|
||||
**优先级 P1(重要):**
|
||||
4. wiki-rebuild - 重建 wiki
|
||||
5. data-ingest - 录入数据
|
||||
6. ingest-url - 抓取 URL
|
||||
7. wiki-agent - 录入历史
|
||||
8. wiki-export - 导出
|
||||
|
||||
**优先级 P2(可选):**
|
||||
9. wiki-research - 研究
|
||||
10. impl-validator - 验证
|
||||
11. graph-colorize - 着色
|
||||
|
||||
---
|
||||
|
||||
## 11. 测试设计
|
||||
|
||||
### 11.1 单元测试
|
||||
|
||||
覆盖所有 Service 层的核心逻辑:
|
||||
- IndexerService 测试
|
||||
- QueryService 测试
|
||||
- GraphService 测试
|
||||
- ParserService 测试
|
||||
|
||||
### 11.2 集成测试
|
||||
|
||||
- MCP 协议层测试(使用 MCP SDK mock)
|
||||
- SQLite 操作测试
|
||||
- Wiki 解析测试
|
||||
|
||||
### 11.3 E2E 测试
|
||||
|
||||
使用真实 wiki 数据集测试:
|
||||
- 查询场景
|
||||
- 搜索场景
|
||||
- 索引更新场景
|
||||
|
||||
---
|
||||
|
||||
## 12. 部署设计
|
||||
|
||||
### 12.1 开发部署
|
||||
|
||||
```bash
|
||||
# 手动启动
|
||||
cd ~/.openclaw/sanguo_projects/sanguo_llmwiki
|
||||
python -m mcp_server.main
|
||||
```
|
||||
|
||||
### 12.2 生产部署
|
||||
|
||||
```bash
|
||||
# PM2 配置
|
||||
cat > ecosystem.config.cjs << 'EOF'
|
||||
module.exports = {
|
||||
apps: [{
|
||||
name: 'wiki-mcp',
|
||||
script: 'python',
|
||||
args: '-m mcp_server.main',
|
||||
cwd: '/Users/chufeng/.openclaw/sanguo_projects/sanguo_llmwiki',
|
||||
instances: 1,
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: '500M',
|
||||
env: {
|
||||
PYTHONUNBUFFERED: '1'
|
||||
}
|
||||
}]
|
||||
}
|
||||
EOF
|
||||
|
||||
pm2 start ecosystem.config.cjs
|
||||
pm2 save
|
||||
```
|
||||
|
||||
### 12.3 MCP 配置
|
||||
|
||||
**stdio 模式(开发):**
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"wiki": {
|
||||
"command": "python",
|
||||
"args": ["-m", "mcp_server.main"],
|
||||
"cwd": "/Users/chufeng/.openclaw/sanguo_projects/sanguo_llmwiki",
|
||||
"env": {
|
||||
"WIKI_VAULT_PATH": "/Volumes/KnowledgeBase/wiki-vault"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**SSE 模式(生产):**
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"wiki": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8080/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*文档版本:v1.0*
|
||||
*创建时间:2026-06-26*
|
||||
Reference in New Issue
Block a user