@topmindspace/topmind-writing-skills 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +931 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +124 -0
  4. package/README.md +124 -0
  5. package/bin/topmind-writing-skills.js +388 -0
  6. package/package.json +56 -0
  7. package/topmind-briefs/CHANGELOG.md +22 -0
  8. package/topmind-briefs/README.en.md +41 -0
  9. package/topmind-briefs/README.md +37 -0
  10. package/topmind-briefs/SKILL.md +101 -0
  11. package/topmind-briefs/dist/topmind-briefs.manifest.json +21 -0
  12. package/topmind-briefs/dist/topmind-briefs.zip +0 -0
  13. package/topmind-briefs/package-lock.json +13 -0
  14. package/topmind-briefs/package.json +1 -0
  15. package/topmind-briefs/references/examples.md +131 -0
  16. package/topmind-briefs/references/platform-specs.md +63 -0
  17. package/topmind-briefs/references/style-guide.md +60 -0
  18. package/topmind-briefs/scripts/audit_css.py +39 -0
  19. package/topmind-briefs/scripts/audit_docs.py +80 -0
  20. package/topmind-briefs/scripts/audit_skill.py +60 -0
  21. package/topmind-briefs/scripts/audit_styles.py +54 -0
  22. package/topmind-briefs/scripts/negative_tests.py +88 -0
  23. package/topmind-briefs/scripts/package_skill.py +188 -0
  24. package/topmind-cover/README.en.md +54 -0
  25. package/topmind-cover/README.md +54 -0
  26. package/topmind-cover/SKILL.md +134 -0
  27. package/topmind-cover/assets/examples/README.md +34 -0
  28. package/topmind-cover/assets/examples/bg-blur-alt.png +0 -0
  29. package/topmind-cover/assets/examples/bg-blur-wechat.png +0 -0
  30. package/topmind-cover/assets/examples/bg-blur.png +0 -0
  31. package/topmind-cover/assets/examples/big-type-wechat.png +0 -0
  32. package/topmind-cover/assets/examples/big-type.png +0 -0
  33. package/topmind-cover/assets/examples/brand-launch-wechat.png +0 -0
  34. package/topmind-cover/assets/examples/brand-launch.png +0 -0
  35. package/topmind-cover/assets/examples/gan-huo-wechat.png +0 -0
  36. package/topmind-cover/assets/examples/gan-huo.png +0 -0
  37. package/topmind-cover/assets/examples/ip-fun-wechat.png +0 -0
  38. package/topmind-cover/assets/examples/ip-fun.png +0 -0
  39. package/topmind-cover/assets/examples/magazine-wechat.png +0 -0
  40. package/topmind-cover/assets/examples/magazine.png +0 -0
  41. package/topmind-cover/assets/examples/minimal-wechat.png +0 -0
  42. package/topmind-cover/assets/examples/minimal.png +0 -0
  43. package/topmind-cover/assets/examples/news-flash-wechat.png +0 -0
  44. package/topmind-cover/assets/examples/news-flash.png +0 -0
  45. package/topmind-cover/assets/examples/overview.png +0 -0
  46. package/topmind-cover/assets/examples/paper-collage-alt.png +0 -0
  47. package/topmind-cover/assets/examples/paper-collage-wechat.png +0 -0
  48. package/topmind-cover/assets/examples/paper-collage.png +0 -0
  49. package/topmind-cover/assets/examples/tutorial-steps-wechat.png +0 -0
  50. package/topmind-cover/assets/examples/tutorial-steps.png +0 -0
  51. package/topmind-cover/assets/examples/white-clean-alt-wechat.png +0 -0
  52. package/topmind-cover/assets/examples/white-clean-alt.png +0 -0
  53. package/topmind-cover/assets/examples/white-clean-wechat.png +0 -0
  54. package/topmind-cover/assets/examples/white-clean.png +0 -0
  55. package/topmind-cover/package-lock.json +16 -0
  56. package/topmind-cover/package.json +16 -0
  57. package/topmind-cover/references/cover-prompts.md +436 -0
  58. package/topmind-cover/references/cover-styles.md +496 -0
  59. package/topmind-cover/references/impact-language.md +94 -0
  60. package/topmind-cover/references/ref-white-clean-1.png +0 -0
  61. package/topmind-cover/references/ref-white-clean-2.png +0 -0
  62. package/topmind-cover/scripts/audit_css.py +39 -0
  63. package/topmind-cover/scripts/audit_docs.py +80 -0
  64. package/topmind-cover/scripts/audit_skill.py +60 -0
  65. package/topmind-cover/scripts/audit_styles.py +54 -0
  66. package/topmind-cover/scripts/crop-cover.py +74 -0
  67. package/topmind-cover/scripts/negative_tests.py +71 -0
  68. package/topmind-cover/scripts/package_skill.py +188 -0
  69. package/topmind-wechat-post/README.en.md +111 -0
  70. package/topmind-wechat-post/README.md +111 -0
  71. package/topmind-wechat-post/SKILL.md +251 -0
  72. package/topmind-wechat-post/assets/themes/amber-review.json +35 -0
  73. package/topmind-wechat-post/assets/themes/graphite.json +35 -0
  74. package/topmind-wechat-post/assets/themes/minimal-ink.json +35 -0
  75. package/topmind-wechat-post/assets/themes/newsprint.json +35 -0
  76. package/topmind-wechat-post/assets/themes/tech-blue.json +35 -0
  77. package/topmind-wechat-post/package-lock.json +16 -0
  78. package/topmind-wechat-post/package.json +16 -0
  79. package/topmind-wechat-post/references/design.md +50 -0
  80. package/topmind-wechat-post/references/element-spec.md +163 -0
  81. package/topmind-wechat-post/references/known-pits.md +85 -0
  82. package/topmind-wechat-post/references/theme-map.md +139 -0
  83. package/topmind-wechat-post/references/typography-rules.md +62 -0
  84. package/topmind-wechat-post/references/wechat-constraints.md +111 -0
  85. package/topmind-wechat-post/references/workflow.md +102 -0
  86. package/topmind-wechat-post/references/writing-quality.md +105 -0
  87. package/topmind-wechat-post/scripts/audit_css.py +39 -0
  88. package/topmind-wechat-post/scripts/audit_docs.py +80 -0
  89. package/topmind-wechat-post/scripts/audit_skill.py +60 -0
  90. package/topmind-wechat-post/scripts/audit_styles.py +54 -0
  91. package/topmind-wechat-post/scripts/lint-wechat.py +446 -0
  92. package/topmind-wechat-post/scripts/md2wechat.py +1792 -0
  93. package/topmind-wechat-post/scripts/negative_tests.py +171 -0
  94. package/topmind-wechat-post/scripts/new-article.py +183 -0
  95. package/topmind-wechat-post/scripts/package_skill.py +188 -0
  96. package/topmind-wechat-post/scripts/push-to-topstream.py +226 -0
  97. package/topmind-wechat-post/scripts/scan_ai_flavor.py +464 -0
  98. package/topmind-wechat-post/scripts/sync-mapping.py +220 -0
  99. package/topmind-wechat-post/scripts/sync-status.py +187 -0
  100. package/topmind-x-article/README.en.md +109 -0
  101. package/topmind-x-article/README.md +104 -0
  102. package/topmind-x-article/SKILL.md +156 -0
  103. package/topmind-x-article/package-lock.json +16 -0
  104. package/topmind-x-article/package.json +16 -0
  105. package/topmind-x-article/references/publish-checklist.md +31 -0
  106. package/topmind-x-article/references/x-format.md +27 -0
  107. package/topmind-x-article/references/x-html-format.md +85 -0
  108. package/topmind-x-article/scripts/audit_css.py +39 -0
  109. package/topmind-x-article/scripts/audit_docs.py +80 -0
  110. package/topmind-x-article/scripts/audit_skill.py +60 -0
  111. package/topmind-x-article/scripts/audit_styles.py +54 -0
  112. package/topmind-x-article/scripts/md2x-html.py +522 -0
  113. package/topmind-x-article/scripts/md2x.py +323 -0
  114. package/topmind-x-article/scripts/negative_tests.py +91 -0
  115. package/topmind-x-article/scripts/package_skill.py +188 -0
@@ -0,0 +1,85 @@
1
+ # x-html-format.md · md2x-html.py 转换规则
2
+
3
+ `scripts/md2x-html.py` 把 Markdown 原稿转成**单文件 HTML**(内联样式 + 图片 base64 内嵌,
4
+ 无外部依赖),浏览器打开即用。对标 `md2wechat` 的公众号体验:页顶「一键复制全文」。
5
+
6
+ ## X 文章编辑器语法(调研结论)
7
+
8
+ 以 X 发文工具实测为准(x-md-to-article 等,2026-09-29 调研):
9
+
10
+ | 原稿元素 | HTML 输出 | 粘贴进 X 后的表现 |
11
+ |---|---|---|
12
+ | `#`(首个) | `h1.no-paste`(页面展示) | **不进剪贴板**:手动填入 X 标题栏 |
13
+ | `##` / `###` | `h2` / `h3` | X 原生"标题 / 副标题"两级 |
14
+ | 段落(`**粗体**`、`` `代码` ``、裸链接) | `p`(strong/code/a 保留) | 原生富文本;行内 code 变纯文本;链接保留 |
15
+ | `- ` / `1. ` | `ul` / `ol` | 原生列表 |
16
+ | `> ` | `blockquote` | 原生引用块 |
17
+ | ``` 围栏(直出提示词) | `div.prompt > blockquote > code` + 「复制提示词」按钮 | 原生引用块。**粘贴流到此为止**:X 不识别粘贴的 `<pre>` 为代码块;读者侧一键复制只能来自 X 原生代码块(Insert → Code 手动建),见下节「提示词的复制链路(双通道)」 |
18
+ | `---` | `hr` | 可能被丢弃 → 发布时手动 Insert → 分割线 |
19
+ | GFM 表格 | `ul.xtable`(表头加粗首项) | **X 粘贴会丢弃 `<table>`**,转列表保内容;如需原生表格,走 X 的 Insert → 表格手动建 |
20
+ | `[图N]` 独占一行 | `<figure>`:内嵌 data-URI 图片 + `[图N]` 编号 + 「下载图片」按钮 | 图片随粘贴带入不稳定,保底按编号下载上传 |
21
+ | 封面 `--cover` | 页顶预览 + 下载(`.no-paste`) | 始终在 X 编辑器独立封面入口单独上传 |
22
+
23
+ 「复制全文」前 JS 自动剔除所有 `<button>` 与 `.no-paste` 块(标题、封面预览),
24
+ 剪贴板里只剩 X 可识别的语义标签:`h2/h3/p/ul/ol/blockquote/a/hr/img`。
25
+ 工具栏三步话术:**① 标题填入 X 标题栏 → ② 一键复制全文 → ③ 粘贴到 X 正文**。
26
+
27
+ ## 提示词的复制链路(双通道)
28
+
29
+ 核实结论(2026-09-29,两处独立实测一致):
30
+
31
+ - X 原生代码块(Insert → Code 插入)**带 native copy button**,读者侧可一键复制
32
+ (jpw-x-articles 真机验证原文:"Code content and native copy buttons persist")。
33
+ - 围栏代码**只能**走 Insert → Code 对话框,"never relies on clipboard HTML round-tripping";
34
+ 粘贴 HTML 会丢弃 `<pre>`(x-md-to-article 实测笔记,2026-09-07)。
35
+
36
+ 因此提示词的"复制"分两段:
37
+
38
+ - **通道 A(作者侧,发布前)**:`X长文.html` 里每个提示词块右上角「复制提示词」按钮,
39
+ 一键复制该块纯文本。这是你粘贴发布时取用提示词文本的入口。
40
+ - **通道 B(读者侧,发布后)**:X 编辑器里把提示词引用块手动转为原生代码块,
41
+ 读者才能一键复制。操作(每块约 10 秒):
42
+ 1. 在 HTML 里点该块「复制提示词」(文本已进剪贴板);
43
+ 2. X 编辑器对应位置 Insert → Code,新建原生代码块,粘贴;
44
+ 3. 删除原来的引用块(避免正文重复)。
45
+ 提示词密集型文章(如 N 条直出提示词)建议逐块转换;纯展示、无需读者取用的代码可跳过。
46
+
47
+ `X发布稿.txt` 里围栏已去标记、内容保留为纯文本,同样可作为通道 B 的文本来源。
48
+
49
+ ## 图片约定
50
+
51
+ - 图片来源:有公众号稿可派生时一律用 `--images-from 公众号稿.md`,
52
+ 从源 md 的 `![]()` 按文档顺序自动取图;**禁止手工拼 `--images` 顺序**
53
+ (文件名数字排序 ≠ 文档顺序,2026-10-01 九月全景曾因此从第六节起整段错位)。
54
+ 无源可派生的纯 X 原创稿才允许 `--images` 手工传参,且须逐张核对。
55
+ 脚本内置校验:`[图N]` 须连续编号且数量 = 图片数,否则报错退出。
56
+ - `--cover` 只做页顶预览 + 下载,**不占 `[图N]` 编号**
57
+ - 配图建议先压缩到宽 900px 左右的 JPEG(单张 <100KB),11 张图总 HTML 约 800KB,
58
+ 直接双击打开不卡
59
+ - 下载按钮用 JS 取同图 `src` 触发下载,data-URI 在 HTML 里只存一份(体积减半)
60
+
61
+ ## 点击放大(lightbox)
62
+
63
+ - 正文每张配图、封面预览图均可点击,弹出全屏 lightbox 放大查看原图
64
+ - lightbox 内提供「新标签页打开原图」(Blob URL,绕开浏览器对 data: URL 顶层导航的限制)、
65
+ 「下载原图」按钮;**刻意不拦截右键菜单**,放大后可右键图片另存,或直接截图
66
+ - lightbox 位于 `#article` 之外,「一键复制全文」不会把它带入剪贴板
67
+
68
+ ## 复制行为
69
+
70
+ - 「一键复制全文」:`ClipboardItem` 同时写 `text/html` + `text/plain`,
71
+ 富文本失败时自动降级纯文本
72
+ - 「复制提示词」:`innerText`(HTML 实体自动解码,拿到的是干净纯文本)
73
+ - 「下载图片/下载封面」:`a[download]` + data-URI,保底链路
74
+
75
+ ## 已知限制(写进发布话术,不要过度承诺)
76
+
77
+ - 文本 + 格式一键复制可靠(富文本剪贴板,多家 X 发文技能同款做法)
78
+ - 图片 data-URI 能否随粘贴带入 X 文章编辑器**不稳定**:
79
+ 有成功案例,但更多实战采用"HTML 粘贴文本 + 图片按编号上传"。
80
+ 话术固定为:"能带入则带入,带不入则按编号下载上传"
81
+ - 封面始终在 X 编辑器单独上传(X 有独立封面入口),HTML 里只预览
82
+ - 分割线、代码块、表格三类 X 原生块**只能走 Insert 菜单**,粘贴流不承诺还原;
83
+ 本链路已做最大兼容(引用块/列表/hr),剩余手动补。
84
+ 其中代码块值得手动补:它是 X 侧唯一带读者一键复制(native copy button)的载体,
85
+ 提示词类内容建议走「提示词的复制链路」通道 B 逐块转换
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """CSS 审计:本组技能无 CSS 资产;若未来加入 .css 文件,则做括号配平基础检查。"""
4
+ from __future__ import annotations
5
+
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ try:
10
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
11
+ except Exception:
12
+ pass
13
+
14
+ ROOT = Path(__file__).resolve().parent.parent
15
+ NAME = ROOT.name
16
+ errors: list[str] = []
17
+
18
+
19
+ def main() -> None:
20
+ print(f"[audit_css] {NAME}")
21
+ css_files = [p for p in ROOT.rglob("*.css") if ".git" not in p.parts and "dist" not in p.parts]
22
+ if not css_files:
23
+ print(" ✓ 无 CSS 资产,跳过")
24
+ print(f"\n[audit_css] {NAME}: 通过")
25
+ return
26
+ for css in css_files:
27
+ text = css.read_text(encoding="utf-8")
28
+ balanced = text.count("{") == text.count("}")
29
+ print((" ✓ " if balanced else " ✗ ") + f"{css.name} 括号配平")
30
+ if not balanced:
31
+ errors.append(css.name)
32
+ if errors:
33
+ print(f"\n[audit_css] {NAME}: {len(errors)} 项失败")
34
+ sys.exit(1)
35
+ print(f"\n[audit_css] {NAME}: 全部通过")
36
+
37
+
38
+ if __name__ == "__main__":
39
+ main()
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """文档审计:references 完整性与内部链接可达性(任一不过 → 退出码 1)。"""
4
+ from __future__ import annotations
5
+
6
+ import re
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ try:
11
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
12
+ except Exception:
13
+ pass
14
+
15
+ ROOT = Path(__file__).resolve().parent.parent
16
+ NAME = ROOT.name
17
+ errors: list[str] = []
18
+
19
+
20
+ def check(cond: bool, msg: str) -> None:
21
+ print((" ✓ " if cond else " ✗ ") + msg)
22
+ if not cond:
23
+ errors.append(msg)
24
+
25
+
26
+ def main() -> None:
27
+ print(f"[audit_docs] {NAME}")
28
+ md_files = [p for p in ROOT.rglob("*.md") if ".git" not in p.parts and "dist" not in p.parts]
29
+ check(len(md_files) >= 2, f"md 文档 ≥2(实 {len(md_files)},含 SKILL.md/README.md)")
30
+
31
+ refdir = ROOT / "references"
32
+ if refdir.is_dir():
33
+ refs = sorted(refdir.glob("*.md"))
34
+ check(len(refs) >= 1, f"references/*.md ≥1(实 {len(refs)})")
35
+ for md in refs:
36
+ check(md.stat().st_size > 0, f"references/{md.name} 非空")
37
+
38
+ for md in md_files:
39
+ text = md.read_text(encoding="utf-8")
40
+ rel = md.relative_to(ROOT).as_posix()
41
+ for link in set(re.findall(r"\]\(([^)]+)\)", text)):
42
+ if link.startswith(("http://", "https://", "#", "mailto:")):
43
+ continue
44
+ target = (md.parent / link.split("#")[0]).resolve()
45
+ if link and not target.exists():
46
+ # 允许指向脚本/资源等非 md 文件的相对链接缺失检查放宽到 md
47
+ if target.suffix == ".md":
48
+ check(False, f"{rel} 悬空链接: {link}")
49
+ check(True, "内部 md 链接可达")
50
+
51
+ # 反引号代码引用中的路径可达(审计 轻微-15 门禁盲区:之前只查 [text](path) 形式,
52
+ # `references/pan-style.md` 这类反引号引用曾漏网)。抓取反引号内的 references/*.md
53
+ # 与 scripts/* 路径并校验存在;glob/占位符写法(* ? < > |)跳过。
54
+ backtick_path_re = re.compile(r"(references|scripts)/([\w.\-/]+)")
55
+ line_suffix_re = re.compile(r":\d+(?:-\d+)?$")
56
+ backtick_bad = 0
57
+ for md in md_files:
58
+ text = md.read_text(encoding="utf-8")
59
+ rel = md.relative_to(ROOT).as_posix()
60
+ for span in set(re.findall(r"`([^`]+)`", text)):
61
+ for kind, p in backtick_path_re.findall(span):
62
+ p = line_suffix_re.sub("", p)
63
+ if not p or any(c in p for c in "*?<>|"):
64
+ continue
65
+ target = md.parent / kind / p
66
+ if not target.exists():
67
+ target = ROOT / kind / p
68
+ if not target.exists():
69
+ backtick_bad += 1
70
+ check(False, f"{rel} 反引号引用不可达: `{kind}/{p}`")
71
+ check(backtick_bad == 0, f"反引号内 references/scripts 引用可达(扫描 {len(md_files)} 个 md 文件)")
72
+
73
+ if errors:
74
+ print(f"\n[audit_docs] {NAME}: {len(errors)} 项失败")
75
+ sys.exit(1)
76
+ print(f"\n[audit_docs] {NAME}: 全部通过")
77
+
78
+
79
+ if __name__ == "__main__":
80
+ main()
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """技能入口审计:SKILL.md 结构完整性(任一不过 → 退出码 1)。"""
4
+ from __future__ import annotations
5
+
6
+ import re
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ try:
11
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
12
+ except Exception:
13
+ pass
14
+
15
+ ROOT = Path(__file__).resolve().parent.parent
16
+ NAME = ROOT.name
17
+ errors: list[str] = []
18
+
19
+
20
+ def check(cond: bool, msg: str) -> None:
21
+ print((" ✓ " if cond else " ✗ ") + msg)
22
+ if not cond:
23
+ errors.append(msg)
24
+
25
+
26
+ def main() -> None:
27
+ print(f"[audit_skill] {NAME}")
28
+ text = (ROOT / "SKILL.md").read_text(encoding="utf-8")
29
+
30
+ m = re.search(r"^triggers:\s*\n((?: - .+\n?)+)", text, re.M)
31
+ triggers = re.findall(r"^ - (.+)$", m.group(1), re.M) if m else []
32
+ check(len(triggers) >= 1, f"triggers ≥1(实 {len(triggers)})")
33
+
34
+ check("When NOT to use" in text or "何时不用" in text, "含 When NOT to use / 何时不用")
35
+
36
+ has_workflow = bool(re.search(r"工作流|Workflow|用法|Usage", text))
37
+ check(has_workflow, "含工作流/用法说明")
38
+
39
+ fm = re.match(r"^---\r?\n([\s\S]*?)\r?\n---", text)
40
+ desc = ""
41
+ if fm:
42
+ dm = re.search(r'^description:\s*"([^"]*)"\s*$', fm.group(1), re.M)
43
+ if dm:
44
+ desc = dm.group(1).strip()
45
+ else:
46
+ dm = re.search(r"^description:\s*>\-\s*\n((?:[ \t]+.*\n?)+)", fm.group(1), re.M)
47
+ if dm:
48
+ desc = " ".join(l.strip() for l in dm.group(1).splitlines())
49
+ check("Use when" in desc or "用" in desc, "description 含使用场景")
50
+ check("Do NOT use" in desc or "不用" in desc, "description 含不适用场景")
51
+ check(len(desc) <= 1024, f"description ≤1024(实 {len(desc)})")
52
+
53
+ if errors:
54
+ print(f"\n[audit_skill] {NAME}: {len(errors)} 项失败")
55
+ sys.exit(1)
56
+ print(f"\n[audit_skill] {NAME}: 全部通过")
57
+
58
+
59
+ if __name__ == "__main__":
60
+ main()
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """样式资产审计:主题/风格资源有效性(任一不过 → 退出码 1)。
4
+
5
+ 本组技能无 CSS 样式表:若存在 assets/themes/*.json 则校验 JSON 可解析;
6
+ references 风格文档非空(已由 audit_docs 覆盖,此处做存在性断言)。
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ try:
15
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
16
+ except Exception:
17
+ pass
18
+
19
+ ROOT = Path(__file__).resolve().parent.parent
20
+ NAME = ROOT.name
21
+ errors: list[str] = []
22
+
23
+
24
+ def check(cond: bool, msg: str) -> None:
25
+ print((" ✓ " if cond else " ✗ ") + msg)
26
+ if not cond:
27
+ errors.append(msg)
28
+
29
+
30
+ def main() -> None:
31
+ print(f"[audit_styles] {NAME}")
32
+ themes = sorted((ROOT / "assets" / "themes").glob("*.json")) if (ROOT / "assets" / "themes").is_dir() else []
33
+ if themes:
34
+ for t in themes:
35
+ try:
36
+ json.loads(t.read_text(encoding="utf-8"))
37
+ check(True, f"assets/themes/{t.name} JSON 可解析")
38
+ except (OSError, json.JSONDecodeError) as e:
39
+ check(False, f"assets/themes/{t.name} 解析失败: {e}")
40
+ else:
41
+ check(True, "无主题 JSON 资产(本技能不依赖主题文件)")
42
+
43
+ refdir = ROOT / "references"
44
+ has_style_doc = refdir.is_dir() and any(refdir.glob("*.md"))
45
+ check(has_style_doc, "references 风格/格式文档存在")
46
+
47
+ if errors:
48
+ print(f"\n[audit_styles] {NAME}: {len(errors)} 项失败")
49
+ sys.exit(1)
50
+ print(f"\n[audit_styles] {NAME}: 全部通过")
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()