@topmindspace/tms-skills 0.1.18 → 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.
- package/CHANGELOG.md +66 -0
- package/README.en.md +6 -3
- package/README.md +6 -3
- package/package.json +5 -2
- package/top-ppt-html/README.en.md +2 -2
- package/top-ppt-html/README.md +1 -1
- package/top-ppt-html/SKILL.md +4 -4
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +24 -12
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +24 -12
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +24 -12
- package/top-ppt-html/assets/examples/2026-09-26-topmind-tms-skills-showcase.html +94 -165
- package/top-ppt-html/assets/examples/2026-09-26-topmind-tms-skills-showcase.model.json +32 -38
- package/top-ppt-html/assets/pptx-export.js +3 -2
- package/top-ppt-html/assets/style-gallery.html +2 -1
- package/top-ppt-html/assets/templates/architecture.html +38 -15
- package/top-ppt-html/assets/templates/engine.css +20 -9
- package/top-ppt-html/assets/templates/presentation.html +38 -17
- package/top-ppt-html/assets/templates/research.html +44 -23
- package/top-ppt-html/package-lock.json +2 -2
- package/top-ppt-html/package.json +1 -1
- package/top-ppt-html/references/components-atoms.md +1 -1
- package/top-ppt-html/references/content-rules.md +17 -11
- package/top-ppt-html/references/failure-modes.md +6 -4
- package/top-ppt-html/references/high-fidelity.md +14 -1
- package/top-ppt-html/references/icons.md +2 -2
- package/top-ppt-html/references/layout-grammar.md +3 -1
- package/top-ppt-html/references/layouts-research.md +1 -1
- package/top-ppt-html/references/pptx-export.md +2 -2
- package/top-ppt-html/references/tech-design.md +2 -0
- package/top-ppt-html/scripts/build_icon_assets.js +92 -0
- package/top-ppt-html/scripts/build_pptx.js +125 -29
- package/top-ppt-html/scripts/extract_model.py +21 -1
- package/top-ppt-html/scripts/icon_lib.js +53 -0
- package/top-ppt-html/scripts/icon_raster.js +86 -0
- package/top-ppt-html/scripts/layout-constants.json +4 -1
- package/top-ppt-html/scripts/lib_layout_regions.js +3 -1
- package/top-ppt-html/scripts/model-schema.json +7 -3
- package/top-ppt-html/scripts/package_skill.py +1 -0
- package/top-ppt-html/scripts/render_from_model.py +125 -24
- package/top-ppt-html/scripts/scaffold_report.py +47 -24
- package/top-ppt-html/scripts/sync_runtime.py +2 -1
- package/top-ppt-html/scripts/validate_pptx.py +14 -3
- package/top-ppt-html/scripts/validate_report.py +1 -1
- package/topmind-cover/README.md +40 -0
- package/topmind-cover/SKILL.md +66 -0
- package/topmind-cover/package-lock.json +16 -0
- package/topmind-cover/package.json +16 -0
- package/topmind-cover/references/cover-styles.md +40 -0
- package/topmind-cover/scripts/audit_css.py +39 -0
- package/topmind-cover/scripts/audit_docs.py +58 -0
- package/topmind-cover/scripts/audit_skill.py +60 -0
- package/topmind-cover/scripts/audit_styles.py +54 -0
- package/topmind-cover/scripts/crop-cover.py +69 -0
- package/topmind-cover/scripts/negative_tests.py +71 -0
- package/topmind-cover/scripts/package_skill.py +188 -0
- package/topmind-wechat-post/README.md +49 -0
- package/topmind-wechat-post/SKILL.md +208 -0
- package/topmind-wechat-post/assets/themes/amber-review.json +35 -0
- package/topmind-wechat-post/assets/themes/graphite.json +35 -0
- package/topmind-wechat-post/assets/themes/minimal-ink.json +35 -0
- package/topmind-wechat-post/assets/themes/newsprint.json +35 -0
- package/topmind-wechat-post/assets/themes/tech-blue.json +35 -0
- package/topmind-wechat-post/package-lock.json +16 -0
- package/topmind-wechat-post/package.json +16 -0
- package/topmind-wechat-post/references/design.md +50 -0
- package/topmind-wechat-post/references/element-spec.md +162 -0
- package/topmind-wechat-post/references/known-pits.md +86 -0
- package/topmind-wechat-post/references/theme-map.md +139 -0
- package/topmind-wechat-post/references/typography-rules.md +62 -0
- package/topmind-wechat-post/references/wechat-constraints.md +106 -0
- package/topmind-wechat-post/references/workflow.md +102 -0
- package/topmind-wechat-post/references/writing-quality.md +129 -0
- package/topmind-wechat-post/scripts/audit_css.py +39 -0
- package/topmind-wechat-post/scripts/audit_docs.py +58 -0
- package/topmind-wechat-post/scripts/audit_skill.py +60 -0
- package/topmind-wechat-post/scripts/audit_styles.py +54 -0
- package/topmind-wechat-post/scripts/lint-wechat.py +446 -0
- package/topmind-wechat-post/scripts/md2wechat.py +1602 -0
- package/topmind-wechat-post/scripts/negative_tests.py +65 -0
- package/topmind-wechat-post/scripts/new-article.py +183 -0
- package/topmind-wechat-post/scripts/package_skill.py +188 -0
- package/topmind-wechat-post/scripts/push-to-topstream.py +226 -0
- package/topmind-wechat-post/scripts/scan_ai_flavor.py +464 -0
- package/topmind-wechat-post/scripts/sync-mapping.py +220 -0
- package/topmind-wechat-post/scripts/sync-status.py +187 -0
- package/topmind-x-article/README.md +40 -0
- package/topmind-x-article/SKILL.md +62 -0
- package/topmind-x-article/package-lock.json +16 -0
- package/topmind-x-article/package.json +16 -0
- package/topmind-x-article/references/publish-checklist.md +24 -0
- package/topmind-x-article/references/x-format.md +24 -0
- package/topmind-x-article/scripts/audit_css.py +39 -0
- package/topmind-x-article/scripts/audit_docs.py +58 -0
- package/topmind-x-article/scripts/audit_skill.py +60 -0
- package/topmind-x-article/scripts/audit_styles.py +54 -0
- package/topmind-x-article/scripts/md2x.py +121 -0
- package/topmind-x-article/scripts/negative_tests.py +74 -0
- package/topmind-x-article/scripts/package_skill.py +188 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""md → X 长文可粘贴纯文本。
|
|
3
|
+
|
|
4
|
+
把 Markdown 原稿转成可直接粘贴进 X Article 编辑器的纯文本:
|
|
5
|
+
标题/正文保留,排版标记剥离,图片转为 [图N] 占位。
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
python3 md2x.py <输入.md> --out <输出/X发布稿.txt>
|
|
9
|
+
|
|
10
|
+
规则见 references/x-format.md。
|
|
11
|
+
"""
|
|
12
|
+
import argparse
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def convert(md: str):
|
|
19
|
+
images = []
|
|
20
|
+
out_lines = []
|
|
21
|
+
in_code = False
|
|
22
|
+
|
|
23
|
+
def img_repl(m):
|
|
24
|
+
alt = m.group(1).strip()
|
|
25
|
+
images.append(alt)
|
|
26
|
+
return f"[图{len(images)}]"
|
|
27
|
+
|
|
28
|
+
for raw in md.splitlines():
|
|
29
|
+
line = raw.rstrip()
|
|
30
|
+
|
|
31
|
+
# frontmatter 直接跳过
|
|
32
|
+
if line.strip() == "---":
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
# 代码块:去围栏,内容缩进保留
|
|
36
|
+
if line.strip().startswith("```"):
|
|
37
|
+
in_code = not in_code
|
|
38
|
+
continue
|
|
39
|
+
if in_code:
|
|
40
|
+
out_lines.append(" " + line)
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
# 图片占位
|
|
44
|
+
line = re.sub(r"!\[([^\]]*)\]\([^)]*\)", img_repl, line)
|
|
45
|
+
|
|
46
|
+
# 标题 → 纯文本行
|
|
47
|
+
m = re.match(r"^(#{1,6})\s+(.*)$", line)
|
|
48
|
+
if m:
|
|
49
|
+
out_lines.append("")
|
|
50
|
+
out_lines.append(m.group(2).strip())
|
|
51
|
+
out_lines.append("")
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
# 引用 → 纯文本
|
|
55
|
+
line = re.sub(r"^>\s?", "", line)
|
|
56
|
+
|
|
57
|
+
# 分割线 → 空行
|
|
58
|
+
if re.match(r"^(\*{3,}|-{3,}|_{3,})\s*$", line):
|
|
59
|
+
out_lines.append("")
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
# 表格行 → "项:值" 列表
|
|
63
|
+
if "|" in line and line.strip().startswith("|"):
|
|
64
|
+
cells = [c.strip() for c in line.strip().strip("|").split("|")]
|
|
65
|
+
if all(re.match(r"^:?-+:?$", c) for c in cells):
|
|
66
|
+
continue
|
|
67
|
+
label = cells[0]
|
|
68
|
+
rest = ",".join(c for c in cells[1:] if c)
|
|
69
|
+
out_lines.append(f"{label}:{rest}" if rest else label)
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
# 行内标记剥离
|
|
73
|
+
line = re.sub(r"\*\*(.+?)\*\*", r"\1", line) # 加粗
|
|
74
|
+
line = re.sub(r"__(.+?)__", r"\1", line)
|
|
75
|
+
line = re.sub(r"\*(.+?)\*", r"\1", line) # 斜体
|
|
76
|
+
line = re.sub(r"`(.+?)`", r"\1", line) # 行内代码
|
|
77
|
+
line = re.sub(r"~~(.+?)~~", r"\1", line) # 删除线
|
|
78
|
+
line = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", r"\1(\2)", line) # 链接
|
|
79
|
+
line = re.sub(r"^(\s*)- \[ \]\s+", r"\1- ", line) # 任务列表
|
|
80
|
+
line = re.sub(r"^(\s*)- \[x\]\s+", r"\1- ", line, flags=re.I)
|
|
81
|
+
|
|
82
|
+
out_lines.append(line)
|
|
83
|
+
|
|
84
|
+
# 压缩 3+ 空行为 2 个
|
|
85
|
+
text = "\n".join(out_lines)
|
|
86
|
+
text = re.sub(r"\n{3,}", "\n\n", text).strip() + "\n"
|
|
87
|
+
|
|
88
|
+
# 文末附图注清单
|
|
89
|
+
if images:
|
|
90
|
+
text += "\n—— 配图清单 ——\n"
|
|
91
|
+
for i, alt in enumerate(images, 1):
|
|
92
|
+
text += f"[图{i}] {alt or '(未写图注)'}\n"
|
|
93
|
+
|
|
94
|
+
return text, images
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def main() -> None:
|
|
98
|
+
ap = argparse.ArgumentParser()
|
|
99
|
+
ap.add_argument("input", help="输入 Markdown")
|
|
100
|
+
ap.add_argument("--out", required=True, help="输出 X发布稿.txt")
|
|
101
|
+
args = ap.parse_args()
|
|
102
|
+
|
|
103
|
+
inp = Path(args.input)
|
|
104
|
+
if not inp.is_file():
|
|
105
|
+
sys.exit(f"错误:找不到输入文件 {inp}")
|
|
106
|
+
md = inp.read_text(encoding="utf-8")
|
|
107
|
+
# 去掉首尾 frontmatter 块
|
|
108
|
+
md = re.sub(r"^---\n.*?\n---\n", "", md, flags=re.S)
|
|
109
|
+
text, images = convert(md)
|
|
110
|
+
|
|
111
|
+
out = Path(args.out)
|
|
112
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
113
|
+
out.write_text(text, encoding="utf-8")
|
|
114
|
+
|
|
115
|
+
chars = len(text.replace("\n", ""))
|
|
116
|
+
print(f"OK {out}")
|
|
117
|
+
print(f"正文约 {chars} 字,配图 {len(images)} 张")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
main()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""topmind-x-article 异常输入测试:md2x.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
|
+
MD2X = ROOT / "scripts" / "md2x.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(MD2X), *args], capture_output=True, text=True, timeout=30)
|
|
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-x-article")
|
|
34
|
+
with tempfile.TemporaryDirectory() as td:
|
|
35
|
+
td = Path(td)
|
|
36
|
+
|
|
37
|
+
# 1. 空文件
|
|
38
|
+
(td / "empty.md").write_text("", encoding="utf-8")
|
|
39
|
+
r = run([str(td / "empty.md"), "--out", str(td / "o1.txt")])
|
|
40
|
+
check("空文件不崩溃", r.returncode == 0 and "Traceback" not in r.stderr)
|
|
41
|
+
|
|
42
|
+
# 2. 只有 frontmatter
|
|
43
|
+
(td / "fm.md").write_text("---\ntitle: t\n---\n", encoding="utf-8")
|
|
44
|
+
r = run([str(td / "fm.md"), "--out", str(td / "o2.txt")])
|
|
45
|
+
check("纯 frontmatter 不崩溃", r.returncode == 0 and "Traceback" not in r.stderr)
|
|
46
|
+
|
|
47
|
+
# 3. 未闭合代码围栏
|
|
48
|
+
(td / "fence.md").write_text("# t\n\n```python\nprint(1)\n", encoding="utf-8")
|
|
49
|
+
r = run([str(td / "fence.md"), "--out", str(td / "o3.txt")])
|
|
50
|
+
check("未闭合围栏不崩溃", r.returncode == 0 and "Traceback" not in r.stderr)
|
|
51
|
+
|
|
52
|
+
# 4. 无分隔行的表格
|
|
53
|
+
(td / "tbl.md").write_text("# t\n\n| a | b |\n| 1 | 2 |\n", encoding="utf-8")
|
|
54
|
+
r = run([str(td / "tbl.md"), "--out", str(td / "o4.txt")])
|
|
55
|
+
check("异常表格不崩溃", r.returncode == 0 and "Traceback" not in r.stderr)
|
|
56
|
+
|
|
57
|
+
# 5. 不存在的输入 → 非零退出且无 Traceback
|
|
58
|
+
r = run([str(td / "nope.md"), "--out", str(td / "o5.txt")])
|
|
59
|
+
check("缺失输入干净报错", r.returncode != 0 and "Traceback" not in r.stderr)
|
|
60
|
+
|
|
61
|
+
# 6. 正常输入产出配图清单
|
|
62
|
+
(td / "ok.md").write_text("# t\n\n\n", encoding="utf-8")
|
|
63
|
+
r = run([str(td / "ok.md"), "--out", str(td / "o6.txt")])
|
|
64
|
+
out = (td / "o6.txt").read_text(encoding="utf-8") if (td / "o6.txt").exists() else ""
|
|
65
|
+
check("正常输入含配图清单", r.returncode == 0 and "[图1]" in out)
|
|
66
|
+
|
|
67
|
+
if fails:
|
|
68
|
+
print(f"\n[negative_tests] {len(fails)} 项失败")
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
print("\n[negative_tests] 全部通过")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
main()
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""技能分发包打包器(发布前完整性校验 + 发布清单)。
|
|
4
|
+
|
|
5
|
+
用法:
|
|
6
|
+
python scripts/package_skill.py # 校验 → 打包 → 写发布清单
|
|
7
|
+
python scripts/package_skill.py --check # 只校验不打包(CI / 提交前自检)
|
|
8
|
+
|
|
9
|
+
产物:
|
|
10
|
+
dist/<skill>.zip 分发包(解压目录名 = 技能名)
|
|
11
|
+
dist/<skill>.manifest.json 发布清单(版本 / 条目 / 大小 / SHA-256)
|
|
12
|
+
|
|
13
|
+
校验(任一不过 → 退出码 1):
|
|
14
|
+
· SKILL.md frontmatter 含 name / description;name 与目录名一致;
|
|
15
|
+
description ≤ 1024 字符(平台截断阈值)
|
|
16
|
+
· README.md / package.json 存在;package.json name 与技能名一致
|
|
17
|
+
· scripts/*.py 全部可编译;references/*.md 非空
|
|
18
|
+
· SKILL.md 引用的 references/<file> 全部存在
|
|
19
|
+
· 无 scripts/_* / __pycache__ / .env 等不应进包的文件
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import hashlib
|
|
24
|
+
import json
|
|
25
|
+
import re
|
|
26
|
+
import sys
|
|
27
|
+
import zipfile
|
|
28
|
+
from datetime import datetime, timezone
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
37
|
+
NAME = ROOT.name
|
|
38
|
+
OUT = ROOT / "dist" / f"{NAME}.zip"
|
|
39
|
+
MANIFEST = ROOT / "dist" / f"{NAME}.manifest.json"
|
|
40
|
+
DESC_LIMIT = 1024
|
|
41
|
+
|
|
42
|
+
INCLUDE = ["SKILL.md", "README.md", "package.json", "assets/", "references/", "scripts/", "evals/", "agents/"]
|
|
43
|
+
EXCLUDE_RES = [
|
|
44
|
+
re.compile(r"(^|/)scripts/_"),
|
|
45
|
+
re.compile(r"(^|/)\.env($|\.)"),
|
|
46
|
+
re.compile(r"(^|/)__pycache__(/|$)"),
|
|
47
|
+
re.compile(r"(^|/)node_modules(/|$)"),
|
|
48
|
+
re.compile(r"(^|/)dist(/|$)"),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
errors: list[str] = []
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def fail(msg: str) -> None:
|
|
55
|
+
errors.append(msg)
|
|
56
|
+
print(f" ✗ {msg}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def ok(msg: str) -> None:
|
|
60
|
+
print(f" ✓ {msg}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def read_frontmatter() -> dict:
|
|
64
|
+
text = (ROOT / "SKILL.md").read_text(encoding="utf-8")
|
|
65
|
+
m = re.match(r"^---\r?\n([\s\S]*?)\r?\n---", text)
|
|
66
|
+
if not m:
|
|
67
|
+
fail("SKILL.md 缺 frontmatter")
|
|
68
|
+
return {}
|
|
69
|
+
fm = m.group(1)
|
|
70
|
+
name = re.search(r"^name:\s*(\S+)\s*$", fm, re.M)
|
|
71
|
+
desc = ""
|
|
72
|
+
dm = re.search(r'^description:\s*"([^"]*)"\s*$', fm, re.M)
|
|
73
|
+
if dm:
|
|
74
|
+
desc = dm.group(1).strip()
|
|
75
|
+
else:
|
|
76
|
+
dm = re.search(r"^description:\s*>\-\s*\n((?:[ \t]+.*\n?)+)", fm, re.M)
|
|
77
|
+
if dm:
|
|
78
|
+
desc = " ".join(line.strip() for line in dm.group(1).splitlines() if line.strip())
|
|
79
|
+
return {"name": name.group(1) if name else "", "description": desc}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def check() -> bool:
|
|
83
|
+
print(f"[check] {NAME}")
|
|
84
|
+
fm = read_frontmatter()
|
|
85
|
+
if fm.get("name") != NAME:
|
|
86
|
+
fail(f"frontmatter name={fm.get('name')!r} 与目录名 {NAME!r} 不一致")
|
|
87
|
+
else:
|
|
88
|
+
ok("name 与目录名一致")
|
|
89
|
+
desc = fm.get("description", "")
|
|
90
|
+
if not desc:
|
|
91
|
+
fail("description 为空")
|
|
92
|
+
elif len(desc) > DESC_LIMIT:
|
|
93
|
+
fail(f"description {len(desc)} 字符,超过 {DESC_LIMIT}")
|
|
94
|
+
else:
|
|
95
|
+
ok(f"description {len(desc)} 字符")
|
|
96
|
+
|
|
97
|
+
for f in ("README.md", "package.json"):
|
|
98
|
+
if not (ROOT / f).is_file():
|
|
99
|
+
fail(f"缺 {f}")
|
|
100
|
+
else:
|
|
101
|
+
ok(f"有 {f}")
|
|
102
|
+
try:
|
|
103
|
+
pkg = json.loads((ROOT / "package.json").read_text(encoding="utf-8"))
|
|
104
|
+
if pkg.get("name") != NAME:
|
|
105
|
+
fail(f"package.json name={pkg.get('name')!r} 与技能名不一致")
|
|
106
|
+
if not re.match(r"^\d+\.\d+\.\d+", str(pkg.get("version", ""))):
|
|
107
|
+
fail("package.json version 非 semver")
|
|
108
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
109
|
+
fail(f"package.json 解析失败: {e}")
|
|
110
|
+
|
|
111
|
+
for p in ROOT.rglob("*"):
|
|
112
|
+
if not p.is_file() or ".git" in p.parts:
|
|
113
|
+
continue
|
|
114
|
+
rel = p.relative_to(ROOT).as_posix()
|
|
115
|
+
for rx in EXCLUDE_RES:
|
|
116
|
+
if rx.search(rel):
|
|
117
|
+
fail(f"不应进包的文件: {rel}")
|
|
118
|
+
break
|
|
119
|
+
ok("无禁用文件")
|
|
120
|
+
|
|
121
|
+
for py in sorted((ROOT / "scripts").glob("*.py")):
|
|
122
|
+
try:
|
|
123
|
+
compile(py.read_text(encoding="utf-8"), str(py), "exec")
|
|
124
|
+
except SyntaxError as e:
|
|
125
|
+
fail(f"{py.name} 编译失败: {e}")
|
|
126
|
+
ok("scripts/*.py 编译通过")
|
|
127
|
+
|
|
128
|
+
refdir = ROOT / "references"
|
|
129
|
+
if refdir.is_dir():
|
|
130
|
+
for md in sorted(refdir.glob("*.md")):
|
|
131
|
+
if md.stat().st_size == 0:
|
|
132
|
+
fail(f"references/{md.name} 为空")
|
|
133
|
+
skill_text = (ROOT / "SKILL.md").read_text(encoding="utf-8")
|
|
134
|
+
for ref in sorted(set(re.findall(r"references/([\w\-.]+\.md)", skill_text))):
|
|
135
|
+
if not (refdir / ref).is_file():
|
|
136
|
+
fail(f"SKILL.md 引用的 references/{ref} 不存在")
|
|
137
|
+
ok("references 完整性通过")
|
|
138
|
+
|
|
139
|
+
if errors:
|
|
140
|
+
print(f"\n[check] {NAME}: {len(errors)} 项失败")
|
|
141
|
+
return False
|
|
142
|
+
print(f"\n[check] {NAME}: 全部通过")
|
|
143
|
+
return True
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def package() -> None:
|
|
147
|
+
if not check():
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
(ROOT / "dist").mkdir(exist_ok=True)
|
|
150
|
+
entries: list[str] = []
|
|
151
|
+
with zipfile.ZipFile(OUT, "w", zipfile.ZIP_DEFLATED) as z:
|
|
152
|
+
for p in sorted(ROOT.rglob("*")):
|
|
153
|
+
if not p.is_file() or ".git" in p.parts:
|
|
154
|
+
continue
|
|
155
|
+
rel = p.relative_to(ROOT)
|
|
156
|
+
rel_posix = rel.as_posix()
|
|
157
|
+
if any(rx.search(rel_posix) for rx in EXCLUDE_RES):
|
|
158
|
+
continue
|
|
159
|
+
if not any(
|
|
160
|
+
rel_posix == inc.rstrip("/") or rel_posix.startswith(inc)
|
|
161
|
+
for inc in INCLUDE
|
|
162
|
+
if inc.endswith("/")
|
|
163
|
+
) and rel_posix not in INCLUDE:
|
|
164
|
+
continue
|
|
165
|
+
z.write(p, arcname=f"{NAME}/{rel_posix}")
|
|
166
|
+
entries.append(rel_posix)
|
|
167
|
+
sha = hashlib.sha256(OUT.read_bytes()).hexdigest()
|
|
168
|
+
pkg = json.loads((ROOT / "package.json").read_text(encoding="utf-8"))
|
|
169
|
+
manifest = {
|
|
170
|
+
"name": NAME,
|
|
171
|
+
"version": pkg.get("version"),
|
|
172
|
+
"built_at": datetime.now(timezone.utc).isoformat(),
|
|
173
|
+
"files": len(entries),
|
|
174
|
+
"sha256": sha,
|
|
175
|
+
"entries": entries,
|
|
176
|
+
}
|
|
177
|
+
MANIFEST.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
178
|
+
print(f"\n[package] {OUT} ({len(entries)} 文件, sha256={sha[:12]}…)")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def main() -> None:
|
|
182
|
+
if "--check" in sys.argv[1:]:
|
|
183
|
+
sys.exit(0 if check() else 1)
|
|
184
|
+
package()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
main()
|