Phase 0-2: Schema cleanup, typed relations, event-driven automation

- Phase 0: AGENTS.md cleanup (dedup quotes, renumber sections, merge qmd)
- Phase 1: typed relations (manage-relations.py, graph-search.py, check-staleness.py, detect-conflicts.py)
- Phase 2: frontmatter validator, weekly lint, knowledge promotion, git hooks
- Fix .gitignore to track tools/ and .githooks/
- Fix git remote URL (remove plaintext token)
- New wiki pages: 504 pages, 34 raw sources
This commit is contained in:
hehaiguang1123
2026-07-01 08:05:43 +08:00
parent e544d6e04a
commit a6f05ab2d5
1067 changed files with 522992 additions and 819 deletions
+495
View File
@@ -0,0 +1,495 @@
# 知识库同步可行性分析与实施计划
> **任务目标**:定期将 `D:\TC_UP\home-wiki` 的笔记内容更新到 `D:\Applications\app\kepano-obsidian-main` 仓库
---
## 一、双仓库结构对比
### 1.1 目录结构映射
| 源仓库 (D:\TC_UP\home-wiki) | 目标仓库 (kepano-obsidian-main) | 兼容性 |
|---------------------------|-------------------------------|-------|
| `concepts/` (12 文件) | `wiki/` (464 文件,概念页) | ✅ 高度兼容 |
| `entities/` | `References/` + `wiki/` (实体页) | ✅ 可兼容 |
| `raw/` (48 文件) | `raw/` (480 文件) | ✅ 完全兼容 |
| `sources/` | `raw/` (已有,需合并) | ✅ 需映射 |
| `Daily/` | `Daily/` | ✅ 完全兼容 |
| `Templates/` | `Templates/` | ✅ 需检查冲突 |
| `MyNotes/` | `Notes/` | ✅ 可映射 |
| `scripts/` | `tools/scripts/` | ✅ 可兼容 |
| `04-Tools/` | 无对应(可作为知识卡片) | ⚠️ 需映射 |
| `syntheses/` | `wiki/` (概念/研究报告) | ✅ 可兼容 |
| `reports/` | `wiki/archive/` (归档报告) | ⚠️ 需过滤 |
### 1.2 核心规范对比
| 维度 | home-wiki | kepano-obsidian-main | 兼容性 |
|------|-----------|---------------------|-------|
| **Frontmatter** | `type`, `title`, `created`, `updated` | `categories`, `tags`, `created`, `source`, `type` | ⚠️ 格式不同 |
| **标签体系** | Canonical Tags ( predefined list) | 自由 tags (no whitelist) | ⚠️ 需转换 |
| **关系类型** | Wikilinks + `→`, `⊍` 符号 | 标准 Wikilinks | ⚠️ 需清理 |
| **Claims 规范** | 严格 source 追溯 | `[raw:文件名:行号]` 格式 | ⚠️ 需转换 |
| **文件命名** | 中文优先 | 中英文混合 | ✅ 兼容 |
| **日期格式** | `YYYY-MM-DD` | `YYYY-MM-DD` | ✅ 兼容 |
---
## 二、可行性分析
### 2.1 技术可行性 ✅ **可行**
1. **底层技术栈一致**:两个仓库都使用 Obsidian + Markdown + Git
2. **核心概念相似**
- 都使用 `raw/` 存储原始来源
- 都使用 `Daily/` 存储日记
- 都使用概念/实体分离的组织方式
3. **内容高度互补**
- home-wiki: 专注"高等教育 AI"12 个概念页,48 个 raw 文件
- kepano-obsidian-main: 综合性知识库,464 个 wiki 页面,480 个 raw 文件
### 2.2 内容冲突风险 ⚠️ **中等**
1. **Frontmatter 格式差异**
```yaml
# home-wiki 格式
---
type: concept
title: 涌现
created: 2026-05-20
updated: 2026-05-20
tags: [phenomenon, systems-theory]
sources: [raw/articles/原始资料.html]
---
# kepano-obsidian-main 格式
---
categories:
- "[[LLM Wiki]]"
tags:
- wiki
- concept
created: 2026-06-30
source: "[[{source-file}]]"
type: concept
---
```
2. **关系符号**home-wiki 使用 ``, `` 等符号,目标仓库不使用
3. **Claims 规范**
```yaml
# home-wiki
claims:
- text: "事实陈述"
source: "raw/articles/原始资料.html"
confidence: 0.9
# kepano-obsidian-main
事实陈述[raw:文件名:行号]
```
### 2.3 数据规模 ⚠️ **需筛选**
| 类型 | home-wiki | kepano-obsidian-main | 策略 |
|------|-----------|---------------------|------|
| 概念页 | 12 | 177+ | ✅ 全部同步 |
| 实体页 | ~5 | 120+ | ✅ 全部同步 |
| 原始来源 | 48 | 480 | ⚠️ 需去重 |
| Daily | 未知 | 未知 | ✅ 合并 |
| 模板 | 未知 | 多个 | ⚠️ 需冲突检查 |
| 工具文档 | 7 | 26+ | ✅ 全部同步 |
---
## 三、实施计划
### 3.1 第一阶段:准备工作(Week 1)
#### 3.1.1 冲突检查与映射表创建
**任务**:创建 `tools/sync-mapping.json` 映射表
```json
{
"directoryMapping": {
"concepts/": "wiki/",
"entities/": ["wiki/", "References/"],
"raw/": "raw/",
"sources/": "raw/",
"Daily/": "Daily/",
"Templates/": "Templates/",
"MyNotes/": "Notes/",
"scripts/": "tools/scripts/",
"04-Tools/": "wiki/", // 作为工具知识卡片
"syntheses/": "wiki/",
"reports/": "wiki/archive/"
},
"tagMapping": {
"phenomenon": "concept/phenomenon",
"theory": "concept/theory",
"technology": "concept/technology",
"person": "people",
"organization": "entity/organization",
"AI-education": "ai/education",
"higher-ed": "education/higher-ed"
},
"conflictFiles": ["AGENTS.md", "SCHEMA.md", "Templates/*.md"]
}
```
**输出**
- `tools/sync-mapping.json` — 目录和标签映射表
- `tools/sync-conflicts.md` — 冲突文件清单
#### 3.1.2 Frontmatter 转换器
**任务**:创建 `tools/sync/convert-frontmatter.py`
```python
# 转换规则
# 1. type: concept → categories: "[[LLM Wiki]]", type: concept
# 2. tags: [a, b] → tags: [wiki, a, b] (添加 wiki 标签)
# 3. sources: [...] → source: "[[{primary-source}]]"
# 4. updated: 删除(目标仓库不使用)
# 5. claims → 转换为 [raw:文件名:行号] 格式
# 6. 移除 →, ⊍ 等关系符号,替换为标准 Wikilinks
```
**输出**
- `tools/sync/convert-frontmatter.py` — Frontmatter 转换脚本
- `tools/sync/convert-frontmatter-test.md` — 转换测试样例
### 3.2 第二阶段:核心内容同步(Week 2)
#### 3.2.1 概念页同步
**任务**:同步 `concepts/` → `wiki/`
**步骤**
1. 读取 `D:\TC_UP\home-wiki\concepts\*.md`
2. 转换 frontmatter(使用转换器)
3. 转换关系符号(``, `` → 标准 Wikilinks
4. 转换 claims`claims[].text[raw:source]` → `[raw:文件名:行号]`
5. 写入 `wiki/`,避免冲突(添加后缀 `_from_homewiki`
6. 更新 `wiki/index.md`(添加到核心概念章节)
**检查点**
- [ ] 12 个概念页全部转换
- [ ] Frontmatter 格式符合目标仓库规范
- [ ] Wikilinks 无断链
- [ ] 索引更新完成
#### 3.2.2 实体页同步
**任务**:同步 `entities/` → `wiki/` + `References/`
**步骤**
1. 读取 `D:\TC_UP\home-wiki\entities\*.md`
2. 根据实体类型分流:
- `subtype: person` → `wiki/` + `References/People.md` 添加条目
- `subtype: organization` → `References/` + `wiki/`
- `subtype: tool` → `wiki/`(工具页)
3. 转换 frontmatter(使用转换器)
4. 检查与现有实体页冲突(如郭朝晖是否已存在)
5. 写入目标位置,更新索引
**检查点**
- [ ] ~5 个实体页全部转换
- [ ] 实体类型分类正确
- [ ] 无重复实体
- [ ] 索引更新完成
### 3.3 第三阶段:原始来源同步(Week 3)
#### 3.3.1 raw/ 去重与合并
**任务**:合并 `raw/` + `sources/` → `raw/`
**步骤**
1. 扫描 `D:\TC_UP\home-wiki\raw/` + `sources/`
2. 计算文件哈希值,去重(避免重复文件)
3. 对比目标仓库 `raw/`,识别新文件
4. 创建新文件到 `raw/`(按类型分类:papers/, articles/, transcripts/
5. 更新 Wiki 页面的 source 字段
**检查点**
- [ ] 去重完成
- [ ] 新文件识别正确
- [ ] Source 字段更新完成
#### 3.3.2 claims 追溯转换
**任务**:转换 claims 为 `[raw:文件名:行号]` 格式
**步骤**
1. 扫描所有 Wiki 页面中的 `claims` 字段
2. 提取 `claims[].source` 路径
3. 查找对应的 raw 文件行号
4. 转换格式:`text[raw:文件名:行号]`
5. 删除原 `claims` 字段
**检查点**
- [ ] 所有 claims 转换完成
- [ ] 行号正确
- [ ] 溯源格式符合目标仓库规范
### 3.4 第四阶段:其他内容同步(Week 4)
#### 3.4.1 Daily/ 合并
**任务**:合并 `Daily/` → `Daily/`
**步骤**
1. 扫描 `D:\TC_UP\home-wiki\Daily\*.md`
2. 按日期去重(同名文件需要合并内容)
3. 写入目标 `Daily/`
**检查点**
- [ ] 日记合并完成
- [ ] 无内容丢失
#### 3.4.2 Templates/ 冲突处理
**任务**:检查模板文件冲突
**步骤**
1. 对比两个仓库的模板文件列表
2. 识别同名文件(如 `Concept Template.md`
3. 决策:保留目标仓库模板,或融合两个模板
**检查点**
- [ ] 冲突识别完成
- [ ] 融合方案确定
#### 3.4.3 工具文档同步
**任务**:同步 `04-Tools/` + `scripts/` → `tools/`
**步骤**
1. 读取 `D:\TC_UP\home-wiki\04-Tools\*.md` + `scripts/`
2. 写入 `tools/scripts/`
3. 更新 `wiki/index.md` 工具章节
**检查点**
- [ ] 工具文档全部同步
- [ ] 索引更新完成
### 3.5 第五阶段:验证与优化(Week 5)
#### 3.5.1 Lint 检查
**任务**:运行 Wiki Lint 检查
**检查项**
- [ ] Frontmatter 一致性
- [ ] Wikilink 连通性
- [ ] 标签格式
- [ ] 日期格式
- [ ] 溯源格式
**修复**
- 运行 `tools/scripts/wiki-lint-orphan.ps1`
- 运行 `tools/scripts/wiki-lint-broken-v2.ps1`
- 运行 `tools/scripts/wiki-lint-fix-type.ps1`
#### 3.5.2 交叉引用检查
**任务**:检查新 Wiki 页面与现有页面的交叉引用
**步骤**
1. 扫描新 Wiki 页面的所有 Wikilinks
2. 检查目标页面是否存在
3. 如不存在,创建占位页面或删除链接
4. 更新相关页面的反向链接
**检查点**
- [ ] 断链修复完成
- [ ] 反向链接添加完成
#### 3.5.3 日志更新
**任务**:更新 `wiki/log.md`
**步骤**
1. 记录同步操作(日期、操作类型、涉及页面)
2. 记录转换规则(frontmatter、claims、标签)
3. 记录冲突处理(文件、解决方案)
**输出**
- `wiki/log.md` 追加同步日志条目
---
## 四、定期同步机制
### 4.1 同步频率
| 频率 | 内容 | 负担 |
|------|------|------|
| **每日** | Daily/ 日记 | 低 |
| **每周** | concepts/, entities/, raw/ 新增内容 | 中 |
| **每月** | 全量检查、去重、Lint | 高 |
### 4.2 自动化脚本
**方案 1PowerShell 脚本**
```powershell
# tools/sync/daily-sync.ps1
# 用途:每日同步 Daily/ 日记
$sourcePath = "D:\TC_UP\home-wiki\Daily"
$targetPath = "D:\Applications\app\kepano-obsidian-main\Daily"
Get-ChildItem $sourcePath -Filter "*.md" | ForEach-Object {
$targetFile = Join-Path $targetPath $_.Name
if (-not (Test-Path $targetFile)) {
Copy-Item $_.FullName $targetFile
Write-Host "Copied $($_.Name)"
} else {
Write-Host "Skipped $($_.Name) (exists)"
}
}
```
**方案 2Python 脚本**
```python
# tools/sync/weekly-sync.py
# 用途:每周同步 concepts/, entities/, raw/
import os
import shutil
from pathlib import Path
MAPPINGS = {
"concepts": "wiki",
"entities": ["wiki", "References"],
"raw": "raw",
}
def sync_directory(source, target, converter=None):
"""同步目录,可选转换器"""
for file in Path(source).glob("*.md"):
target_file = Path(target) / file.name
if not target_file.exists():
if converter:
content = converter(file.read_text())
target_file.write_text(content)
else:
shutil.copy(file, target_file)
print(f"Synced {file.name}")
```
### 4.3 冲突解决策略
| 冲突类型 | 策略 | 优先级 |
|---------|------|--------|
| 文件同名 | 添加后缀 `_from_homewiki` | 目标仓库 |
| Frontmatter 格式 | 转换为目标仓库格式 | 目标仓库 |
| 标签冲突 | 保留目标仓库标签,融合源仓库标签 | 目标仓库 |
| Wikilink 断链 | 创建占位页面或删除链接 | 目标仓库 |
| claims 格式 | 转换为 `[raw:文件名:行号]` | 目标仓库 |
---
## 五、风险与对策
### 5.1 风险识别
| 风险 | 概率 | 影响 | 对策 |
|------|------|------|------|
| **格式冲突** | 高 | 中 | 提前创建转换器,自动化处理 |
| **内容重复** | 中 | 低 | 去重脚本,哈希值比对 |
| **断链风险** | 中 | 高 | Lint 检查,反向链接补充 |
| **数据丢失** | 低 | 高 | Git 备份,增量同步 |
| **同步失败** | 中 | 中 | 错误日志,回滚机制 |
### 5.2 回滚机制
```powershell
# Git 回滚命令
git log --oneline -10 # 查看最近 10 次提交
git reset --hard HEAD~1 # 回滚到上一次提交
git reflog # 查看所有提交历史
```
---
## 六、成功标准
### 6.1 定量指标
| 指标 | 目标值 | 测量方法 |
|------|--------|---------|
| 概念页同步率 | 100% (12/12) | `Get-ChildItem wiki\*_from_homewiki.md | Measure-Object` |
| 实体页同步率 | 100% (~5/~5) | 人工检查 + grep |
| raw/ 去重率 | 100% | 哈希值比对 |
| Lint 错误率 | 0% | 运行 lint 脚本 |
| Wikilink 断链数 | 0 | 运行断链检测 |
### 6.2 定性标准
- [ ] 所有同步内容符合目标仓库 Frontmatter 规范
- [ ] 所有 claims 正确转换为 `[raw:文件名:行号]` 格式
- [ ] 所有 Wikilink 可正常跳转
- [ ] 索引页面更新完成,包含新内容
- [ ] 日志记录完整,可追溯操作历史
---
## 七、时间表
| 阶段 | 任务 | 时间 | 产出 |
|------|------|------|------|
| Week 1 | 准备工作(映射表、转换器) | 5 天 | `sync-mapping.json`, `convert-frontmatter.py` |
| Week 2 | 核心内容同步(概念、实体) | 5 天 | 12 概念页 + ~5 实体页 |
| Week 3 | 原始来源同步(raw/ 去重、claims 转换) | 5 天 | 48 raw 文件,claims 转换 |
| Week 4 | 其他内容同步(Daily, Templates, Tools | 5 天 | Daily, Templates, Tools 同步完成 |
| Week 5 | 验证与优化(Lint, 交叉引用, 日志) | 5 天 | Lint 通过,索引更新,日志追加 |
**总计**:5 周(35 个工作日)
---
## 八、后续维护
### 8.1 日常维护任务
- **每日**:运行 `tools/sync/daily-sync.ps1`(同步 Daily/
- **每周**:运行 `tools/sync/weekly-sync.py`(同步新增内容)
- **每月**:运行 Lint 检查 + 去重 + 索引更新
### 8.2 定期审查
- **每季度**:审查同步脚本的有效性,更新映射表
- **每半年**:评估同步策略,优化自动化程度
- **每年**:评估仓库结构,调整同步方向(单向→双向)
---
## 九、总结
### 9.1 可行性结论 ✅ **高度可行**
1. **技术兼容**:两个仓库底层技术栈一致(Obsidian + Markdown + Git
2. **内容互补**home-wiki 专注"高等教育 AI",与目标仓库高度互补
3. **风险可控**:通过转换器、Lint 检查、回滚机制,风险可控
4. **收益明确**:统一知识库,减少维护负担,提高知识积累效率
### 9.2 关键成功因素
1. **提前规划**:映射表、转换器、冲突处理策略
2. **自动化脚本**:减少人工操作,降低错误率
3. **增量同步**:避免一次性大规模迁移,降低风险
4. **验证机制**:Lint 检查、交叉引用、日志记录
### 9.3 下一步行动
1. **立即行动**:创建 `tools/sync-mapping.json` 映射表
2. **本周任务**:开发 `convert-frontmatter.py` 转换器
3. **下周任务**:开始核心内容同步(概念页)
---
*本方案由 opencode 生成,日期:2026-06-30*
+363
View File
@@ -0,0 +1,363 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
home-wiki → kepano-obsidian-main 同步转换脚本
用途:将 home-wiki 的笔记内容转换为目标仓库格式并同步。
功能:
1. Frontmatter 格式转换
2. OpenClaw 标记清理
3. Wikilink 路径保持
4. 批量处理目录
用法:
python sync_homewiki.py concepts # 同步概念页
python sync_homewiki.py entities # 同步实体页
python sync_sync_homewiki.py all # 同步全部
"""
import os
import re
import sys
import shutil
from pathlib import Path
from datetime import datetime
# 路径配置
SOURCE_ROOT = Path(r"D:\TC_UP\home-wiki")
TARGET_ROOT = Path(r"D:\Applications\app\kepano-obsidian-main")
# 目录映射
DIR_MAPPING = {
"concepts": "wiki",
"entities": "wiki",
"syntheses": "wiki",
}
# 标签映射(home-wiki tag → 目标仓库 tag
TAG_MAPPING = {
"complexity": "concept/complexity",
"systems-theory": "concept/systems-theory",
"consciousness": "concept/consciousness",
"physics": "concept/physics",
"phenomenon": "concept/phenomenon",
"theory": "concept/theory",
"technology": "concept/technology",
"practice": "concept/practice",
"person": "people",
"innovation-management": "concept/innovation",
"methodology": "concept/methodology",
"higher-ed": "education/higher-ed",
"AI-education": "ai/education",
"LLM": "llm",
"agent": "agent",
"RAG": "rag",
"knowledge-graph": "knowledge-graph",
}
def parse_frontmatter(content):
"""解析 markdown 文件的 frontmatter,返回 (yaml_text, body)"""
match = re.match(r'^---\n(.*?)\n---\n(.*)', content, re.DOTALL)
if not match:
return None, content
return match.group(1), match.group(2)
def convert_frontmatter(yaml_text, source_type="concept"):
"""将 home-wiki frontmatter 转换为目标仓库格式"""
lines = yaml_text.split('\n')
converted = []
# 提取字段
tags = []
sources = []
created = None
page_type = source_type
subtype = None
title = None
aliases = []
i = 0
while i < len(lines):
line = lines[i]
# type 字段
m = re.match(r'^type:\s*(.+)', line)
if m:
page_type = m.group(1).strip()
i += 1
continue
# subtype 字段
m = re.match(r'^subtype:\s*(.+)', line)
if m:
subtype = m.group(1).strip()
i += 1
continue
# title 字段
m = re.match(r'^title:\s*(.+)', line)
if m:
title = m.group(1).strip().strip('"').strip("'")
i += 1
continue
# created 字段
m = re.match(r'^created:\s*(.+)', line)
if m:
created = m.group(1).strip()
i += 1
continue
# tags 字段(行内列表格式)
m = re.match(r'^tags:\s*\[(.*)\]', line)
if m:
tag_str = m.group(1)
tags = [t.strip().strip('"').strip("'") for t in tag_str.split(',') if t.strip()]
i += 1
continue
# tags 字段(YAML 列表格式)
m = re.match(r'^tags:\s*$', line)
if m:
i += 1
while i < len(lines) and lines[i].strip().startswith('-'):
tag = lines[i].strip().lstrip('-').strip()
tags.append(tag)
i += 1
continue
# sources 字段(行内列表格式)
m = re.match(r'^sources:\s*\[(.*)\]', line)
if m:
src_str = m.group(1)
sources = [s.strip().strip('"').strip("'") for s in src_str.split(',') if s.strip()]
i += 1
continue
# sources 字段(YAML 列表格式)
m = re.match(r'^sources:\s*$', line)
if m:
i += 1
while i < len(lines) and lines[i].strip().startswith('-'):
src = lines[i].strip().lstrip('-').strip().strip('"').strip("'")
sources.append(src)
i += 1
continue
# 跳过不需要的字段
if re.match(r'^(updated|confidence|status|description|name|nameEn|born|field):\s*', line):
i += 1
continue
# claims 字段(跳过多行)
if re.match(r'^claims:\s*', line):
i += 1
while i < len(lines) and (lines[i].startswith(' ') or lines[i].startswith(' -')):
i += 1
continue
i += 1
# 构建新的 frontmatter
if not created:
created = datetime.now().strftime('%Y-%m-%d')
new_lines = []
new_lines.append('---')
new_lines.append('categories:')
new_lines.append(' - "[[LLM Wiki]]"')
# tags
new_lines.append('tags:')
new_lines.append(' - wiki')
if page_type == 'entity':
new_lines.append(' - people')
else:
new_lines.append(' - concept')
if subtype:
mapped = TAG_MAPPING.get(subtype, subtype)
new_lines.append(f' - {mapped}')
for tag in tags:
mapped = TAG_MAPPING.get(tag, tag)
if mapped not in ['wiki', 'concept', 'people'] and mapped not in [l.strip().lstrip('-').strip() for l in new_lines[4:]]:
new_lines.append(f' - {mapped}')
new_lines.append(f'created: {created}')
# source 字段
if sources:
src = sources[0]
# 提取文件名(去掉路径)
src_filename = Path(src).name
if src.startswith('http'):
new_lines.append(f'source: "{src}"')
else:
src_name = Path(src_filename).stem
new_lines.append(f'source: "[[{src_name}]]"')
new_lines.append(f'type: {page_type}')
if title and title != page_type:
new_lines.append('aliases:')
new_lines.append(f' - {title}')
new_lines.append('---')
return '\n'.join(new_lines)
def clean_body(body):
"""清理正文中的 OpenClaw 标记和无用章节"""
# 移除 openclaw 注释块(单行)
body = re.sub(r'<!-- openclaw:[^>]*-->', '', body)
# 移除 Related 章节(openclaw 生成的)
body = re.sub(r'## Related\s*\n<!-- openclaw:wiki:related:start -->.*?<!-- openclaw:wiki:related:end -->',
'', body, flags=re.DOTALL)
# 移除 Notes 章节(openclaw 空标记)
body = re.sub(r'## Notes\s*\n<!-- openclaw:human:start -->\s*<!-- openclaw:human:end -->\s*',
'', body)
# 移除 Summary 的 openclaw 标记(保留内容)
body = re.sub(r'<!-- openclaw:wiki:generated:start -->\s*', '', body)
body = re.sub(r'\s*<!-- openclaw:wiki:generated:end -->', '', body)
# 清理多余空行
body = re.sub(r'\n{4,}', '\n\n\n', body)
# 移除末尾的 "最后更新" 行(home-wiki 特有)
body = re.sub(r'\n\*最后更新[:][^*]*\*\s*$', '', body)
return body.strip() + '\n'
def convert_file(source_path, target_dir, source_type="concept"):
"""转换单个文件"""
source_path = Path(source_path)
target_dir = Path(target_dir)
# 跳过 index.md
if source_path.name == 'index.md':
return False, f"跳过 index.md"
# 读取源文件
content = source_path.read_text(encoding='utf-8')
# 解析 frontmatter
yaml_text, body = parse_frontmatter(content)
if yaml_text is None:
return False, f"无 frontmatter,跳过"
# 转换 frontmatter
new_yaml = convert_frontmatter(yaml_text, source_type)
# 清理正文
cleaned_body = clean_body(body)
# 组合
new_content = new_yaml + '\n\n' + cleaned_body
# 写入目标
target_path = target_dir / source_path.name
# 冲突检查
if target_path.exists():
return False, f"目标文件已存在: {target_path.name}"
target_path.write_text(new_content, encoding='utf-8')
return True, f"已同步: {source_path.name}"
def sync_directory(source_subdir, target_subdir, source_type="concept"):
"""同步整个目录"""
source_dir = SOURCE_ROOT / source_subdir
target_dir = TARGET_ROOT / target_subdir
if not source_dir.exists():
print(f"源目录不存在: {source_dir}")
return
print(f"\n{'='*60}")
print(f"同步: {source_subdir}{target_subdir}")
print(f"{'='*60}")
success = 0
skipped = 0
failed = 0
for md_file in sorted(source_dir.glob("*.md")):
ok, msg = convert_file(md_file, target_dir, source_type)
if ok:
print(f"{msg}")
success += 1
else:
print(f" ⏭️ {msg}")
skipped += 1
print(f"\n汇总: {success} 同步, {skipped} 跳过")
return success
def sync_raw_articles():
"""同步 raw/articles/ 的 HTML 文件"""
source_dir = SOURCE_ROOT / "raw" / "articles"
target_dir = TARGET_ROOT / "raw" / "homewiki-articles"
if not source_dir.exists():
print(f"源目录不存在: {source_dir}")
return 0
target_dir.mkdir(parents=True, exist_ok=True)
print(f"\n{'='*60}")
print(f"同步: raw/articles/ → raw/homewiki-articles/")
print(f"{'='*60}")
count = 0
for file in source_dir.iterdir():
if file.is_file():
target_file = target_dir / file.name
if not target_file.exists():
shutil.copy2(file, target_file)
print(f" ✅ 已复制: {file.name}")
count += 1
else:
print(f" ⏭️ 已存在: {file.name}")
print(f"\n汇总: {count} 文件复制")
return count
def main():
if len(sys.argv) < 2:
print("用法: python sync_homewiki.py [concepts|entities|syntheses|raw|all]")
sys.exit(1)
task = sys.argv[1]
total = 0
if task in ('concepts', 'all'):
total += sync_directory("concepts", "wiki", "concept")
if task in ('entities', 'all'):
total += sync_directory("entities", "wiki", "entity")
if task in ('syntheses', 'all'):
total += sync_directory("syntheses", "wiki", "synthesis")
if task in ('raw', 'all'):
total += sync_raw_articles()
print(f"\n{'='*60}")
print(f"全部完成!共同步 {total} 个文件")
print(f"{'='*60}")
if __name__ == '__main__':
main()