Files
WorkBuddy df56c2c137 初始化提交:contentm_agent 工作区全量快照
内容分四块:
1、产品规划产出 —— MCN 短视频整合营销工作台的①段五份(1a 需求/1b 竞品/1c 画像/1d 策略/1e 场景)、②段两份(2a 功能/2b 布局)、③段界面(DESIGN.md 契约与令牌表 + mcn-workbench.html 原型 + 实测/会诊/审查三份 + 23 张闸门截图)。
2、开源竞品调研 —— 5 个内容工作台项目的取证原始件与 1b 系列分析文档。
3、参考资料 —— 竞品视频抽帧 1145 张 + 2 个源视频 + 功能点截图。
4、机制侧 —— 协作脚本与状态台账、工作区记忆日志、抽帧/OCR 脚本。

.gitignore 只排运行时日志、脚本备份副本与一次性探针输出,其余按原样入库。
2026-10-08 08:13:02 +08:00

119 lines
4.6 KiB
Python

# -*- coding: utf-8 -*-
"""取证脚本(第 2 棒):Postiz ↔ PostSider 文件树差异量化。
输入:api/postiz.tree.json 与 api/postsider.tree.json(第 1 棒已拉,本脚本只读复用)。
输出:api/fork/fork-diff.json —— 逐路径比对结果 + 子树级统计,供分析文档引用(可复算)。
口径:GitHub tree API 的 blob `sha`(内容哈希)。
sha 相同 ⇒ 两个文件**逐字节相同**;sha 不同 ⇒ 内容不同(可能只是换牌字符串,也可能是真改动)。
⛔ 本脚本**不判**"改动是好是坏",只给计数与清单;⛔ 也不做"改动行数"——tree API 不给行数。
"""
import json, os, sys, time, collections
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
HERE = os.path.dirname(os.path.abspath(__file__))
API = os.path.join(HERE, "api")
OUT = os.path.join(API, "fork")
os.makedirs(OUT, exist_ok=True)
def blobs(name):
with open(os.path.join(API, name), encoding="utf-8") as f:
d = json.load(f)
return {e["path"]: e["sha"] for e in d["tree"] if e["type"] == "blob"}
P = blobs("postiz.tree.json")
S = blobs("postsider.tree.json")
common = set(P) & set(S)
identical = sorted(k for k in common if P[k] == S[k])
content_diff = sorted(k for k in common if P[k] != S[k])
only_postiz = sorted(set(P) - set(S))
only_postsider = sorted(set(S) - set(P))
def subtree(pref):
p = {k for k in P if k.startswith(pref)}
s = {k for k in S if k.startswith(pref)}
b = p & s
return {
"pref": pref,
"postiz": len(p),
"postsider": len(s),
"common": len(b),
"byte_identical": len([k for k in b if P[k] == S[k]]),
"content_diff": len([k for k in b if P[k] != S[k]]),
"only_postiz": len(p - s),
"only_postsider": len(s - p),
}
PREF = [
"apps/", "apps/backend/", "apps/frontend/", "apps/orchestrator/", "apps/sdk/",
"apps/commands/", "apps/extension/", "apps/mcp/",
"libraries/", "libraries/nestjs-libraries/src/integrations/",
"libraries/nestjs-libraries/src/database/prisma/", "libraries/helpers/src/",
"libraries/react-shared-libraries/",
]
social = "libraries/nestjs-libraries/src/integrations/social/"
def providers(m):
return sorted(k[len(social):] for k in m if k.startswith(social) and k.endswith(".provider.ts"))
def cnt(m, pred):
return sum(1 for k in m if pred(k))
res = {
"generated_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
"source_trees": {
"postiz": {"branch": "main", "tree_sha": "3128e870e653b1763c47392784df20f5e58cc087", "blobs": len(P)},
"postsider": {"branch": "main", "tree_sha": "d5ecaf019acf2b8b38da1768c8b27bfd376de8eb", "blobs": len(S)},
},
"totals": {
"postiz_blobs": len(P),
"postsider_blobs": len(S),
"common_paths": len(common),
"byte_identical": len(identical),
"content_diff": len(content_diff),
"only_postiz": len(only_postiz),
"only_postsider": len(only_postsider),
},
"subtrees": [subtree(p) for p in PREF],
"signals": {
"test_files": {
"postiz_spec_ts": cnt(P, lambda k: k.endswith((".spec.ts", ".test.ts"))),
"postsider_spec_ts": cnt(S, lambda k: k.endswith((".spec.ts", ".test.ts"))),
},
"root_jest_configs": {
"postiz": cnt(P, lambda k: k.startswith("jest.") and "/" not in k),
"postsider": cnt(S, lambda k: k.startswith("jest.") and "/" not in k),
},
"social_providers": {
"postiz": providers(P),
"postsider": providers(S),
"only_postsider": sorted(set(providers(S)) - set(providers(P))),
"only_postiz": sorted(set(providers(P)) - set(providers(S))),
},
"prisma_migrations": {
"postiz_count": cnt(P, lambda k: "/database/prisma/migrations/" in k),
"postsider_count": cnt(S, lambda k: "/database/prisma/migrations/" in k),
},
},
"paths": {
"only_postsider_top_dirs": sorted({k.split("/")[0] for k in only_postsider}),
"only_postiz_top_dirs": sorted({k.split("/")[0] for k in only_postiz}),
"only_postsider_root_files": [k for k in only_postsider if "/" not in k],
"content_diff_by_top": dict(collections.Counter(k.split("/")[0] for k in content_diff).most_common()),
},
}
with open(os.path.join(OUT, "fork-diff.json"), "w", encoding="utf-8") as f:
json.dump(res, f, ensure_ascii=False, indent=1)
print(json.dumps(res["totals"], ensure_ascii=False))
print(json.dumps(res["signals"]["test_files"], ensure_ascii=False))
print("DONE -> " + os.path.join(OUT, "fork-diff.json"))