Files
UnityAI/hooks/sync_doc_associations.py
lms ee1ff7f444 [docs][refactor]: 将旧 01~06 中文教程合并整理为 modules/frameworks 文件夹结构
- 删除 01~05 旧中文教程目录
- 将 03 核心功能教程拆分为各 framework 模块的子文档
- 06 工具脚本移入 modules/frameworks/编辑器工具/UI工具/
- 非框架内容合并进 rules/20-开发规则.md 与 rules/40-工作流清单.md
- 更新所有交叉链接与文档关联表

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:36:15 +08:00

219 lines
7.4 KiB
Python
Raw 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
# hooks/sync_doc_associations.py
#
# 当 md 文件变更时,自动在关联文档的标准位置追加变更提醒:
# - 有 ## 变更记录 表格的模块文档:追加一行变更记录
# - 有 ## 下一步 清单的 progress/tasks 文档:追加一个待检查项
# - 其他文档:追加 ## 最近关联变更 章节
#
# 本脚本只追加、不覆盖正文;执行后请通过 git diff 人工确认。
import os
import re
import subprocess
import sys
from datetime import datetime
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
EXCLUDED_PREFIXES = (
'.claude/', '.codex/', '.git/',
'Library/', 'Temp/', 'Logs/', 'UserSettings/',
'obj/', 'bin/', 'HybridCLRData/',
'Assets/ThirdPlugins/',
'Assets/.WX-WASM-SDK-V2/',
'Packages/',
)
def run_git(args):
result = subprocess.run(
["git"] + args,
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
encoding="utf-8",
)
return result.stdout.strip(), result.returncode
def get_changed_md_files():
"""获取 tracked 改动 + untracked 新 md 文件。"""
changed, code = run_git(["diff", "--name-only"])
if code != 0 or not changed:
changed = ""
untracked, code = run_git(["ls-files", "--others", "--exclude-standard"])
if code == 0 and untracked:
changed = (changed + "\n" + untracked).strip()
if not changed:
return []
files = [line.strip() for line in changed.splitlines() if line.strip().endswith(".md")]
files = [f for f in files if not f.startswith(EXCLUDED_PREFIXES)]
return sorted(set(files))
def resolve_link(source_file: Path, link: str):
"""解析相对链接为项目相对路径。"""
if re.match(r"^[a-zA-Z][a-zA-Z0-9+.-]*://", link):
return None
if link.startswith("#"):
return None
link = link.split("#")[0]
if not link:
return None
target = (source_file.parent / link).resolve()
try:
target.relative_to(PROJECT_ROOT)
except ValueError:
return None
rel = target.relative_to(PROJECT_ROOT)
return str(rel).replace("\\", "/")
def extract_associations(md_path: Path):
"""读取 md 的 ## 文档关联 章节,返回关联文件相对路径列表。"""
content = md_path.read_text(encoding="utf-8")
in_section = False
targets = []
for line in content.splitlines():
if re.match(r"^##\s+文档关联\s*$", line):
in_section = True
continue
if in_section and re.match(r"^##\s+", line):
break
if in_section:
for _, link in re.findall(r"\[([^\]]*)\]\(([^\)]+)\)", line):
resolved = resolve_link(md_path, link)
if resolved:
targets.append(resolved)
return targets
def find_section_range(lines, title_re):
"""返回 [start, end) 行号end 为下一个 ## 标题或文件末尾。"""
start = None
for i, line in enumerate(lines):
if re.match(title_re, line):
start = i
continue
if start is not None and re.match(r"^##\s+", line):
return start, i
if start is not None:
return start, len(lines)
return None, None
def append_changelog(target_path: Path, source_rel: str, date_str: str) -> bool:
"""在 ## 变更记录 表格追加一行;成功返回 True。"""
content = target_path.read_text(encoding="utf-8")
lines = content.splitlines()
start, end = find_section_range(lines, r"^##\s+变更记录\s*$")
if start is None:
return False
# 找到表格最后一行(跳过标题和分隔线)
insert_pos = end
for i in range(start + 1, end):
if lines[i].strip().startswith("|") and not re.match(r"^\|[-:\s|]+\|$", lines[i].strip()):
insert_pos = i + 1
new_line = f"| {date_str} | [{source_rel}](../../{source_rel}) 发生变更 | 待补充影响范围 | 待确认 |"
lines.insert(insert_pos, new_line)
target_path.write_text("\n".join(lines) + ("\n" if content.endswith("\n") else ""), encoding="utf-8")
return True
def append_next_step(target_path: Path, source_rel: str) -> bool:
"""在 ## 下一步 清单追加一个待检查项;成功返回 True。"""
content = target_path.read_text(encoding="utf-8")
lines = content.splitlines()
start, end = find_section_range(lines, r"^##\s+下一步\s*$")
if start is None:
return False
new_line = f"- [ ] 检查 [{source_rel}](../../{source_rel}) 的变更是否影响本文档"
# 避免重复
for line in lines[start:end]:
if source_rel in line and "变更是否影响本文档" in line:
return True # 已存在,视为成功但不写入
lines.insert(end, new_line)
target_path.write_text("\n".join(lines) + ("\n" if content.endswith("\n") else ""), encoding="utf-8")
return True
def append_recent_changes_section(target_path: Path, source_rel: str, date_str: str) -> bool:
"""在没有 变更记录/下一步 的文档末尾追加 ## 最近关联变更。"""
content = target_path.read_text(encoding="utf-8")
# 生成从 target 到 source 的相对链接
try:
link = os.path.relpath(PROJECT_ROOT / source_rel, target_path.parent).replace("\\", "/")
except ValueError:
link = source_rel
if "## 最近关联变更" in content:
lines = content.splitlines()
start, end = find_section_range(lines, r"^##\s+最近关联变更\s*$")
if start is not None:
new_line = f"- {date_str}:关联文档 [{source_rel}]({link}) 发生变更,需同步检查。"
lines.insert(end, new_line)
target_path.write_text("\n".join(lines) + ("\n" if content.endswith("\n") else ""), encoding="utf-8")
return True
section = (
"\n---\n\n"
"## 最近关联变更\n\n"
f"- {date_str}:关联文档 [{source_rel}]({link}) 发生变更,需同步检查。\n"
)
target_path.write_text(content.rstrip() + section, encoding="utf-8")
return True
def sync_associations():
changed_files = get_changed_md_files()
if not changed_files:
print("[sync-doc-assoc] No .md changes detected.")
return 0
date_str = datetime.now().strftime("%Y-%m-%d")
modified = []
for source_rel in changed_files:
source_path = PROJECT_ROOT / source_rel
if not source_path.exists():
continue
targets = extract_associations(source_path)
for target_rel in targets:
target_path = PROJECT_ROOT / target_rel
if not target_path.is_file():
# Skip directories, globs, or missing targets
continue
if target_rel == source_rel:
continue
done = (
append_changelog(target_path, source_rel, date_str)
or append_next_step(target_path, source_rel)
or append_recent_changes_section(target_path, source_rel, date_str)
)
if done:
modified.append((source_rel, target_rel))
if not modified:
print("[sync-doc-assoc] No associated docs need updating.")
return 0
print("[sync-doc-assoc] Updated associated docs:")
for source, target in modified:
print(f" - {source} -> {target}")
print("\n[sync-doc-assoc] Please review the changes with git diff before committing.")
return 0
if __name__ == "__main__":
sys.exit(sync_associations())