phase 6 part 2: per-week related-topics sections (gen_week_topics.py), week14/15/16 adjustments from BOSS research, README/CONTRIBUTING updates
This commit is contained in:
1 parent
5b1a7a430f
commit
4f12b8b2ba
22 files changed
+588
-18
No files matched your search
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""根据 catalog.json 中每个条目的 week 字段,在 plan/weekNN-*.md 中生成“本周相关专题”一节。
|
||||
|
||||
- 生成内容放在 <!-- week-topics:start --> 和 <!-- week-topics:end --> 之间,重复运行会替换,不会重复插入
|
||||
- 第一次插入的位置:“## 本周自查清单 / ## 本周验收”之前(代码块之外);没有这两个标题时放在文末
|
||||
- 先运行 tools/build_catalog.py 生成 catalog.json
|
||||
|
||||
用法:python3 tools/gen_week_topics.py [--check] (--check 只检查是否需要更新,需要时返回 1)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
START, END = "<!-- week-topics:start -->", "<!-- week-topics:end -->"
|
||||
ANCHOR = re.compile(r"^## (本周自查清单|本周验收)")
|
||||
FENCE = re.compile(r"^\s*(```|~~~)")
|
||||
TYPE_ORDER = {"lesson": 0, "scenario": 1, "interview": 2}
|
||||
TYPE_NAME = {"lesson": "课程", "scenario": "场景", "interview": "面试题"}
|
||||
AI_CODING_NOTE = ("- **AI 编程练习(每周)**:本周至少一个功能用 AI 编程工具(Cursor / Claude Code / Codex 等)完成,"
|
||||
"按 [AI 编程:工作流](../topics/ai/ai-coding/01-workflow-rules-skills.md) 写清需求和规则,"
|
||||
"按 [验证与证据](../topics/ai/ai-coding/02-verify-evidence.md) 保存测试结果和会话记录(脱敏后),"
|
||||
"第 15 周整理成“AI 编程 + 验证方法”的应聘案例。")
|
||||
|
||||
|
||||
def build_section(week: int, items: list[dict], stacks: dict[str, dict]) -> str:
|
||||
by_stack: dict[str, list[dict]] = defaultdict(list)
|
||||
cross: list[dict] = []
|
||||
for it in items:
|
||||
if it["type"] == "scenario" and it["domain"] == "cross":
|
||||
cross.append(it)
|
||||
elif it["type"] in TYPE_ORDER and it.get("stack"):
|
||||
by_stack[it["path"].split("/")[2]].append(it)
|
||||
lines = [START, "", "## 本周相关专题", "",
|
||||
"> 本节由 `tools/gen_week_topics.py` 根据专题文件的 `week` 字段自动生成,请勿手改;"
|
||||
"想调整就修改对应专题文件的 frontmatter 后重新生成。", ""]
|
||||
if week <= 14:
|
||||
lines += [AI_CODING_NOTE, ""]
|
||||
if by_stack:
|
||||
lines += ["| 技术栈 | 课程 / 场景 / 面试题 |", "| --- | --- |"]
|
||||
for sid in sorted(by_stack, key=lambda s: (stacks.get(s, {}).get("domain", ""), s)):
|
||||
st = stacks.get(sid, {})
|
||||
name = f"[{st.get('title', sid)}](../{st['index']})" if st.get("index") else sid
|
||||
links = []
|
||||
for it in sorted(by_stack[sid], key=lambda x: (TYPE_ORDER[x["type"]], x.get("order") or 0, x["path"])):
|
||||
title = TYPE_NAME[it["type"]] if it["type"] == "interview" else it["title"].split(":")[0]
|
||||
links.append(f"{TYPE_NAME[it['type']]}:[{title}](../{it['path']})" if it["type"] != "interview"
|
||||
else f"[面试题](../{it['path']})")
|
||||
lines.append(f"| {name} | {';'.join(links)} |")
|
||||
lines.append("")
|
||||
if cross:
|
||||
lines += ["**跨技术栈场景**:" + "、".join(f"[{c['title'].split(':')[0]}](../{c['path']})" for c in sorted(cross, key=lambda c: c["path"])), ""]
|
||||
lines.append(END)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def insert(text: str, section: str) -> str:
|
||||
if START in text and END in text:
|
||||
return re.sub(re.escape(START) + r".*?" + re.escape(END), lambda _: section, text, count=1, flags=re.S)
|
||||
in_fence = False
|
||||
lines = text.split("\n")
|
||||
for i, line in enumerate(lines):
|
||||
if FENCE.match(line):
|
||||
in_fence = not in_fence
|
||||
elif not in_fence and ANCHOR.match(line):
|
||||
return "\n".join(lines[:i] + [section, ""] + lines[i:])
|
||||
return text.rstrip("\n") + "\n\n" + section + "\n"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
check = "--check" in sys.argv
|
||||
cat = json.loads((ROOT / "catalog.json").read_text(encoding="utf-8"))
|
||||
stacks = {s["stack"]: s for s in cat["stacks"]}
|
||||
per_week: dict[int, list[dict]] = defaultdict(list)
|
||||
for it in cat["items"]:
|
||||
if it["path"].startswith(("topics/", "scenarios/")):
|
||||
for w in it.get("week") or []:
|
||||
per_week[int(w)].append(it)
|
||||
stale = []
|
||||
for f in sorted((ROOT / "plan").glob("week*.md")):
|
||||
week = int(re.match(r"week(\d+)", f.name).group(1))
|
||||
old = f.read_text(encoding="utf-8")
|
||||
new = insert(old, build_section(week, per_week.get(week, []), stacks))
|
||||
if new != old:
|
||||
stale.append(f.name)
|
||||
if not check:
|
||||
f.write_text(new, encoding="utf-8")
|
||||
if check:
|
||||
if stale:
|
||||
print("需要重新生成:" + "、".join(stale))
|
||||
return 1
|
||||
print("各周“本周相关专题”已是最新")
|
||||
return 0
|
||||
print(f"已更新 {len(stale)} 个周计划文件")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user