a6f05ab2d5
- 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
187 lines
5.6 KiB
Python
187 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
import sys
|
|
import io
|
|
import re
|
|
import datetime
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Tuple
|
|
|
|
if sys.platform == "win32":
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
|
|
|
|
|
def extract_existing_frontmatter(file_path: Path) -> Dict[str, any]:
|
|
try:
|
|
content = file_path.read_text(encoding="utf-8")
|
|
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
|
|
if match:
|
|
fm_text = match.group(1)
|
|
fm = {}
|
|
for line in fm_text.split("\n"):
|
|
if ":" in line:
|
|
key, value = line.split(":", 1)
|
|
key = key.strip()
|
|
value = value.strip()
|
|
if value.startswith("[") or value.startswith("{"):
|
|
continue
|
|
elif value.startswith('"') and value.endswith('"'):
|
|
fm[key] = value.strip('"')
|
|
elif value:
|
|
fm[key] = value
|
|
return fm
|
|
except Exception as e:
|
|
print(f"Error reading {file_path.name}: {e}")
|
|
return {}
|
|
|
|
|
|
def get_card_type_from_filename(filename: str) -> Tuple[str, str]:
|
|
name = Path(filename).stem
|
|
|
|
if "AI教育" in name or "全球图景" in name or "九校" in name:
|
|
return "concept", "概念"
|
|
elif any(
|
|
x in name
|
|
for x in [
|
|
"CMU",
|
|
"MIT",
|
|
"斯坦福",
|
|
"牛津",
|
|
"哈佛",
|
|
"AIEOU",
|
|
"RAISE",
|
|
"Accelerator",
|
|
]
|
|
):
|
|
return "entity", "机构"
|
|
elif any(
|
|
x in name for x in ["Victor", "Emma", "Ken", "Rose", "Neil", "Ryan", "Kestin"]
|
|
):
|
|
return "entity", "人物"
|
|
elif "知识卡片" in name:
|
|
return "concept", "概念"
|
|
|
|
return "concept", "概念"
|
|
|
|
|
|
def update_frontmatter(file_path: Path) -> bool:
|
|
try:
|
|
content = file_path.read_text(encoding="utf-8")
|
|
|
|
if not content.startswith("---"):
|
|
print(f"⚠️ No frontmatter in {file_path.name}")
|
|
return False
|
|
|
|
fm_match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
|
|
if not fm_match:
|
|
print(f"⚠️ Cannot parse frontmatter in {file_path.name}")
|
|
return False
|
|
|
|
fm_text = fm_match.group(1)
|
|
body = content[fm_match.end() :]
|
|
|
|
existing_fm = {}
|
|
for line in fm_text.split("\n"):
|
|
if ":" in line:
|
|
key, value = line.split(":", 1)
|
|
existing_fm[key.strip()] = value.strip()
|
|
|
|
card_type, card_category = get_card_type_from_filename(file_path.name)
|
|
|
|
new_fm_lines = [
|
|
"---",
|
|
"categories:",
|
|
' - "[[LLM Wiki]]"',
|
|
' - "[[知识卡片]]"',
|
|
"tags:",
|
|
]
|
|
|
|
existing_tags = existing_fm.get("tags", "")
|
|
if existing_tags:
|
|
if existing_tags.startswith("["):
|
|
tag_match = re.findall(r"'([^']+)'", existing_tags)
|
|
for tag in tag_match:
|
|
new_fm_lines.append(f" - {tag}")
|
|
else:
|
|
for tag in existing_tags.split(","):
|
|
tag = tag.strip()
|
|
if tag:
|
|
new_fm_lines.append(f" - {tag}")
|
|
else:
|
|
new_fm_lines.append(" - AI教育")
|
|
new_fm_lines.append(" - 知识卡片")
|
|
|
|
new_fm_lines.extend(
|
|
[
|
|
f"type: {card_type}",
|
|
f"created: {existing_fm.get('created', datetime.date.today().isoformat())}",
|
|
]
|
|
)
|
|
|
|
if "updated" in existing_fm:
|
|
new_fm_lines.append(f"updated: {existing_fm['updated']}")
|
|
if "source" in existing_fm:
|
|
new_fm_lines.append(f"source: {existing_fm['source']}")
|
|
if "title" in existing_fm:
|
|
new_fm_lines.append(f"title: {existing_fm['title']}")
|
|
if "review-date" in existing_fm:
|
|
new_fm_lines.append(f"review-date: {existing_fm['review-date']}")
|
|
if "review-status" in existing_fm:
|
|
new_fm_lines.append(f"review-status: {existing_fm['review-status']}")
|
|
|
|
new_fm_lines.append("---")
|
|
|
|
new_content = "\n".join(new_fm_lines) + body
|
|
file_path.write_text(new_content, encoding="utf-8")
|
|
|
|
print(
|
|
f"✅ Updated: {file_path.name} (type={card_type}, category={card_category})"
|
|
)
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"❌ Error updating {file_path.name}: {e}")
|
|
return False
|
|
|
|
|
|
def main():
|
|
print("=" * 60)
|
|
print("Knowledge Cards Frontmatter Batch Update Tool")
|
|
print("=" * 60)
|
|
print(f"Execution time: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
print()
|
|
|
|
cards_dir = Path(__file__).parent.parent / "知识卡片"
|
|
|
|
if not cards_dir.exists():
|
|
print(f"❌ Knowledge cards directory not found: {cards_dir}")
|
|
return
|
|
|
|
card_files = list(cards_dir.glob("*.md"))
|
|
print(f"Found {len(card_files)} knowledge card files")
|
|
print()
|
|
|
|
success_count = 0
|
|
fail_count = 0
|
|
|
|
for card_file in card_files:
|
|
result = update_frontmatter(card_file)
|
|
if result:
|
|
success_count += 1
|
|
else:
|
|
fail_count += 1
|
|
|
|
print()
|
|
print("=" * 60)
|
|
print("Update Complete")
|
|
print("=" * 60)
|
|
print(f"Total files: {len(card_files)}")
|
|
print(f"Successfully updated: {success_count}")
|
|
print(f"Failed: {fail_count}")
|
|
print("=" * 60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|