#!/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}")