dy-dashboard/lint_knowledge.py
2026-10-28 11:14:17 +08:00

225 lines
8.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""知识库自动检查脚本(Lint)
自动修复:
- 索引一致性:缺失的文章补条目,失效的标记 [MISSING]
- 内部链接:断链修复(同一文件名存在 → 自动修正路径)
- 原始资料引用验证
报告(不自动修):
- 孤立页面
- 缺少交叉引用的相关文章
"""
import os, re, json
from datetime import datetime
VAULT = "/home/gxd/蝶雅供应链"
WIKI = os.path.join(VAULT, "知识库")
RAW = os.path.join(VAULT, "原始资料")
INDEX_FILE = os.path.join(WIKI, "索引.md")
LOG_FILE = os.path.join(WIKI, "操作日志.md")
issues = []
auto_fixed = 0
def log(msg):
print(f" {msg}")
def get_md_files(directory):
"""获取目录下所有 md 文件(递归)"""
files = {}
for root, dirs, fnames in os.walk(directory):
for f in fnames:
if f.endswith('.md') and f not in ('索引.md', '操作日志.md'):
rel = os.path.relpath(os.path.join(root, f), directory)
files[f] = rel
return files
def check_index_consistency():
"""检查索引一致性"""
global auto_fixed
if not os.path.exists(INDEX_FILE):
issues.append("索引.md 不存在")
return
# 获取所有 wiki 文章
wiki_files = get_md_files(WIKI)
with open(INDEX_FILE, 'r', encoding='utf-8') as f:
content = f.read()
# 解析索引中的文章路径
indexed_paths = set()
for line in content.split('\n'):
if '](' in line and line.startswith('|'):
m = re.search(r'\]\(([^)]+)\)', line)
if m:
path = m.group(1)
indexed_paths.add(path)
# 检查:文件存在但索引中没有
for fname, relpath in wiki_files.items():
if relpath not in indexed_paths:
log(f"⚠️ 文章存在但未在索引中: {relpath}")
# 尝试自动补索引(需要知道主题)
topic = relpath.split('/')[0] if '/' in relpath else None
if topic:
# 读取文章获取摘要
summary = ""
with open(os.path.join(WIKI, relpath), 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('## 概述'):
next_line = next(f, '').strip()
summary = next_line[:50]
break
if line.startswith('> 来源') or line.startswith('> Source'):
continue
# 生成新的索引行
today = datetime.now().strftime('%Y-%m-%d')
new_line = f"| [{fname[:-3]}]({relpath}) | {summary or '(无摘要)'} | {today} |"
# 找到对应主题的表格最后一行,追加
topic_header = f"## {topic}"
if topic_header in content:
# 找到 topic 区域末尾,插入新行
lines = content.split('\n')
insert_pos = -1
in_topic = False
for i, line in enumerate(lines):
if line == topic_header:
in_topic = True
continue
if in_topic and line.startswith('## '):
break
if in_topic and line.startswith('|') and '暂无' in line:
# 替换"暂无"行
lines[i] = new_line
insert_pos = -2 # flag for replaced
break
if in_topic and line.startswith('| ['):
insert_pos = i + 1
if insert_pos == -2:
# 已替换
pass
elif insert_pos > 0:
lines.insert(insert_pos, new_line)
content = '\n'.join(lines)
auto_fixed += 1
# 检查:索引中有但文件不存在
for path in indexed_paths:
full_path = os.path.join(WIKI, path)
if not os.path.exists(full_path):
log(f"⚠️ 索引中的文章已删除: {path}")
# 标记为 MISSING(避免重复追加)
if '[MISSING]' not in content.split(f"]({path})")[1][:30]:
content = content.replace(f"]({path})", f"]({path}) [MISSING]")
auto_fixed += 1
auto_fixed += 1
# 写入更新后的索引
with open(INDEX_FILE, 'w', encoding='utf-8') as f:
f.write(content)
def check_internal_links():
"""检查文章内部的 Wiki 链接"""
global auto_fixed
wiki_files = {}
for root, dirs, fnames in os.walk(WIKI):
for f in fnames:
if f.endswith('.md'):
rel = os.path.relpath(os.path.join(root, f), WIKI)
wiki_files[f] = rel
for root, dirs, fnames in os.walk(WIKI):
for fname in fnames:
if not fname.endswith('.md') or fname in ('索引.md', '操作日志.md'):
continue
fpath = os.path.join(root, fname)
with open(fpath, 'r', encoding='utf-8') as f:
content = f.read()
# 查找 markdown 链接
for m in re.finditer(r'\[([^\]]+)\]\(([^)]+)\)', content):
link_text = m.group(1)
link_target = m.group(2)
# 跳过外部链接
if link_target.startswith('http://') or link_target.startswith('https://'):
continue
# 跳过 file:// URI(本机文件链接)和页内锚点(#xxx)
if link_target.startswith('file://') or link_target.startswith('#'):
continue
# 检查路径是否有效
# 如果是相对路径,基于当前文件目录解析
base_dir = os.path.dirname(fpath)
abs_target = os.path.normpath(os.path.join(base_dir, link_target))
if not os.path.exists(abs_target):
target_file = os.path.basename(link_target)
if target_file in wiki_files:
# 找到了正确路径
correct_path = os.path.relpath(
os.path.join(WIKI, wiki_files[target_file]),
os.path.dirname(fpath)
)
if correct_path != link_target:
content = content.replace(
f']({link_target})',
f']({correct_path})'
)
log(f"🔧 修复链接: {fname} → {link_target} → {correct_path}")
auto_fixed += 1
else:
issues.append(f"断链: {fname} → {link_target} (文件不存在)")
with open(fpath, 'w', encoding='utf-8') as f:
f.write(content)
def check_orphan_pages():
"""检查孤立页面(没有其他文章引用)"""
all_content = ""
for root, dirs, fnames in os.walk(WIKI):
for fname in fnames:
if fname.endswith('.md') and fname not in ('索引.md', '操作日志.md'):
fpath = os.path.join(root, fname)
with open(fpath, 'r', encoding='utf-8') as f:
all_content += f.read() + "\n"
for root, dirs, fnames in os.walk(WIKI):
for fname in fnames:
if fname.endswith('.md') and fname not in ('索引.md', '操作日志.md'):
# 检查是否有其他文章引用了它
name_without_ext = fname[:-3]
if name_without_ext not in all_content.replace(fname, ''):
rel = os.path.relpath(os.path.join(root, fname), WIKI)
if rel not in ('供应链流程/知识库搭建架构.md', '供应链流程/2026-06-20-知识库搭建全记录.md'):
issues.append(f"孤立页面: {rel} (没有入链)")
def update_log():
"""更新操作日志"""
count_issues = len(issues)
if count_issues == 0 and auto_fixed == 0:
return
today = datetime.now().strftime("%Y-%m-%d")
with open(LOG_FILE, 'a', encoding='utf-8') as f:
f.write(f"\n## [{today}] 检查 | {count_issues} 个问题,{auto_fixed} 处自动修复\n")
for issue in issues:
f.write(f"- {issue}\n")
if __name__ == '__main__':
print(f"🔍 知识库检查开始...")
check_index_consistency()
check_internal_links()
check_orphan_pages()
update_log()
print(f"\n✅ 检查完成: {len(issues)} 个问题, {auto_fixed} 处自动修复")
if issues:
for issue in issues:
print(f" ⚠️ {issue}")