@topmindspace/tms-skills 0.1.19 → 0.2.0

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 (59) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.en.md +5 -2
  3. package/README.md +4 -1
  4. package/package.json +5 -2
  5. package/topmind-cover/README.md +40 -0
  6. package/topmind-cover/SKILL.md +66 -0
  7. package/topmind-cover/package-lock.json +16 -0
  8. package/topmind-cover/package.json +16 -0
  9. package/topmind-cover/references/cover-styles.md +40 -0
  10. package/topmind-cover/scripts/audit_css.py +39 -0
  11. package/topmind-cover/scripts/audit_docs.py +58 -0
  12. package/topmind-cover/scripts/audit_skill.py +60 -0
  13. package/topmind-cover/scripts/audit_styles.py +54 -0
  14. package/topmind-cover/scripts/crop-cover.py +69 -0
  15. package/topmind-cover/scripts/negative_tests.py +71 -0
  16. package/topmind-cover/scripts/package_skill.py +188 -0
  17. package/topmind-wechat-post/README.md +49 -0
  18. package/topmind-wechat-post/SKILL.md +208 -0
  19. package/topmind-wechat-post/assets/themes/amber-review.json +35 -0
  20. package/topmind-wechat-post/assets/themes/graphite.json +35 -0
  21. package/topmind-wechat-post/assets/themes/minimal-ink.json +35 -0
  22. package/topmind-wechat-post/assets/themes/newsprint.json +35 -0
  23. package/topmind-wechat-post/assets/themes/tech-blue.json +35 -0
  24. package/topmind-wechat-post/package-lock.json +16 -0
  25. package/topmind-wechat-post/package.json +16 -0
  26. package/topmind-wechat-post/references/design.md +50 -0
  27. package/topmind-wechat-post/references/element-spec.md +162 -0
  28. package/topmind-wechat-post/references/known-pits.md +86 -0
  29. package/topmind-wechat-post/references/theme-map.md +139 -0
  30. package/topmind-wechat-post/references/typography-rules.md +62 -0
  31. package/topmind-wechat-post/references/wechat-constraints.md +106 -0
  32. package/topmind-wechat-post/references/workflow.md +102 -0
  33. package/topmind-wechat-post/references/writing-quality.md +129 -0
  34. package/topmind-wechat-post/scripts/audit_css.py +39 -0
  35. package/topmind-wechat-post/scripts/audit_docs.py +58 -0
  36. package/topmind-wechat-post/scripts/audit_skill.py +60 -0
  37. package/topmind-wechat-post/scripts/audit_styles.py +54 -0
  38. package/topmind-wechat-post/scripts/lint-wechat.py +446 -0
  39. package/topmind-wechat-post/scripts/md2wechat.py +1602 -0
  40. package/topmind-wechat-post/scripts/negative_tests.py +65 -0
  41. package/topmind-wechat-post/scripts/new-article.py +183 -0
  42. package/topmind-wechat-post/scripts/package_skill.py +188 -0
  43. package/topmind-wechat-post/scripts/push-to-topstream.py +226 -0
  44. package/topmind-wechat-post/scripts/scan_ai_flavor.py +464 -0
  45. package/topmind-wechat-post/scripts/sync-mapping.py +220 -0
  46. package/topmind-wechat-post/scripts/sync-status.py +187 -0
  47. package/topmind-x-article/README.md +40 -0
  48. package/topmind-x-article/SKILL.md +62 -0
  49. package/topmind-x-article/package-lock.json +16 -0
  50. package/topmind-x-article/package.json +16 -0
  51. package/topmind-x-article/references/publish-checklist.md +24 -0
  52. package/topmind-x-article/references/x-format.md +24 -0
  53. package/topmind-x-article/scripts/audit_css.py +39 -0
  54. package/topmind-x-article/scripts/audit_docs.py +58 -0
  55. package/topmind-x-article/scripts/audit_skill.py +60 -0
  56. package/topmind-x-article/scripts/audit_styles.py +54 -0
  57. package/topmind-x-article/scripts/md2x.py +121 -0
  58. package/topmind-x-article/scripts/negative_tests.py +74 -0
  59. package/topmind-x-article/scripts/package_skill.py +188 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## [0.2.0] - 2026-09-29
2
+
3
+ ### 新增三个写作/配图技能
4
+
5
+ - **topmind-wechat-post `0.1.0`**:公众号文章全生命周期技能(由 topmind-wechat 适配改名)。
6
+ 交付包 / 审校改写 / 质量三关(事实·逻辑·去 AI 味 ≥85)/ 状态同步 / 微信内联排版(`md2wechat --embed-images`)/ 发布清单。
7
+ 适配点:frontmatter 去 topmind-pack 专有键(`degradation`/`action_category`/`entrypoint`),
8
+ 跨 skill 引用改为通用表述,`writing-quality.md` 改用技能自带 `scan_ai_flavor.py`
9
+ (原引用本机 `~/.workbuddy` 路径),`md2wechat.py` 缺失输入改干净报错(原 Traceback)。
10
+ - **topmind-x-article `0.1.0`**:X 长文一键发布。`md2x.py` 把 Markdown 转可直接粘贴进
11
+ X Article 编辑器的纯文本(标题→纯文本行、加粗/斜体去标记、链接→`文字(url)`、
12
+ 图片→`[图N]`+文末配图清单、表格→"项:值"列表);`references/publish-checklist.md`
13
+ 沉淀实战经验(首评置顶补信息、图序核对、发布后抓回核对)。缺失输入干净报错。
14
+ - **topmind-cover `0.1.0`**:文章封面配图,X / 公众号共用。设计铁律(震撼·醒目·主题突出:
15
+ 一图一主题、大标题 ≤10 字、四周 8% 留白)+ 5 套风格模板;
16
+ `crop-cover.py` 从 16:9 主图中央裁出公众号 900×383 版(X 用 1200×675),落盘命名规范。
17
+ 缺失输入 / 非图片干净报错。
18
+ - 每个新技能带轻量 CI 门禁:`package_skill.py --check`(frontmatter/版本/引用完整性/禁用文件)、
19
+ `audit_skill.py` / `audit_docs.py` / `audit_styles.py` / `audit_css.py`、`negative_tests.py`
20
+ (异常输入不崩溃)。`ci_privacy_scan.py` 全仓通过。
21
+
1
22
  ## [0.1.19] - 2026-09-28
2
23
 
3
24
  ### 排版修复 · 大纲篇章化 · 参考资料去假 · 图标真导出 · PPTX 高保真增强
package/README.en.md CHANGED
@@ -77,8 +77,11 @@ See [top-ppt-html/README.md](./top-ppt-html/README.md) and [SKILL.md](./top-ppt-
77
77
  | Skill | Version | What it does |
78
78
  |-------|---------|--------------|
79
79
  | [`top-ppt-html`](./top-ppt-html/) | **0.1.19** | Formal business decks: paginated HTML + editable 16:9 PPTX; dual delivery · MD3-inspired density; 3 modes × 9 styles; strict 0/0 |
80
+ | [`topmind-wechat-post`](./topmind-wechat-post/) | **0.1.0** | WeChat article lifecycle: package, review, 3 quality gates, inline typography & publish checklist |
81
+ | [`topmind-x-article`](./topmind-x-article/) | **0.1.0** | X long-form one-click publish: Markdown → paste-ready plain text + cover + checklist |
82
+ | [`topmind-cover`](./topmind-cover/) | **0.1.0** | Cover art for X / WeChat: striking, theme-focused; size specs + style templates + crop tooling |
80
83
 
81
- Installer [`@topmindspace/tms-skills`](https://www.npmjs.com/package/@topmindspace/tms-skills) is also **0.1.19** (whole-repo same-tag releases).
84
+ Installer [`@topmindspace/tms-skills`](https://www.npmjs.com/package/@topmindspace/tms-skills) is **0.2.0** (whole-repo same-tag releases).
82
85
 
83
86
  ## Install
84
87
 
@@ -88,7 +91,7 @@ Installer [`@topmindspace/tms-skills`](https://www.npmjs.com/package/@topmindspa
88
91
  npx @topmindspace/tms-skills list
89
92
  npx @topmindspace/tms-skills install top-ppt-html
90
93
  npx @topmindspace/tms-skills install top-ppt-html --to ./.claude/skills
91
- npx @topmindspace/tms-skills@0.1.19 install top-ppt-html # pin
94
+ npx @topmindspace/tms-skills@0.2.0 install top-ppt-html # pin
92
95
  ```
93
96
 
94
97
  ```bash
package/README.md CHANGED
@@ -75,8 +75,11 @@
75
75
  | 技能 | 版本 | 做什么 |
76
76
  |------|------|--------|
77
77
  | [`top-ppt-html`](./top-ppt-html/) | **0.1.19** | 正式商务演示:HTML 可翻页 + 16:9 可编辑 PPTX;双交付 · MD3 密度克制;三模式 × 九风格;strict 0/0 |
78
+ | [`topmind-wechat-post`](./topmind-wechat-post/) | **0.1.0** | 公众号文章全生命周期:交付包、审校改写、质量三关、微信内联排版与发布清单 |
79
+ | [`topmind-x-article`](./topmind-x-article/) | **0.1.0** | X 长文一键发布:Markdown 原稿转可直接粘贴的纯文本 + 封面图 + 发布清单 |
80
+ | [`topmind-cover`](./topmind-cover/) | **0.1.0** | 文章封面配图(X / 公众号共用):震撼醒目主题突出,尺寸规范 + 风格模板 + 裁剪落盘 |
78
81
 
79
- 安装器 [`@topmindspace/tms-skills`](https://www.npmjs.com/package/@topmindspace/tms-skills) 同为 **0.1.19**(整仓同 tag 发版)。
82
+ 安装器 [`@topmindspace/tms-skills`](https://www.npmjs.com/package/@topmindspace/tms-skills) 为 **0.2.0**(整仓同 tag 发版)。
80
83
 
81
84
  ## 安装
82
85
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topmindspace/tms-skills",
3
- "version": "0.1.19",
3
+ "version": "0.2.0",
4
4
  "description": "TopMindspace 智能体技能 monorepo — 通过 npm / GitHub npx 安装技能(top-ppt-html:HTML+PPT 双交付,正式商务演示文稿)。",
5
5
  "license": "MIT",
6
6
  "author": "TopMindspace",
@@ -30,7 +30,10 @@
30
30
  "README.en.md",
31
31
  "LICENSE",
32
32
  "CHANGELOG.md",
33
- "top-ppt-html"
33
+ "top-ppt-html",
34
+ "topmind-cover",
35
+ "topmind-wechat-post",
36
+ "topmind-x-article"
34
37
  ],
35
38
  "engines": {
36
39
  "node": ">=18"
@@ -0,0 +1,40 @@
1
+ # topmind-cover · 封面配图生成
2
+
3
+ 为 X 长文和公众号文章生成封面图的可复用技能。目标:**震撼、醒目、主题突出**。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npx @topmindspace/tms-skills install topmind-cover
9
+ ```
10
+
11
+ ## 用法
12
+
13
+ 1. 输入文章标题 + 3 个主题关键词 + 平台(x / wechat / both)
14
+ 2. 按 `references/cover-styles.md` 选风格模板组 prompt(标题字逐字写进 prompt)
15
+ 3. 用 agent 的图片生成能力出图(横构图 16:9,四周留白 8%)
16
+ 4. 肉眼检查:标题字逐字正确、主体完整、一眼能说出主题
17
+ 5. 裁剪落盘:
18
+
19
+ ```bash
20
+ python3 scripts/crop-cover.py <主图> --slug <slug> --out-dir <包>/images/
21
+ # 产出 00-封面.png (1200×675) + 00-封面-公众号.png (900×383,中央裁剪)
22
+ ```
23
+
24
+ ## 尺寸
25
+
26
+ | 平台 | 尺寸 | 比例 |
27
+ |------|------|------|
28
+ | X Article 封面 | 1200×675 | 16:9(主图) |
29
+ | 公众号封面大图 | 900×383 | 2.35:1(中央裁) |
30
+
31
+ ## 设计铁律
32
+
33
+ 一图一主题;主体占画面 40%+;大标题 ≤10 字高对比;忌元素堆砌、小字密排、多主体打架。
34
+
35
+ ## 开发
36
+
37
+ ```bash
38
+ python3 scripts/package_skill.py --check
39
+ python3 scripts/negative_tests.py
40
+ ```
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: topmind-cover
3
+ version: 1.0.0
4
+ description: "文章封面配图生成:X 长文与公众号共用。震撼、醒目、主题突出;平台尺寸规范、风格模板、命名落盘、成图检查一次配齐。Use when 封面、封面图、头图、配图、cover。Do NOT use for 正文插图、PPT/报告封面。"
5
+ action_category: write
6
+ triggers:
7
+ - 封面
8
+ - 封面图
9
+ - 头图
10
+ - 配图
11
+ - cover
12
+ triggers_cn:
13
+ - 公众号封面
14
+ - X 封面
15
+ - 文章封面
16
+ author: Muse
17
+ license: MIT
18
+ updated: 2026-09-29
19
+ ---
20
+
21
+ # topmind-cover · 封面配图生成
22
+
23
+ 为 X 长文和公众号文章生成封面图的可复用技能。目标:**震撼、醒目、主题突出**。
24
+
25
+ ```
26
+ 标题/主题 → 风格模板 → 生成 → 检查 → 裁剪双尺寸 → 落盘命名
27
+ ```
28
+
29
+ ## 尺寸表
30
+
31
+ | 平台 | 尺寸 | 比例 | 备注 |
32
+ |------|------|------|------|
33
+ | X Article 封面 | 1200×675 | 16:9 | 主尺寸,先按这个生成 |
34
+ | 公众号封面大图 | 900×383 | 2.35:1 | 从 16:9 中央裁剪 |
35
+
36
+ 生成 prompt 里写明"横构图 16:9";公众号版用 `scripts/crop-cover.py` 从主图中央裁出,不重新生成(保系列一致)。
37
+
38
+ ## 工作流
39
+
40
+ 1. **输入**:文章标题、3 个主题关键词、平台(`x` / `wechat` / `both`,默认 both)、风格(默认按题材自动选,见 `references/cover-styles.md`)。
41
+ 2. **组 prompt**:`[风格模板] + 主体描述 + 大标题字(≤10 字,直接用文章标题或其压缩版)+ 横构图16:9 + 四周留白8%`。标题字必须在 prompt 里逐字写明,防 AI 自造错别字。
42
+ 3. **生成**:`media.generate_image`,`name` 用 `<slug>-cover`。
43
+ 4. **检查**(必做,肉眼看一遍):
44
+ - 标题字逐字正确,无错别字、无多字少字
45
+ - 主体完整不被裁切,画面不杂乱
46
+ - 一眼能说出主题;说不出就重生成
47
+ 5. **裁剪落盘**:
48
+ ```bash
49
+ python3 scripts/crop-cover.py <主图> --slug <slug> --out-dir <包>/images/
50
+ ```
51
+ 产出 `00-封面.png`(1200×675,X/公众号共用主文件)与 `00-封面-公众号.png`(900×383)。
52
+ 公众号包规约:正文引用 `images/00-封面.png`,`图片上传清单.md` 登记两版。
53
+ 6. **系列感**:同一系列文章固定色系 + 版式语言,prompt 复用同一风格模板。
54
+
55
+ ## 设计铁律
56
+
57
+ - 一图一主题;主体占画面 40% 以上,居中或三分法
58
+ - 大标题 ≤10 字,高对比(暗底亮字 / 亮底深字)
59
+ - 四周 8% 安全边距,标题不贴边
60
+ - 忌:元素堆砌、小字密排、多主体打架、标题字错误
61
+
62
+ ## When NOT to use
63
+
64
+ - 正文插图:主体是文字说明,按正文配图流程走
65
+ - 报告/PPT 封面:走 `top-ppt-html`
66
+ - 只想找现成图:走 `image_search` 技能
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "topmind-cover",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "topmind-cover",
9
+ "version": "0.1.0",
10
+ "license": "MIT",
11
+ "engines": {
12
+ "node": ">=18"
13
+ }
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "topmind-cover",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "文章封面配图生成(X 长文 / 公众号共用):震撼醒目主题突出,尺寸规范 + 风格模板 + 裁剪落盘。",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/topmindspace/tms-skills.git",
10
+ "directory": "topmind-cover"
11
+ },
12
+ "type": "commonjs",
13
+ "engines": {
14
+ "node": ">=18"
15
+ }
16
+ }
@@ -0,0 +1,40 @@
1
+ # 封面风格模板
2
+
3
+ 选风格先看题材,再看系列延续性。同一系列固定一个模板,只换主体与标题字。
4
+
5
+ ## 科技深空(默认 · AI/技术题材)
6
+
7
+ - 深色底(藏青/墨黑)+ 霓虹线条 + 细颗粒星空
8
+ - 主体:产品/人物半身,面光干净
9
+ - 标题:白色粗黑,字号压住上 1/3
10
+ - 例:Manus 2.0、Cue、AI 智能体题材
11
+
12
+ ## 清新浅色(人物/生活/轻题材)
13
+
14
+ - 米白/浅灰底 + 柔光 + 大面积留白
15
+ - 主体居中,神态自然
16
+ - 标题:深灰细黑,落在下方 1/4
17
+ - 例:卡牌系列人物、随笔
18
+
19
+ ## 国潮撞色(文化/历史题材)
20
+
21
+ - 朱红/黛青撞色 + 传统纹样暗纹
22
+ - 主体:国风人物或器物
23
+ - 标题:书法感粗体,竖排亦可
24
+
25
+ ## 硬核评测(产品评测题材)
26
+
27
+ - 深灰底 + 产品特写 + 数据流装饰
28
+ - 标题:黄/白撞色,配 1 个核心数字(如"32%")
29
+ - 忌:数字必须与正文一致,承重数字先核实
30
+
31
+ ## 通用 prompt 组装公式
32
+
33
+ ```
34
+ {风格模板一句话},主体:{主体描述},
35
+ 画面中央大标题“{≤10字标题}”,{暗底亮字/亮底深字},
36
+ 横构图16:9,主体占画面40%以上,四周留白8%,
37
+ 极简,不堆砌元素,电影感光影
38
+ ```
39
+
40
+ 标题字逐字写进 prompt;生成后第一件事就是检查标题字。
@@ -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,58 @@
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
+ if errors:
52
+ print(f"\n[audit_docs] {NAME}: {len(errors)} 项失败")
53
+ sys.exit(1)
54
+ print(f"\n[audit_docs] {NAME}: 全部通过")
55
+
56
+
57
+ if __name__ == "__main__":
58
+ 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()
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ """封面图裁剪:从 16:9 主图中央裁出公众号 2.35:1 版,并统一落盘命名。
3
+
4
+ 用法:
5
+ python3 crop-cover.py <主图路径> --slug <slug> --out-dir <包>/images/
6
+
7
+ 产出:
8
+ <out-dir>/00-封面.png 1200x675 (X / 公众号共用主文件)
9
+ <out-dir>/00-封面-公众号.png 900x383 (公众号封面大图,中央裁剪)
10
+ """
11
+ import argparse
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ try:
16
+ from PIL import Image
17
+ except ImportError:
18
+ sys.exit("需要 Pillow:pip install pillow")
19
+
20
+ TARGETS = {
21
+ "00-封面.png": (1200, 675),
22
+ "00-封面-公众号.png": (900, 383),
23
+ }
24
+
25
+
26
+ def main() -> None:
27
+ ap = argparse.ArgumentParser()
28
+ ap.add_argument("src", help="16:9 主图路径")
29
+ ap.add_argument("--slug", default="cover")
30
+ ap.add_argument("--out-dir", required=True)
31
+ args = ap.parse_args()
32
+
33
+ src = Path(args.src)
34
+ out_dir = Path(args.out_dir)
35
+ out_dir.mkdir(parents=True, exist_ok=True)
36
+
37
+ if not src.is_file():
38
+ sys.exit(f"错误:找不到输入图片 {src}")
39
+ try:
40
+ img = Image.open(src).convert("RGB")
41
+ except Exception as e:
42
+ sys.exit(f"错误:无法读取图片 {src}({e})")
43
+ w, h = img.size
44
+
45
+ # 先按 16:9 中央裁一块基准
46
+ base_ratio = 16 / 9
47
+ if w / h > base_ratio:
48
+ cw, ch = int(h * base_ratio), h
49
+ else:
50
+ cw, ch = w, int(w / base_ratio)
51
+ x0, y0 = (w - cw) // 2, (h - ch) // 2
52
+ base = img.crop((x0, y0, x0 + cw, y0 + ch))
53
+
54
+ for name, (tw, th) in TARGETS.items():
55
+ ratio = tw / th
56
+ bw, bh = base.size
57
+ if bw / bh > ratio:
58
+ cw2, ch2 = int(bh * ratio), bh
59
+ else:
60
+ cw2, ch2 = bw, int(bw / ratio)
61
+ x1, y1 = (bw - cw2) // 2, (bh - ch2) // 2
62
+ out = base.crop((x1, y1, x1 + cw2, y1 + ch2)).resize((tw, th), Image.LANCZOS)
63
+ dest = out_dir / name
64
+ out.save(dest)
65
+ print(f"OK {dest} ({tw}x{th})")
66
+
67
+
68
+ if __name__ == "__main__":
69
+ main()
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """topmind-cover 异常输入测试:crop-cover.py 错误路径干净(任一失败 → 退出码 1)。"""
4
+ from __future__ import annotations
5
+
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ from pathlib import Path
10
+
11
+ try:
12
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
13
+ except Exception:
14
+ pass
15
+
16
+ ROOT = Path(__file__).resolve().parent.parent
17
+ CROP = ROOT / "scripts" / "crop-cover.py"
18
+ PY = sys.executable
19
+ fails: list[str] = []
20
+
21
+
22
+ def run(args: list[str]) -> subprocess.CompletedProcess:
23
+ return subprocess.run([PY, str(CROP), *args], capture_output=True, text=True, timeout=60)
24
+
25
+
26
+ def check(name: str, cond: bool, detail: str = "") -> None:
27
+ print((" ✓ " if cond else " ✗ ") + name + (f" — {detail}" if detail and not cond else ""))
28
+ if not cond:
29
+ fails.append(name)
30
+
31
+
32
+ def main() -> None:
33
+ print("[negative_tests] topmind-cover")
34
+ with tempfile.TemporaryDirectory() as td:
35
+ td = Path(td)
36
+
37
+ # 1. 缺失输入 → 非零退出,无 Traceback
38
+ r = run([str(td / "nope.png"), "--out-dir", str(td / "o1")])
39
+ check("缺失输入干净报错", r.returncode != 0 and "Traceback" not in r.stderr, r.stderr[:100])
40
+
41
+ # 2. 非图片文件 → 非零退出,无 Traceback
42
+ (td / "notimg.txt").write_text("hello", encoding="utf-8")
43
+ r = run([str(td / "notimg.txt"), "--out-dir", str(td / "o2")])
44
+ check("非图片干净报错", r.returncode != 0 and "Traceback" not in r.stderr, r.stderr[:100])
45
+
46
+ # 3. 正常图片产出双尺寸
47
+ try:
48
+ from PIL import Image
49
+ img = Image.new("RGB", (1600, 900), (20, 30, 60))
50
+ img.save(td / "ok.png")
51
+ r = run([str(td / "ok.png"), "--out-dir", str(td / "o3")])
52
+ got_a = (td / "o3" / "00-封面.png").exists()
53
+ got_b = (td / "o3" / "00-封面-公众号.png").exists()
54
+ check("正常图片产出双尺寸", r.returncode == 0 and got_a and got_b, r.stderr[:100])
55
+ if got_a:
56
+ w, h = Image.open(td / "o3" / "00-封面.png").size
57
+ check("主图 1200×675", (w, h) == (1200, 675), f"实 {w}×{h}")
58
+ if got_b:
59
+ w, h = Image.open(td / "o3" / "00-封面-公众号.png").size
60
+ check("公众号图 900×383", (w, h) == (900, 383), f"实 {w}×{h}")
61
+ except ImportError:
62
+ print(" - 跳过图片生成测试(无 Pillow)")
63
+
64
+ if fails:
65
+ print(f"\n[negative_tests] {len(fails)} 项失败")
66
+ sys.exit(1)
67
+ print("\n[negative_tests] 全部通过")
68
+
69
+
70
+ if __name__ == "__main__":
71
+ main()