mirror of
https://github.com/tradecatlabs/vibe-coding-cn.git
synced 2026-11-02 12:06:14 +00:00
* refactor: toolbox - 收口Context Skills Extensions三域资源 * fix: toolbox - 重定向文集中的嵌套标签链接 * fix: toolbox - 保全被ignore遮蔽的原跟踪事实文件 * refactor: toolbox - 退役workflows残留并平铺原定义 * docs: gongfa - 整理Lambda演算正文并冻结首发来源 * docs: gongfa - 登记Lambda演算天级上品并紧随拼好码 * docs: gongfa - 同步Lambda演算AI引用覆盖 * docs: readme - 移除工具箱怎么用整块 * refactor: skills - 删除16个官方引用仅保留自有入口 * refactor: extensions - 退役33个Skill脚本并清理调用 * refactor: maintenance - 迁移校验测试至.github维护层 * chore: extensions - 移除用户点名的八项程序与资料 * refactor: maintenance - 平铺共享模块并移除lib目录
171 lines
5.3 KiB
Python
171 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Regenerate collapsible fine-grained TOC blocks in docs linear README files."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
# 同一平铺维护模块由校验器和生成器共享,使用真实入口位置而非调用者cwd。
|
|
sys.path.insert(0, str(ROOT / '.github'))
|
|
from taxonomy import doc_readmes_from_taxonomy
|
|
|
|
SUMMARY_LINE = "<summary><strong>完整细粒度目录(点击展开/收起)</strong></summary>"
|
|
ANCHOR_PATTERN = re.compile(r"<a\s+id=[\"']([^\"']+)[\"']")
|
|
HEADING_PATTERN = re.compile(r"^(#{2,6})\s+(.+?)\s*#*\s*$")
|
|
|
|
|
|
def heading_after(lines: list[str], start: int) -> tuple[int, str] | None:
|
|
for line in lines[start + 1 : start + 8]:
|
|
stripped = line.strip()
|
|
if not stripped:
|
|
continue
|
|
match = HEADING_PATTERN.match(stripped)
|
|
if not match:
|
|
return None
|
|
return len(match.group(1)), match.group(2).strip()
|
|
return None
|
|
|
|
|
|
def title_from_anchor(anchor: str, main_anchors: list[str]) -> str:
|
|
for main_anchor in sorted(main_anchors, key=len, reverse=True):
|
|
if anchor == main_anchor:
|
|
return main_anchor
|
|
prefix = f"{main_anchor}-"
|
|
if anchor.startswith(prefix):
|
|
return anchor.removeprefix(prefix).replace("-", " ")
|
|
return anchor.replace("-", " ")
|
|
|
|
|
|
def collect_toc_entries(text: str, main_anchors: list[str]) -> list[tuple[int, str, str]]:
|
|
lines = text.splitlines()
|
|
required_prefixes = tuple(f"{anchor}-" for anchor in main_anchors)
|
|
entries: list[tuple[int, str, str]] = []
|
|
seen: set[str] = set()
|
|
|
|
for idx, line in enumerate(lines):
|
|
for anchor in ANCHOR_PATTERN.findall(line):
|
|
if anchor in seen:
|
|
continue
|
|
if anchor not in main_anchors and not anchor.startswith(required_prefixes):
|
|
continue
|
|
seen.add(anchor)
|
|
|
|
heading = heading_after(lines, idx)
|
|
if heading:
|
|
level, title = heading
|
|
else:
|
|
level, title = 3, title_from_anchor(anchor, main_anchors)
|
|
entries.append((level, title, anchor))
|
|
|
|
return entries
|
|
|
|
|
|
def render_toc(entries: list[tuple[int, str, str]]) -> str:
|
|
rendered = [
|
|
"<details>",
|
|
SUMMARY_LINE,
|
|
"",
|
|
"### 细粒度目录",
|
|
"",
|
|
]
|
|
|
|
for level, title, anchor in entries:
|
|
indent = " " * max(level - 2, 0)
|
|
rendered.append(f"{indent}- [{title}](#{anchor})")
|
|
|
|
rendered.extend(["", "</details>"])
|
|
return "\n".join(rendered)
|
|
|
|
|
|
def active_toc_bounds(text: str) -> tuple[int, int] | None:
|
|
in_fence = False
|
|
fence_marker = ""
|
|
offset = 0
|
|
details_start: int | None = None
|
|
|
|
for line in text.splitlines(keepends=True):
|
|
stripped = line.lstrip()
|
|
if stripped.startswith(("```", "~~~")):
|
|
marker = stripped[:3]
|
|
if not in_fence:
|
|
in_fence = True
|
|
fence_marker = marker
|
|
elif marker == fence_marker:
|
|
in_fence = False
|
|
fence_marker = ""
|
|
|
|
if not in_fence:
|
|
if "<details>" in line:
|
|
details_start = offset + line.index("<details>")
|
|
if SUMMARY_LINE in line and details_start is not None:
|
|
end = text.find("</details>", offset)
|
|
if end == -1:
|
|
return None
|
|
return details_start, end + len("</details>")
|
|
|
|
offset += len(line)
|
|
|
|
return None
|
|
|
|
|
|
def replace_toc_block(text: str, toc: str) -> tuple[str, bool]:
|
|
bounds = active_toc_bounds(text)
|
|
if bounds is None:
|
|
return text, False
|
|
|
|
details_start, details_end = bounds
|
|
return f"{text[:details_start]}{toc}{text[details_end:]}", True
|
|
|
|
|
|
def main() -> int:
|
|
changed: list[str] = []
|
|
errors: list[str] = []
|
|
|
|
doc_readmes = doc_readmes_from_taxonomy()
|
|
if not doc_readmes:
|
|
print("OK synced docs TOC blocks: 0 changed")
|
|
return 0
|
|
|
|
for rel_path, main_anchors in doc_readmes.items():
|
|
path = ROOT / rel_path
|
|
if not path.exists():
|
|
errors.append(f"{rel_path}: missing docs README")
|
|
continue
|
|
|
|
text = path.read_text(encoding='utf-8')
|
|
if text.startswith(('<!-- generated: sync-gongfa-catalog.py -->',
|
|
'<!-- generated: sync-faqi-catalog.py -->')):
|
|
# 两本文集的目录由唯一JSON源的生成器维护;结构检查仍覆盖完整锚点。
|
|
continue
|
|
entries = collect_toc_entries(text, main_anchors)
|
|
if not entries:
|
|
errors.append(f"{rel_path}: no TOC entries generated")
|
|
continue
|
|
|
|
new_text, replaced = replace_toc_block(text, render_toc(entries))
|
|
if not replaced:
|
|
errors.append(f"{rel_path}: missing standard collapsible TOC block")
|
|
continue
|
|
|
|
if new_text != text:
|
|
path.write_text(new_text, encoding="utf-8")
|
|
changed.append(str(rel_path))
|
|
|
|
if errors:
|
|
print("SYNC_DOC_TOC_ERRORS")
|
|
for error in errors:
|
|
print(error)
|
|
return 1
|
|
|
|
for rel_path in changed:
|
|
print(f"updated {rel_path}")
|
|
print(f"OK synced docs TOC blocks: {len(changed)} changed")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|