track: 新增 OpenWork / Orca / Maka 三项目,样本达到 4 个
样本 - openwork (different-ai/openwork) 23.2k★ 能力分发层,DCO + evals + worlds - orca (stablyai/orca) 58.8k★ 并行编排外壳,5000 issue 实为团队自开任务板 - maka (apache/maka) 4.4k★ 执行记录层,Apache 孵化但无正式 release 档案 - 三份五维档案填完,risk.claim_vs_reality 均已核实到证据 - ipollowork 象限坐标从占位 9/9/9/9 校准为 8/7/7/7(与新样本同一标尺) 文章 - 新增横评文《四个 Agent 工作台的路线分野:把「多引擎中立」拆开看》(emlog id=99) - 总纲文回填四项目矩阵 + 双视图象限图 (emlog id=98) 镜像 - gitinbox/maka 已推送(public,source-only 快照 12 MB) - openwork(291MB) / orca(671MB) 受 50KB/s 带宽约束,档案标 mode: pending 规则 - license-tiers.yaml 新增「目录切分许可」降级流程(OpenWork 判例: 排除 ee/ 后为纯 MIT → public,完整镜像 → private) - registry.yaml openwork 可见性改 public + mirror_excludes: ["ee/"] 脚本修复 - _common.py 新增 upsert_block:手工整块覆盖 ecosystem 删掉 metrics 时能自愈 - mirror_sync.sh:SOURCE_ONLY 未初始化(set -u 下偶发炸); 浅仓推送被服务端拒绝 → 自动 --unshallow,失败降级快照; 量体积前先 repack(此前新 commit 全是 loose object,恒打印 0 MB) - build_matrix.py:成熟度长文本移出表格(新增 maturity_level 短标签); 象限图标签靠右时翻转到左侧防溢出;SVG 加 max-width 防窄屏溢出; 文章索引改为自动生成(原先手抄,横评文发了索引里却没有) - publish_emlog.py:ID 回写按「有无同名档案」分流, 00-* 跨项目文章落到 articles/<id>.meta.yaml(此前 ID 无处可写)
This commit is contained in:
+138
-4
@@ -28,6 +28,28 @@ from _common import ARTICLES, PROJECTS, load_yaml # noqa: E402
|
||||
|
||||
QUAD_THRESHOLD = 4
|
||||
BEGIN, END = "<!-- MATRIX:BEGIN -->", "<!-- MATRIX:END -->"
|
||||
IDX_BEGIN, IDX_END = "<!-- INDEX:BEGIN -->", "<!-- INDEX:END -->"
|
||||
OVERVIEW_AID = "00-overview"
|
||||
|
||||
|
||||
def short_maturity(risk: dict) -> str:
|
||||
"""成熟度在档案里是一整段判断(几十字),直接塞进表格会把列撑爆。
|
||||
|
||||
优先用档案给的 `maturity_level` 短标签(人工判断最准);
|
||||
没有则从长文本截取第一个分句兜底。
|
||||
"""
|
||||
r = risk or {}
|
||||
lv = r.get("maturity_level")
|
||||
if lv:
|
||||
return str(lv)
|
||||
m = (r.get("maturity") or "").strip()
|
||||
if not m:
|
||||
return "-"
|
||||
for sep in ("。", ";", "\n"):
|
||||
if sep in m:
|
||||
m = m.split(sep)[0]
|
||||
break
|
||||
return m[:14] + ("…" if len(m) > 14 else "")
|
||||
|
||||
|
||||
def cell(v, empty="-") -> str:
|
||||
@@ -93,9 +115,18 @@ def build_tables(items: list[dict]) -> str:
|
||||
stale = " ⚠" if m.get("stale") else ""
|
||||
add(f"| **{d.get('name','')}** | {cell(m.get('stars'))} | {cell(m.get('forks'))} | "
|
||||
f"{cell(m.get('open_issues'))} | {cell(m.get('latest_release'))} | "
|
||||
f"{cell(m.get('release_cadence'))} | {cell((d.get('risk') or {}).get('maturity'))} | "
|
||||
f"{cell(m.get('release_cadence'))} | {short_maturity(d.get('risk') or {})} | "
|
||||
f"{cell(m.get('data_date'))}{stale} |")
|
||||
|
||||
# 成熟度是长文本,塞进表格会把列撑爆。表格里只放短标签,
|
||||
# 完整的成熟度判断单独列在表后,读者想看细节再往下读。
|
||||
mats = [(d.get("name", ""), (d.get("risk") or {}).get("maturity")) for d in items]
|
||||
if any(v for _, v in mats):
|
||||
add("\n**成熟度详解**\n")
|
||||
for name, v in mats:
|
||||
if v:
|
||||
add(f"- **{name}**:{v}")
|
||||
|
||||
# 宣称 vs 实测:全套矩阵里信息密度最高的一张表
|
||||
rows = []
|
||||
for d in items:
|
||||
@@ -121,6 +152,87 @@ def build_tables(items: list[dict]) -> str:
|
||||
return "\n".join(L)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 文章索引
|
||||
def read_h1(aid: str) -> str:
|
||||
"""取 articles/<aid>.md 的第一个一级标题作为文章名。"""
|
||||
p = ARTICLES / f"{aid}.md"
|
||||
if not p.exists():
|
||||
return aid
|
||||
for line in p.read_text(encoding="utf-8").lstrip().split("\n"):
|
||||
if line.startswith("# "):
|
||||
return line[2:].strip()
|
||||
return aid
|
||||
|
||||
|
||||
def collect_cross_articles() -> list[dict]:
|
||||
"""跨项目文章(总纲 / 横评 / 专题)的发布元数据。
|
||||
|
||||
ID 落在 articles/<aid>.meta.yaml 里,由 publish_emlog.py 维护。
|
||||
总纲文自身要排除——索引出现在总纲内部,自己链自己没有意义。
|
||||
"""
|
||||
out = []
|
||||
for mp in sorted(ARTICLES.glob("*.meta.yaml")):
|
||||
aid = mp.name[: -len(".meta.yaml")]
|
||||
if aid == OVERVIEW_AID:
|
||||
continue
|
||||
d = load_yaml(mp) or {}
|
||||
if not d.get("emlog_id"):
|
||||
continue
|
||||
out.append({
|
||||
"aid": aid,
|
||||
"title": read_h1(aid),
|
||||
"emlog_id": d["emlog_id"],
|
||||
"url": d.get("url") or f"https://blog.apescale.com/?post={d['emlog_id']}",
|
||||
"last_published": d.get("last_published", ""),
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
def article_index_section(items: list[dict]) -> str:
|
||||
"""文章索引。
|
||||
|
||||
这段原来是手抄的,结果新发了横评文、索引里却没有它——手写的索引
|
||||
每发一篇文章就会漂移一次。改成从两处事实源生成:
|
||||
项目深度文 ← data/projects/*.yaml 的 article 块
|
||||
跨项目文章 ← articles/*.meta.yaml
|
||||
"""
|
||||
L = ["<!-- INDEX:BEGIN -->"]
|
||||
cross = collect_cross_articles()
|
||||
|
||||
L.append("| 项目 | 一句话结论 | 深度文 |")
|
||||
L.append("| --- | --- | --- |")
|
||||
fallback = cross[0]["url"] if cross else ""
|
||||
for d in items:
|
||||
art = d.get("article") or {}
|
||||
url = art.get("url") or ""
|
||||
if url:
|
||||
link = f"[已发布]({url})"
|
||||
elif fallback:
|
||||
link = f"待发布(见下方横评)"
|
||||
else:
|
||||
link = "待发布"
|
||||
one = (d.get("verdict") or {}).get("one_liner") or "-"
|
||||
# 一句话结论本来就长,索引表里截断到第一个分句,避免一行几十字
|
||||
for sep in ("——", "。", ";"):
|
||||
if sep in one:
|
||||
one = one.split(sep)[0]
|
||||
break
|
||||
L.append(f"| **{d.get('name','')}** | {one} | {link} |")
|
||||
|
||||
if cross:
|
||||
L.append("\n**跨项目文章**\n")
|
||||
for a in cross:
|
||||
L.append(f"- [{a['title']}]({a['url']}) — {a['last_published']}")
|
||||
|
||||
pending = [d.get("name", "") for d in items if not (d.get("article") or {}).get("url")]
|
||||
if pending:
|
||||
L.append(f"\n> 待补深度文:{'、'.join(pending)}。"
|
||||
f"当前可先读上方跨项目文章,其中已包含这些项目的完整分维度对比。")
|
||||
|
||||
L.append("<!-- INDEX:END -->")
|
||||
return "\n".join(L)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 图
|
||||
def stack_diagram(items: list[dict]) -> str:
|
||||
"""项目 <4 个时的赛道分层图:按交互形态把项目归到三层栈。"""
|
||||
@@ -161,8 +273,12 @@ def quadrant_svg(items: list[dict]) -> str:
|
||||
svg = []
|
||||
for i, (title, xk, yk, xlo, xhi, ylo, yhi) in enumerate(plots):
|
||||
ox = i * (W + 30)
|
||||
# max-width:100% + height:auto 不能省:SVG 带固定 width/height 属性,
|
||||
# 在窄屏(手机、窄主题)里会撑破容器横向溢出。有 viewBox 时浏览器
|
||||
# 才能按这两个声明等比缩放。
|
||||
svg.append(f'<svg viewBox="0 0 {W} {H}" width="{W}" height="{H}" '
|
||||
f'xmlns="http://www.w3.org/2000/svg" style="font-family:sans-serif">')
|
||||
f'xmlns="http://www.w3.org/2000/svg" '
|
||||
f'style="font-family:sans-serif;max-width:100%;height:auto">')
|
||||
svg.append(f'<text x="{W//2}" y="26" text-anchor="middle" font-size="15" '
|
||||
f'font-weight="600" fill="#1f2328">{title}</text>')
|
||||
px, py, pw, ph = PAD, 55, W - PAD * 2, H - PAD - 55
|
||||
@@ -190,7 +306,16 @@ def quadrant_svg(items: list[dict]) -> str:
|
||||
name = d.get("name", "")
|
||||
svg.append(f'<circle cx="{cx:.1f}" cy="{cy:.1f}" r="7" fill="#0969da" '
|
||||
f'fill-opacity="0.75" stroke="#fff" stroke-width="1.5"/>')
|
||||
svg.append(f'<text x="{cx+11:.1f}" y="{cy+4:.1f}" font-size="12" fill="#1f2328">{name}</text>')
|
||||
# 标签默认画在点右侧,但满分点会顶到绘图区右边界(cx == px+pw),
|
||||
# 文字就会溢出画布被裁掉。靠右时翻到点的左侧。
|
||||
# 估算文字宽度:中文按 12px、其余按 7px,够用且不依赖字体度量。
|
||||
tw = sum(12 if ord(ch) > 0x2E80 else 7 for ch in name)
|
||||
if cx + 11 + tw > W - 4:
|
||||
svg.append(f'<text x="{cx-11:.1f}" y="{cy+4:.1f}" text-anchor="end" '
|
||||
f'font-size="12" fill="#1f2328">{name}</text>')
|
||||
else:
|
||||
svg.append(f'<text x="{cx+11:.1f}" y="{cy+4:.1f}" font-size="12" '
|
||||
f'fill="#1f2328">{name}</text>')
|
||||
svg.append("</svg>")
|
||||
return "\n".join(svg)
|
||||
|
||||
@@ -216,7 +341,7 @@ def main() -> int:
|
||||
print(block)
|
||||
return 0
|
||||
|
||||
path = ARTICLES / f"{'00-overview'}.md"
|
||||
path = ARTICLES / f"{OVERVIEW_AID}.md"
|
||||
if not path.exists():
|
||||
print(f"总纲文不存在:{path}")
|
||||
return 1
|
||||
@@ -225,6 +350,15 @@ def main() -> int:
|
||||
print(f"总纲文缺少 {BEGIN} / {END} 占位标记,未回填。")
|
||||
return 1
|
||||
text = re.sub(rf"{re.escape(BEGIN)}.*?{re.escape(END)}", lambda _: block, text, flags=re.S)
|
||||
|
||||
# 文章索引同样回填,两个块各管一段,互不干扰
|
||||
index = article_index_section(items)
|
||||
if IDX_BEGIN in text and IDX_END in text:
|
||||
text = re.sub(rf"{re.escape(IDX_BEGIN)}.*?{re.escape(IDX_END)}",
|
||||
lambda _: index, text, flags=re.S)
|
||||
else:
|
||||
print(f" [warn] 总纲文缺少 {IDX_BEGIN} / {IDX_END},文章索引未回填")
|
||||
|
||||
path.write_text(text, encoding="utf-8")
|
||||
print(f"==> 已回填总纲文:{path}")
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user