import os import re WIKI_DIR = r"C:\Program_coding\임도설계 및 견적자동화 프로그램 개발\docs\wiki" PAGES_DIR = os.path.join(WIKI_DIR, "pages") CONCEPTS_DIR = os.path.join(WIKI_DIR, "concepts") INDEX_PATH = os.path.join(WIKI_DIR, "index.md") errors = [] warnings = [] all_wiki_files = {} # relative_path -> absolute_path # Collect all markdown files in wiki/ (ignoring graphify-out and memory directories) for root, dirs, files in os.walk(WIKI_DIR): # skip graphify-out or memory if "graphify-out" in root or "memory" in root: continue for file in files: if file.endswith(".md"): abs_path = os.path.join(root, file) rel_path = os.path.relpath(abs_path, WIKI_DIR).replace("\\", "/") all_wiki_files[rel_path] = abs_path simple_name = os.path.splitext(rel_path)[0] all_wiki_files[simple_name] = abs_path # Read index.md content with open(INDEX_PATH, "r", encoding="utf-8") as f: index_content = f.read() # Pattern for wikilinks: [[link]] or [[link|label]] wikilink_pat = re.compile(r"\[\[([^\]|]+)(?:\|[^\]]+)?\]\]") for rel_path, abs_path in list(all_wiki_files.items()): if "/" not in rel_path and rel_path != "index" and rel_path != "log": continue if rel_path in ["index", "log", "index.md", "log.md"]: continue with open(abs_path, "r", encoding="utf-8") as f: lines = f.readlines() line_count = len(lines) content = "".join(lines) # Rule 14: Max 100 lines if line_count > 100: warnings.append(f"[Line Count] `{rel_path}.md` exceeds 100 lines ({line_count} lines).") # Check YAML Frontmatter via simple regex frontmatter_match = re.match(r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL) if not frontmatter_match: errors.append(f"[Frontmatter] `{rel_path}.md` has no valid YAML frontmatter.") continue fm_text = frontmatter_match.group(1) # Simple regex parsing for status & page_id status_match = re.search(r"^status:\s*(\w+)", fm_text, re.MULTILINE) page_id_match = re.search(r"^page_id:\s*([^\n\r]+)", fm_text, re.MULTILINE) if not status_match: errors.append(f"[Status] `{rel_path}.md` has no status field in frontmatter.") else: status = status_match.group(1).strip() if status not in ["draft", "stable", "stale"]: errors.append( f"[Status] `{rel_path}.md` has invalid status '{status}'. Must be draft, stable, or stale." ) elif status == "stale": warnings.append( f"[Stale Page] `{rel_path}.md` is marked as stale and needs updates from raw inputs." ) # Rule 5: page_id required for pages/ if "pages/" in rel_path: if not page_id_match: errors.append(f"[page_id] `{rel_path}.md` is in pages/ but lacks a 'page_id' field.") # Check for broken wikilinks in content links = wikilink_pat.findall(content) for link in links: link_clean = ( link.strip().replace("\\", "/").split("#")[0] ) # ignore anchor for file existence check if not link_clean: continue found = False # 1. Absolute link from vault root (e.g. concepts/storage_paths) if link_clean in all_wiki_files: found = True # 2. Simple name match (e.g. storage_paths, B01_frontend) elif link_clean.split("/")[-1] in all_wiki_files: found = True # 3. Handle subpages like [[B01_Dashboard/B01_frontend]] elif f"pages/{link_clean}" in all_wiki_files: found = True elif f"concepts/{link_clean}" in all_wiki_files: found = True # 4. Handle nested directory patterns elif link_clean.startswith("../"): # Resolve relative link curr_dir = os.path.dirname(rel_path) resolved = os.path.normpath(os.path.join(curr_dir, link_clean)).replace("\\", "/") if resolved in all_wiki_files or resolved.replace("pages/", "") in all_wiki_files: found = True if not found: errors.append(f"[Broken Link] `{rel_path}.md` contains broken wikilink: [[{link}]]") # Check if this file is registered in index.md (avoid orphans) file_base = os.path.basename(abs_path) file_name_no_ext = os.path.splitext(file_base)[0] if file_name_no_ext not in index_content: # check if relative path is in index rel_path_no_ext = rel_path.replace(".md", "") if ( rel_path_no_ext not in index_content and rel_path_no_ext.split("/")[-1] not in index_content ): warnings.append( f"[Orphan Page] `{rel_path}.md` is not linked or mentioned in index.md." ) print("=== LINT ERRORS ===") for e in sorted(list(set(errors))): print(e) print("\n=== LINT WARNINGS ===") for w in sorted(list(set(warnings))): print(w) print("\nLint completed.")