import os import re from pathlib import Path # 위키 루트 경로 설정 wiki_root = Path(r"D:\02_Software_Prog\임도설계 및 견적자동화 프로그램 개발\docs\wiki") raw_root = Path(r"D:\02_Software_Prog\임도설계 및 견적자동화 프로그램 개발\docs\raw") def load_frontmatter(file_path): content = file_path.read_text(encoding="utf-8") # Frontmatter regex match = re.match(r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL) if match: fm_text = match.group(1) fm = {} # Simple YAML key-value parser for basic string/list properties for line in fm_text.splitlines(): line = line.strip() if not line or line.startswith("#"): continue if ":" in line: key, val = line.split(":", 1) key = key.strip() val = val.strip().strip("'\"") fm[key] = val return fm, content[match.end():] return None, content def extract_wiki_links(text): # Matches [[link]] or [[link|alias]] or [[link#section]] or [[link#section|alias]] links = re.findall(r"\[\[(.*?)\]\]", text) cleaned_links = [] for l in links: # Split alias if "|" in l: l = l.split("|")[0] # Split section if "#" in l: l = l.split("#")[0] l = l.strip() if l: cleaned_links.append(l) return cleaned_links def run_lint(): print("=== Start Wiki Linting (No PyYAML, Filtered) ===") all_files = list(wiki_root.glob("**/*.md")) index_file = wiki_root / "index.md" log_file = wiki_root / "log.md" # 1. 파일 목록화 및 Frontmatter 체크 pages = {} concepts = {} errors = [] warnings = [] for f in all_files: rel_path = f.relative_to(wiki_root).as_posix() # Skip output files generated by graphify in graphify-out if rel_path.startswith("graphify-out/") or rel_path in ["index.md", "log.md", "AGENTS.md", "CLAUDE.md"]: continue fm, body = load_frontmatter(f) # 기본 규칙 검사 if not fm: errors.append(f"Missing Frontmatter: {rel_path}") continue # status 값 검사 status = fm.get("status") if status not in ["draft", "stable", "stale"]: errors.append(f"Invalid status '{status}' in {rel_path}. Must be draft, stable, or stale.") # 100줄 제한 검사 (10% 즉 110줄 허용) lines_count = len(f.read_text(encoding="utf-8").splitlines()) if lines_count > 110: warnings.append(f"File exceeds 110 lines ({lines_count} lines): {rel_path}") # 페이지와 컨셉 분류 if "pages/" in rel_path: pages[rel_path] = { "fm": fm, "body": body, "path": f, "links": extract_wiki_links(body) } # 파일명 형식 규칙 5 검사: {page_id}_{기능명}.md filename = f.name page_id = fm.get("page_id") if not page_id: errors.append(f"Missing page_id in page: {rel_path}") else: expected_prefix = page_id.split("_")[0] # e.g. A01 if not filename.startswith(expected_prefix): warnings.append(f"Filename does not match page_id format: {rel_path} (page_id: {page_id})") else: concepts[rel_path] = { "fm": fm, "body": body, "path": f, "links": extract_wiki_links(body) } # 2. 위키 링크 정합성 검증 (Broken Links) # 개념/페이지 맵 구성 available_links = {} for p_rel in pages: name_no_ext = Path(p_rel).with_suffix("").as_posix() available_links[name_no_ext] = p_rel # Also map short form if distinct short_name = Path(p_rel).name[:-3] available_links[short_name] = p_rel for c_rel in concepts: name_no_ext = Path(c_rel).with_suffix("").as_posix() available_links[name_no_ext] = c_rel # Short form short_name = Path(c_rel).name[:-3] available_links[short_name] = c_rel # Subdirectories for concepts like db_schema/* if "concepts/" in name_no_ext: available_links[name_no_ext.replace("concepts/", "")] = c_rel # 링크 검사 for rel_path, info in {**pages, **concepts}.items(): for link in info["links"]: if link.startswith("http://") or link.startswith("https://") or link.startswith("file:///"): continue normalized_link = link.replace("\\", "/") if normalized_link not in available_links and f"concepts/{normalized_link}" not in available_links and f"pages/{normalized_link}" not in available_links: warnings.append(f"Broken Link in {rel_path}: [[{link}]]") # 3. index.md 등록 상태 확인 index_content = index_file.read_text(encoding="utf-8") if index_file.exists() else "" for rel_path in pages: short_name = Path(rel_path).name[:-3] dir_name = Path(rel_path).parent.name expected_ref_dir = f"{dir_name}/{short_name}" if expected_ref_dir not in index_content and f"[[{short_name}]]" not in index_content and short_name not in index_content: warnings.append(f"Page not indexed in index.md: {rel_path} (Expected link to [[{expected_ref_dir}]] or [[{short_name}]])") # 4. 결과 출력 print(f"\nScanning completed: {len(all_files)} total markdown files.") print(f"Detected {len(pages)} pages and {len(concepts)} concept documents.") print(f"\n--- Errors ({len(errors)}) ---") for e in errors: print(f"[ERROR] {e}") print(f"\n--- Warnings ({len(warnings)}) ---") for w in warnings: print(f"[WARN] {w}") if __name__ == "__main__": run_lint()