@topmindspace/tms-skills 2.0.1 → 2.1.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 +145 -134
- package/README.md +1 -1
- package/bin/tms-skills.js +160 -160
- package/package.json +48 -48
- package/top-ppt-html/README.md +254 -254
- package/top-ppt-html/SKILL.md +111 -111
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3926 -3926
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.model.json +167 -167
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.html +3926 -3926
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.model.json +167 -167
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.model.json +913 -913
- package/top-ppt-html/assets/pptx-export.js +1944 -1944
- package/top-ppt-html/assets/style-gallery.html +589 -589
- package/top-ppt-html/assets/templates/architecture.html +3728 -3728
- package/top-ppt-html/assets/templates/engine.css +840 -840
- package/top-ppt-html/assets/templates/presentation.html +3738 -3738
- package/top-ppt-html/assets/templates/research.html +4017 -4017
- package/top-ppt-html/assets/templates/ui.js +520 -520
- package/top-ppt-html/evals/prompts.csv +15 -15
- package/top-ppt-html/evals/rubric.schema.json +25 -25
- package/top-ppt-html/evals/run_evals.py +220 -220
- package/top-ppt-html/evals/trace.example.json +16 -16
- package/top-ppt-html/package-lock.json +207 -0
- package/top-ppt-html/package.json +35 -35
- package/top-ppt-html/references/charts-basic.md +624 -624
- package/top-ppt-html/references/charts-discipline.md +108 -108
- package/top-ppt-html/references/charts-extended.md +482 -482
- package/top-ppt-html/references/charts.md +28 -28
- package/top-ppt-html/references/components-atoms.md +624 -624
- package/top-ppt-html/references/components.md +30 -30
- package/top-ppt-html/references/content-rules.md +490 -490
- package/top-ppt-html/references/design-system-engine.md +235 -235
- package/top-ppt-html/references/design-system.md +478 -478
- package/top-ppt-html/references/failure-modes.md +214 -214
- package/top-ppt-html/references/high-fidelity.md +127 -127
- package/top-ppt-html/references/icons.md +397 -397
- package/top-ppt-html/references/industry-benchmark.md +105 -105
- package/top-ppt-html/references/infographics-stats.md +308 -308
- package/top-ppt-html/references/infographics-structure.md +226 -226
- package/top-ppt-html/references/infographics.md +43 -43
- package/top-ppt-html/references/layout-grammar.md +315 -315
- package/top-ppt-html/references/layouts-architecture.md +108 -108
- package/top-ppt-html/references/layouts-combo.md +600 -600
- package/top-ppt-html/references/layouts-research.md +160 -160
- package/top-ppt-html/references/modes.md +254 -254
- package/top-ppt-html/references/outline-design.md +275 -275
- package/top-ppt-html/references/playbook.md +266 -266
- package/top-ppt-html/references/pptx-export.md +209 -209
- package/top-ppt-html/references/reform-plan.md +252 -252
- package/top-ppt-html/references/styles.md +370 -370
- package/top-ppt-html/references/tech-design.md +138 -138
- package/top-ppt-html/scripts/audit_css.py +109 -109
- package/top-ppt-html/scripts/audit_docs.py +176 -176
- package/top-ppt-html/scripts/audit_skill.py +220 -220
- package/top-ppt-html/scripts/audit_styles.py +351 -351
- package/top-ppt-html/scripts/build_examples.py +2276 -2276
- package/top-ppt-html/scripts/build_pptx.js +2380 -2380
- package/top-ppt-html/scripts/capture_theme_overview.js +78 -78
- package/top-ppt-html/scripts/checks_html.py +127 -127
- package/top-ppt-html/scripts/cross_verify.py +294 -294
- package/top-ppt-html/scripts/env_probe.py +158 -158
- package/top-ppt-html/scripts/extract_model.py +210 -210
- package/top-ppt-html/scripts/extract_snippet.py +374 -374
- package/top-ppt-html/scripts/gen_channel_a.js +214 -214
- package/top-ppt-html/scripts/layout-constants.json +3377 -3377
- package/top-ppt-html/scripts/layout_slots.json +830 -830
- package/top-ppt-html/scripts/lib_layout_regions.js +412 -412
- package/top-ppt-html/scripts/measure_height.py +178 -178
- package/top-ppt-html/scripts/model-schema.json +547 -547
- package/top-ppt-html/scripts/negative_tests.py +307 -307
- package/top-ppt-html/scripts/package_skill.py +291 -291
- package/top-ppt-html/scripts/prepare_images.py +341 -341
- package/top-ppt-html/scripts/probe_image_export.py +188 -188
- package/top-ppt-html/scripts/quality_gate.py +301 -301
- package/top-ppt-html/scripts/regression.py +308 -308
- package/top-ppt-html/scripts/render_compare.py +275 -275
- package/top-ppt-html/scripts/render_from_model.py +698 -698
- package/top-ppt-html/scripts/scaffold_report.py +1054 -1054
- package/top-ppt-html/scripts/section-file-map.json +104 -104
- package/top-ppt-html/scripts/sync_runtime.py +662 -662
- package/top-ppt-html/scripts/validate_pptx.py +1510 -1510
- package/top-ppt-html/scripts/validate_report.py +1456 -1456
|
@@ -1,291 +1,291 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""TopPPT HTML · 技能分发包打包器(发布前完整性校验 + 发布清单)
|
|
4
|
-
|
|
5
|
-
用法:
|
|
6
|
-
python scripts/package_skill.py # 校验 → 打包 → 写发布清单
|
|
7
|
-
python scripts/package_skill.py --check # 只校验不打包(CI / 提交前自检)
|
|
8
|
-
|
|
9
|
-
产物:
|
|
10
|
-
dist/top-ppt-html.zip 分发包(解压目录名 = 技能全名 top-ppt-html)
|
|
11
|
-
dist/top-ppt-html.manifest.json 发布清单(版本 / 条目 / 大小 / SHA-256),随包留档
|
|
12
|
-
|
|
13
|
-
────────────────────────────────────────────────────────────────────────
|
|
14
|
-
进包(= 技能运行与维护所需的最小完备集,缺一即校验失败)
|
|
15
|
-
────────────────────────────────────────────────────────────────────────
|
|
16
|
-
SKILL.md 技能入口(frontmatter + 触发边界 + 工作流 + 铁律)
|
|
17
|
-
README.md 人类视角的简介 / 安装 / 开发 / 打包
|
|
18
|
-
package.json Node 依赖声明(pptxgenjs)与常用命令
|
|
19
|
-
assets/templates/ 三份模式模板 + engine.css / ui.js(公共片段注入源)
|
|
20
|
-
assets/examples/ 9 份示例 HTML + 9 份 REPORT_MODEL(复制起点 · 覆盖全部 9 风格)
|
|
21
|
-
assets/pptx-export.js PPTX 预览运行时(注入源)
|
|
22
|
-
assets/style-gallery.html 风格 × 模式 × 亮暗主题交互画廊
|
|
23
|
-
assets/theme-overview*.png 4 张主题参考图(六项问询辅助)
|
|
24
|
-
references/*.md 15 篇规范(playbook.md = 唯一常读入口 L1;其余 L2 按需加载)
|
|
25
|
-
scripts/ 生成 / 校验 / 回归 / 维护工具(py + js + json 单源)
|
|
26
|
-
scripts/extract_snippet.py L2 节级片段抽取(减少整读大规范)
|
|
27
|
-
scripts/layout_slots.json 页型布局 IR(高频页型语义槽位)
|
|
28
|
-
|
|
29
|
-
────────────────────────────────────────────────────────────────────────
|
|
30
|
-
不进包(构建产物 / 本地状态 / 缓存,与 .gitignore 口径一致)
|
|
31
|
-
────────────────────────────────────────────────────────────────────────
|
|
32
|
-
dist/ 分发包与回归产物本身
|
|
33
|
-
report-assets/ prepare_images.py 的产出(片段 / 模型对象)
|
|
34
|
-
render-compare/ render_compare.py 的渲染对照产物
|
|
35
|
-
node_modules/ npm 依赖(安装期生成)
|
|
36
|
-
__pycache__/ Python 缓存
|
|
37
|
-
IDE / 智能体本地状态目录 IDE / 智能体本地状态(不入库、不分发)
|
|
38
|
-
scripts/_* 下划线开头的临时脚本(用后即删)
|
|
39
|
-
|
|
40
|
-
────────────────────────────────────────────────────────────────────────
|
|
41
|
-
发布前校验(任一不过 → 退出码 1,不产出 zip)
|
|
42
|
-
────────────────────────────────────────────────────────────────────────
|
|
43
|
-
· SKILL.md frontmatter 含 name / description;name 与分发包目录名一致;
|
|
44
|
-
description 为单行双引号字符串且 ≤ 1024 字符(平台截断阈值)
|
|
45
|
-
· 15 篇 references、3 份模式模板 + engine.css / ui.js、9 组示例、4 张参考图齐全
|
|
46
|
-
· 关键脚本齐全(单源注入 / 审计 / 示例重建 / 回归 / 双校验器 / 精导 / 打包)
|
|
47
|
-
"""
|
|
48
|
-
import fnmatch
|
|
49
|
-
import hashlib
|
|
50
|
-
import json
|
|
51
|
-
import re
|
|
52
|
-
import sys
|
|
53
|
-
import zipfile
|
|
54
|
-
from datetime import datetime, timezone
|
|
55
|
-
from pathlib import Path
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
59
|
-
except Exception:
|
|
60
|
-
pass
|
|
61
|
-
|
|
62
|
-
ROOT = Path(__file__).resolve().parent.parent
|
|
63
|
-
NAME = 'top-ppt-html' # 技能全名 = 分发包顶层目录名 = SKILL.md name
|
|
64
|
-
OUT = ROOT / 'dist' / f'{NAME}.zip'
|
|
65
|
-
MANIFEST = ROOT / 'dist' / f'{NAME}.manifest.json'
|
|
66
|
-
DESC_LIMIT = 1024
|
|
67
|
-
# 版本唯一事实源 = layout-constants.json `version`(v8.3 起 frontmatter 不携带非标 version 键,
|
|
68
|
-
# manifest 版本一律取 LC;frontmatter 若显式声明则以其为准——但不鼓励)
|
|
69
|
-
try:
|
|
70
|
-
VERSION = str(json.loads(
|
|
71
|
-
(ROOT / 'scripts' / 'layout-constants.json').read_text(encoding='utf-8')).get('version') or '0.0')
|
|
72
|
-
except (OSError, json.JSONDecodeError):
|
|
73
|
-
VERSION = '0.0'
|
|
74
|
-
|
|
75
|
-
# 进包清单(显式枚举,缺一即失败)
|
|
76
|
-
INCLUDE = [
|
|
77
|
-
'SKILL.md',
|
|
78
|
-
'README.md',
|
|
79
|
-
'package.json',
|
|
80
|
-
'assets/pptx-export.js',
|
|
81
|
-
'assets/style-gallery.html',
|
|
82
|
-
'assets/theme-overview.png',
|
|
83
|
-
'assets/theme-overview-presentation.png',
|
|
84
|
-
'assets/theme-overview-research.png',
|
|
85
|
-
'assets/theme-overview-architecture.png',
|
|
86
|
-
'assets/templates/*',
|
|
87
|
-
'assets/examples/*',
|
|
88
|
-
'references/*',
|
|
89
|
-
'scripts/*',
|
|
90
|
-
'evals/*',
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
# 出包规则(命中即跳过;与 .gitignore 口径一致)
|
|
94
|
-
EXCLUDE = [
|
|
95
|
-
'scripts/_*', # 临时脚本
|
|
96
|
-
'scripts/__pycache__/*',
|
|
97
|
-
'**/__pycache__/*',
|
|
98
|
-
'**/node_modules/*',
|
|
99
|
-
'**/report-assets/*',
|
|
100
|
-
'**/render-compare/*',
|
|
101
|
-
'**/dist/*',
|
|
102
|
-
'**/.codebuddy/*',
|
|
103
|
-
'**/.h3caiwork/*',
|
|
104
|
-
'**/.playwright-mcp/*',
|
|
105
|
-
'**/.claude/*',
|
|
106
|
-
'**/.agents/*',
|
|
107
|
-
'**/.mimocode/*',
|
|
108
|
-
'**/OPTIMIZATION-PLAN.md',
|
|
109
|
-
]
|
|
110
|
-
|
|
111
|
-
# 必须齐全的关键文件(发布门禁)
|
|
112
|
-
REQUIRED = [
|
|
113
|
-
'SKILL.md', 'README.md', 'package.json',
|
|
114
|
-
'assets/pptx-export.js', 'assets/style-gallery.html',
|
|
115
|
-
'assets/theme-overview.png', 'assets/theme-overview-presentation.png',
|
|
116
|
-
'assets/theme-overview-research.png', 'assets/theme-overview-architecture.png',
|
|
117
|
-
'assets/templates/presentation.html', 'assets/templates/research.html',
|
|
118
|
-
'assets/templates/architecture.html', 'assets/templates/engine.css',
|
|
119
|
-
'assets/templates/ui.js',
|
|
120
|
-
'references/playbook.md',
|
|
121
|
-
'references/modes.md', 'references/outline-design.md', 'references/design-system.md',
|
|
122
|
-
'references/styles.md', 'references/content-rules.md', 'references/components.md',
|
|
123
|
-
'references/charts.md',
|
|
124
|
-
'references/infographics.md', 'references/icons.md', 'references/pptx-export.md',
|
|
125
|
-
'references/high-fidelity.md', 'references/failure-modes.md',
|
|
126
|
-
'references/tech-design.md', 'references/industry-benchmark.md',
|
|
127
|
-
'scripts/env_probe.py', 'scripts/audit_docs.py', 'scripts/audit_skill.py',
|
|
128
|
-
'scripts/scaffold_report.py',
|
|
129
|
-
'scripts/sync_runtime.py', 'scripts/audit_styles.py', 'scripts/build_examples.py',
|
|
130
|
-
'scripts/regression.py', 'scripts/validate_report.py', 'scripts/validate_pptx.py',
|
|
131
|
-
'scripts/extract_model.py', 'scripts/build_pptx.js', 'scripts/gen_channel_a.js',
|
|
132
|
-
'scripts/cross_verify.py', 'scripts/prepare_images.py', 'scripts/probe_image_export.py',
|
|
133
|
-
'scripts/render_compare.py', 'scripts/capture_theme_overview.js', 'scripts/package_skill.py',
|
|
134
|
-
'scripts/layout-constants.json', 'scripts/model-schema.json', 'scripts/layout_slots.json',
|
|
135
|
-
'scripts/lib_layout_regions.js', 'scripts/extract_snippet.py', 'scripts/quality_gate.py',
|
|
136
|
-
'scripts/checks_html.py', 'scripts/section-file-map.json',
|
|
137
|
-
'scripts/measure_height.py', 'scripts/audit_css.py', 'scripts/negative_tests.py',
|
|
138
|
-
'references/components-atoms.md', 'references/layouts-research.md',
|
|
139
|
-
'references/layouts-architecture.md', 'references/layouts-combo.md',
|
|
140
|
-
'references/charts-basic.md', 'references/charts-extended.md', 'references/charts-discipline.md',
|
|
141
|
-
'references/design-system-engine.md',
|
|
142
|
-
'references/infographics-stats.md', 'references/infographics-structure.md',
|
|
143
|
-
'evals/prompts.csv', 'evals/run_evals.py', 'evals/rubric.schema.json',
|
|
144
|
-
'evals/trace.example.json',
|
|
145
|
-
]
|
|
146
|
-
|
|
147
|
-
# 必须达到最小数量的集合(名称含日期,故按数量校验)
|
|
148
|
-
MIN_COUNTS = {
|
|
149
|
-
'assets/examples/*.html': 9,
|
|
150
|
-
'assets/examples/*.model.json': 9,
|
|
151
|
-
'references/*.md': 15,
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def picked(rel: str) -> bool:
|
|
156
|
-
return any(rel == p or _match(rel, p) for p in EXCLUDE)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def _match(rel: str, pat: str) -> bool:
|
|
160
|
-
if fnmatch.fnmatch(rel, pat):
|
|
161
|
-
return True
|
|
162
|
-
# '**/x/*' 需匹配任意深度
|
|
163
|
-
if pat.startswith('**/'):
|
|
164
|
-
tail = pat[3:]
|
|
165
|
-
return fnmatch.fnmatch(rel, tail) or fnmatch.fnmatch(rel, '*/' + tail)
|
|
166
|
-
return False
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
def check_skill_md():
|
|
170
|
-
"""frontmatter 门禁:name / description 必需,name 与包名一致,description ≤ 上限。"""
|
|
171
|
-
p = ROOT / 'SKILL.md'
|
|
172
|
-
if not p.exists():
|
|
173
|
-
return None, ['缺少 SKILL.md']
|
|
174
|
-
txt = p.read_text(encoding='utf-8')
|
|
175
|
-
if not txt.startswith('---'):
|
|
176
|
-
return None, ['SKILL.md 未以 YAML frontmatter(---)开头']
|
|
177
|
-
m = re.match(r'^---\s*\n(.*?)\n---\s*\n', txt, re.S)
|
|
178
|
-
if not m:
|
|
179
|
-
return None, ['SKILL.md frontmatter 未正确闭合']
|
|
180
|
-
fm = m.group(1)
|
|
181
|
-
errs = []
|
|
182
|
-
name_m = re.search(r'^name:\s*(\S+)\s*$', fm, re.M)
|
|
183
|
-
if not name_m:
|
|
184
|
-
errs.append('frontmatter 缺 name')
|
|
185
|
-
elif name_m.group(1) != NAME:
|
|
186
|
-
errs.append(f'frontmatter name={name_m.group(1)!r} 与分发包目录名 {NAME!r} 不一致')
|
|
187
|
-
desc_m = re.search(r'^description:\s*"(.*)"\s*$', fm, re.M)
|
|
188
|
-
if not desc_m:
|
|
189
|
-
errs.append('frontmatter 缺 description(须为单行双引号字符串)')
|
|
190
|
-
desc = ''
|
|
191
|
-
else:
|
|
192
|
-
desc = desc_m.group(1)
|
|
193
|
-
if len(desc) > DESC_LIMIT:
|
|
194
|
-
errs.append(f'description {len(desc)} 字符 > {DESC_LIMIT}')
|
|
195
|
-
if '<' in desc or '>' in desc:
|
|
196
|
-
errs.append('description 含尖括号(部分平台会拒载)')
|
|
197
|
-
ver_m = re.search(r'^version:\s*"?([\w.\-]+)"?\s*$', fm, re.M)
|
|
198
|
-
return {'name': NAME, 'description_len': len(desc),
|
|
199
|
-
'version': ver_m.group(1) if ver_m else VERSION}, errs
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def collect():
|
|
203
|
-
seen, files = set(), []
|
|
204
|
-
for pat in INCLUDE:
|
|
205
|
-
for h in sorted(ROOT.glob(pat)):
|
|
206
|
-
if not h.is_file():
|
|
207
|
-
continue
|
|
208
|
-
rel = h.relative_to(ROOT).as_posix()
|
|
209
|
-
if rel in seen or picked(rel):
|
|
210
|
-
continue
|
|
211
|
-
seen.add(rel)
|
|
212
|
-
files.append((rel, h))
|
|
213
|
-
return files
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def verify(files):
|
|
217
|
-
errs = []
|
|
218
|
-
have = {rel for rel, _ in files}
|
|
219
|
-
for rel in REQUIRED:
|
|
220
|
-
if rel not in have:
|
|
221
|
-
errs.append(f'缺必需文件: {rel}')
|
|
222
|
-
for pat, n in MIN_COUNTS.items():
|
|
223
|
-
got = len([r for r in have if _match(r, pat) or r == pat])
|
|
224
|
-
if got < n:
|
|
225
|
-
errs.append(f'{pat} 数量 {got} < {n}')
|
|
226
|
-
# 反向:不应出现的路径
|
|
227
|
-
for rel in have:
|
|
228
|
-
if rel.startswith('scripts/_') or '__pycache__' in rel or 'node_modules' in rel:
|
|
229
|
-
errs.append(f'不应进包: {rel}')
|
|
230
|
-
return errs
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def main():
|
|
234
|
-
check_only = '--check' in sys.argv[1:]
|
|
235
|
-
|
|
236
|
-
files = collect()
|
|
237
|
-
if not files:
|
|
238
|
-
print('错误:未收集到任何文件')
|
|
239
|
-
return 1
|
|
240
|
-
errs = verify(files)
|
|
241
|
-
meta, fm_errs = check_skill_md()
|
|
242
|
-
errs += fm_errs
|
|
243
|
-
|
|
244
|
-
print('── 发布前校验 ──')
|
|
245
|
-
print(f' 技能名 {NAME} · 版本 {meta.get("version") if meta else "?"} · '
|
|
246
|
-
f'description {meta.get("description_len") if meta else "?"} 字符')
|
|
247
|
-
print(f' 进包文件 {len(files)} 个')
|
|
248
|
-
if errs:
|
|
249
|
-
print(' 校验失败:')
|
|
250
|
-
for e in errs:
|
|
251
|
-
print(f' ✗ {e}')
|
|
252
|
-
return 1
|
|
253
|
-
print(' 校验通过:必需文件齐全 · frontmatter 合规 · 无临时/缓存路径')
|
|
254
|
-
|
|
255
|
-
if check_only:
|
|
256
|
-
print('\n(--check 模式:仅校验,不打包)')
|
|
257
|
-
return 0
|
|
258
|
-
|
|
259
|
-
OUT.parent.mkdir(parents=True, exist_ok=True)
|
|
260
|
-
with zipfile.ZipFile(OUT, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as z:
|
|
261
|
-
for rel, h in files:
|
|
262
|
-
z.write(h, f'{NAME}/{rel}')
|
|
263
|
-
|
|
264
|
-
entries, total = [], 0
|
|
265
|
-
for rel, h in files:
|
|
266
|
-
raw = h.read_bytes()
|
|
267
|
-
total += len(raw)
|
|
268
|
-
entries.append({'path': rel, 'bytes': len(raw),
|
|
269
|
-
'sha256': hashlib.sha256(raw).hexdigest()})
|
|
270
|
-
manifest = {
|
|
271
|
-
'skill': NAME,
|
|
272
|
-
'version': (meta or {}).get('version'),
|
|
273
|
-
'descriptionLength': (meta or {}).get('description_len'),
|
|
274
|
-
'builtAt': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
275
|
-
'zip': f'dist/{NAME}.zip',
|
|
276
|
-
'entries': len(entries),
|
|
277
|
-
'totalBytes': total,
|
|
278
|
-
'zipBytes': OUT.stat().st_size,
|
|
279
|
-
'files': entries,
|
|
280
|
-
}
|
|
281
|
-
MANIFEST.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding='utf-8')
|
|
282
|
-
|
|
283
|
-
print(f'\n── 打包完成 ──')
|
|
284
|
-
print(f' {OUT}')
|
|
285
|
-
print(f' 条目 {len(entries)} 个 · 原始 {total/1024:.0f} KB · 压缩 {OUT.stat().st_size/1024:.0f} KB')
|
|
286
|
-
print(f' {MANIFEST}(版本 / 条目 / SHA-256,随包留档)')
|
|
287
|
-
return 0
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if __name__ == '__main__':
|
|
291
|
-
sys.exit(main())
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""TopPPT HTML · 技能分发包打包器(发布前完整性校验 + 发布清单)
|
|
4
|
+
|
|
5
|
+
用法:
|
|
6
|
+
python scripts/package_skill.py # 校验 → 打包 → 写发布清单
|
|
7
|
+
python scripts/package_skill.py --check # 只校验不打包(CI / 提交前自检)
|
|
8
|
+
|
|
9
|
+
产物:
|
|
10
|
+
dist/top-ppt-html.zip 分发包(解压目录名 = 技能全名 top-ppt-html)
|
|
11
|
+
dist/top-ppt-html.manifest.json 发布清单(版本 / 条目 / 大小 / SHA-256),随包留档
|
|
12
|
+
|
|
13
|
+
────────────────────────────────────────────────────────────────────────
|
|
14
|
+
进包(= 技能运行与维护所需的最小完备集,缺一即校验失败)
|
|
15
|
+
────────────────────────────────────────────────────────────────────────
|
|
16
|
+
SKILL.md 技能入口(frontmatter + 触发边界 + 工作流 + 铁律)
|
|
17
|
+
README.md 人类视角的简介 / 安装 / 开发 / 打包
|
|
18
|
+
package.json Node 依赖声明(pptxgenjs)与常用命令
|
|
19
|
+
assets/templates/ 三份模式模板 + engine.css / ui.js(公共片段注入源)
|
|
20
|
+
assets/examples/ 9 份示例 HTML + 9 份 REPORT_MODEL(复制起点 · 覆盖全部 9 风格)
|
|
21
|
+
assets/pptx-export.js PPTX 预览运行时(注入源)
|
|
22
|
+
assets/style-gallery.html 风格 × 模式 × 亮暗主题交互画廊
|
|
23
|
+
assets/theme-overview*.png 4 张主题参考图(六项问询辅助)
|
|
24
|
+
references/*.md 15 篇规范(playbook.md = 唯一常读入口 L1;其余 L2 按需加载)
|
|
25
|
+
scripts/ 生成 / 校验 / 回归 / 维护工具(py + js + json 单源)
|
|
26
|
+
scripts/extract_snippet.py L2 节级片段抽取(减少整读大规范)
|
|
27
|
+
scripts/layout_slots.json 页型布局 IR(高频页型语义槽位)
|
|
28
|
+
|
|
29
|
+
────────────────────────────────────────────────────────────────────────
|
|
30
|
+
不进包(构建产物 / 本地状态 / 缓存,与 .gitignore 口径一致)
|
|
31
|
+
────────────────────────────────────────────────────────────────────────
|
|
32
|
+
dist/ 分发包与回归产物本身
|
|
33
|
+
report-assets/ prepare_images.py 的产出(片段 / 模型对象)
|
|
34
|
+
render-compare/ render_compare.py 的渲染对照产物
|
|
35
|
+
node_modules/ npm 依赖(安装期生成)
|
|
36
|
+
__pycache__/ Python 缓存
|
|
37
|
+
IDE / 智能体本地状态目录 IDE / 智能体本地状态(不入库、不分发)
|
|
38
|
+
scripts/_* 下划线开头的临时脚本(用后即删)
|
|
39
|
+
|
|
40
|
+
────────────────────────────────────────────────────────────────────────
|
|
41
|
+
发布前校验(任一不过 → 退出码 1,不产出 zip)
|
|
42
|
+
────────────────────────────────────────────────────────────────────────
|
|
43
|
+
· SKILL.md frontmatter 含 name / description;name 与分发包目录名一致;
|
|
44
|
+
description 为单行双引号字符串且 ≤ 1024 字符(平台截断阈值)
|
|
45
|
+
· 15 篇 references、3 份模式模板 + engine.css / ui.js、9 组示例、4 张参考图齐全
|
|
46
|
+
· 关键脚本齐全(单源注入 / 审计 / 示例重建 / 回归 / 双校验器 / 精导 / 打包)
|
|
47
|
+
"""
|
|
48
|
+
import fnmatch
|
|
49
|
+
import hashlib
|
|
50
|
+
import json
|
|
51
|
+
import re
|
|
52
|
+
import sys
|
|
53
|
+
import zipfile
|
|
54
|
+
from datetime import datetime, timezone
|
|
55
|
+
from pathlib import Path
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
59
|
+
except Exception:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
63
|
+
NAME = 'top-ppt-html' # 技能全名 = 分发包顶层目录名 = SKILL.md name
|
|
64
|
+
OUT = ROOT / 'dist' / f'{NAME}.zip'
|
|
65
|
+
MANIFEST = ROOT / 'dist' / f'{NAME}.manifest.json'
|
|
66
|
+
DESC_LIMIT = 1024
|
|
67
|
+
# 版本唯一事实源 = layout-constants.json `version`(v8.3 起 frontmatter 不携带非标 version 键,
|
|
68
|
+
# manifest 版本一律取 LC;frontmatter 若显式声明则以其为准——但不鼓励)
|
|
69
|
+
try:
|
|
70
|
+
VERSION = str(json.loads(
|
|
71
|
+
(ROOT / 'scripts' / 'layout-constants.json').read_text(encoding='utf-8')).get('version') or '0.0')
|
|
72
|
+
except (OSError, json.JSONDecodeError):
|
|
73
|
+
VERSION = '0.0'
|
|
74
|
+
|
|
75
|
+
# 进包清单(显式枚举,缺一即失败)
|
|
76
|
+
INCLUDE = [
|
|
77
|
+
'SKILL.md',
|
|
78
|
+
'README.md',
|
|
79
|
+
'package.json',
|
|
80
|
+
'assets/pptx-export.js',
|
|
81
|
+
'assets/style-gallery.html',
|
|
82
|
+
'assets/theme-overview.png',
|
|
83
|
+
'assets/theme-overview-presentation.png',
|
|
84
|
+
'assets/theme-overview-research.png',
|
|
85
|
+
'assets/theme-overview-architecture.png',
|
|
86
|
+
'assets/templates/*',
|
|
87
|
+
'assets/examples/*',
|
|
88
|
+
'references/*',
|
|
89
|
+
'scripts/*',
|
|
90
|
+
'evals/*',
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
# 出包规则(命中即跳过;与 .gitignore 口径一致)
|
|
94
|
+
EXCLUDE = [
|
|
95
|
+
'scripts/_*', # 临时脚本
|
|
96
|
+
'scripts/__pycache__/*',
|
|
97
|
+
'**/__pycache__/*',
|
|
98
|
+
'**/node_modules/*',
|
|
99
|
+
'**/report-assets/*',
|
|
100
|
+
'**/render-compare/*',
|
|
101
|
+
'**/dist/*',
|
|
102
|
+
'**/.codebuddy/*',
|
|
103
|
+
'**/.h3caiwork/*',
|
|
104
|
+
'**/.playwright-mcp/*',
|
|
105
|
+
'**/.claude/*',
|
|
106
|
+
'**/.agents/*',
|
|
107
|
+
'**/.mimocode/*',
|
|
108
|
+
'**/OPTIMIZATION-PLAN.md',
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
# 必须齐全的关键文件(发布门禁)
|
|
112
|
+
REQUIRED = [
|
|
113
|
+
'SKILL.md', 'README.md', 'package.json',
|
|
114
|
+
'assets/pptx-export.js', 'assets/style-gallery.html',
|
|
115
|
+
'assets/theme-overview.png', 'assets/theme-overview-presentation.png',
|
|
116
|
+
'assets/theme-overview-research.png', 'assets/theme-overview-architecture.png',
|
|
117
|
+
'assets/templates/presentation.html', 'assets/templates/research.html',
|
|
118
|
+
'assets/templates/architecture.html', 'assets/templates/engine.css',
|
|
119
|
+
'assets/templates/ui.js',
|
|
120
|
+
'references/playbook.md',
|
|
121
|
+
'references/modes.md', 'references/outline-design.md', 'references/design-system.md',
|
|
122
|
+
'references/styles.md', 'references/content-rules.md', 'references/components.md',
|
|
123
|
+
'references/charts.md',
|
|
124
|
+
'references/infographics.md', 'references/icons.md', 'references/pptx-export.md',
|
|
125
|
+
'references/high-fidelity.md', 'references/failure-modes.md',
|
|
126
|
+
'references/tech-design.md', 'references/industry-benchmark.md',
|
|
127
|
+
'scripts/env_probe.py', 'scripts/audit_docs.py', 'scripts/audit_skill.py',
|
|
128
|
+
'scripts/scaffold_report.py',
|
|
129
|
+
'scripts/sync_runtime.py', 'scripts/audit_styles.py', 'scripts/build_examples.py',
|
|
130
|
+
'scripts/regression.py', 'scripts/validate_report.py', 'scripts/validate_pptx.py',
|
|
131
|
+
'scripts/extract_model.py', 'scripts/build_pptx.js', 'scripts/gen_channel_a.js',
|
|
132
|
+
'scripts/cross_verify.py', 'scripts/prepare_images.py', 'scripts/probe_image_export.py',
|
|
133
|
+
'scripts/render_compare.py', 'scripts/capture_theme_overview.js', 'scripts/package_skill.py',
|
|
134
|
+
'scripts/layout-constants.json', 'scripts/model-schema.json', 'scripts/layout_slots.json',
|
|
135
|
+
'scripts/lib_layout_regions.js', 'scripts/extract_snippet.py', 'scripts/quality_gate.py',
|
|
136
|
+
'scripts/checks_html.py', 'scripts/section-file-map.json',
|
|
137
|
+
'scripts/measure_height.py', 'scripts/audit_css.py', 'scripts/negative_tests.py',
|
|
138
|
+
'references/components-atoms.md', 'references/layouts-research.md',
|
|
139
|
+
'references/layouts-architecture.md', 'references/layouts-combo.md',
|
|
140
|
+
'references/charts-basic.md', 'references/charts-extended.md', 'references/charts-discipline.md',
|
|
141
|
+
'references/design-system-engine.md',
|
|
142
|
+
'references/infographics-stats.md', 'references/infographics-structure.md',
|
|
143
|
+
'evals/prompts.csv', 'evals/run_evals.py', 'evals/rubric.schema.json',
|
|
144
|
+
'evals/trace.example.json',
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
# 必须达到最小数量的集合(名称含日期,故按数量校验)
|
|
148
|
+
MIN_COUNTS = {
|
|
149
|
+
'assets/examples/*.html': 9,
|
|
150
|
+
'assets/examples/*.model.json': 9,
|
|
151
|
+
'references/*.md': 15,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def picked(rel: str) -> bool:
|
|
156
|
+
return any(rel == p or _match(rel, p) for p in EXCLUDE)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _match(rel: str, pat: str) -> bool:
|
|
160
|
+
if fnmatch.fnmatch(rel, pat):
|
|
161
|
+
return True
|
|
162
|
+
# '**/x/*' 需匹配任意深度
|
|
163
|
+
if pat.startswith('**/'):
|
|
164
|
+
tail = pat[3:]
|
|
165
|
+
return fnmatch.fnmatch(rel, tail) or fnmatch.fnmatch(rel, '*/' + tail)
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def check_skill_md():
|
|
170
|
+
"""frontmatter 门禁:name / description 必需,name 与包名一致,description ≤ 上限。"""
|
|
171
|
+
p = ROOT / 'SKILL.md'
|
|
172
|
+
if not p.exists():
|
|
173
|
+
return None, ['缺少 SKILL.md']
|
|
174
|
+
txt = p.read_text(encoding='utf-8')
|
|
175
|
+
if not txt.startswith('---'):
|
|
176
|
+
return None, ['SKILL.md 未以 YAML frontmatter(---)开头']
|
|
177
|
+
m = re.match(r'^---\s*\n(.*?)\n---\s*\n', txt, re.S)
|
|
178
|
+
if not m:
|
|
179
|
+
return None, ['SKILL.md frontmatter 未正确闭合']
|
|
180
|
+
fm = m.group(1)
|
|
181
|
+
errs = []
|
|
182
|
+
name_m = re.search(r'^name:\s*(\S+)\s*$', fm, re.M)
|
|
183
|
+
if not name_m:
|
|
184
|
+
errs.append('frontmatter 缺 name')
|
|
185
|
+
elif name_m.group(1) != NAME:
|
|
186
|
+
errs.append(f'frontmatter name={name_m.group(1)!r} 与分发包目录名 {NAME!r} 不一致')
|
|
187
|
+
desc_m = re.search(r'^description:\s*"(.*)"\s*$', fm, re.M)
|
|
188
|
+
if not desc_m:
|
|
189
|
+
errs.append('frontmatter 缺 description(须为单行双引号字符串)')
|
|
190
|
+
desc = ''
|
|
191
|
+
else:
|
|
192
|
+
desc = desc_m.group(1)
|
|
193
|
+
if len(desc) > DESC_LIMIT:
|
|
194
|
+
errs.append(f'description {len(desc)} 字符 > {DESC_LIMIT}')
|
|
195
|
+
if '<' in desc or '>' in desc:
|
|
196
|
+
errs.append('description 含尖括号(部分平台会拒载)')
|
|
197
|
+
ver_m = re.search(r'^version:\s*"?([\w.\-]+)"?\s*$', fm, re.M)
|
|
198
|
+
return {'name': NAME, 'description_len': len(desc),
|
|
199
|
+
'version': ver_m.group(1) if ver_m else VERSION}, errs
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def collect():
|
|
203
|
+
seen, files = set(), []
|
|
204
|
+
for pat in INCLUDE:
|
|
205
|
+
for h in sorted(ROOT.glob(pat)):
|
|
206
|
+
if not h.is_file():
|
|
207
|
+
continue
|
|
208
|
+
rel = h.relative_to(ROOT).as_posix()
|
|
209
|
+
if rel in seen or picked(rel):
|
|
210
|
+
continue
|
|
211
|
+
seen.add(rel)
|
|
212
|
+
files.append((rel, h))
|
|
213
|
+
return files
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def verify(files):
|
|
217
|
+
errs = []
|
|
218
|
+
have = {rel for rel, _ in files}
|
|
219
|
+
for rel in REQUIRED:
|
|
220
|
+
if rel not in have:
|
|
221
|
+
errs.append(f'缺必需文件: {rel}')
|
|
222
|
+
for pat, n in MIN_COUNTS.items():
|
|
223
|
+
got = len([r for r in have if _match(r, pat) or r == pat])
|
|
224
|
+
if got < n:
|
|
225
|
+
errs.append(f'{pat} 数量 {got} < {n}')
|
|
226
|
+
# 反向:不应出现的路径
|
|
227
|
+
for rel in have:
|
|
228
|
+
if rel.startswith('scripts/_') or '__pycache__' in rel or 'node_modules' in rel:
|
|
229
|
+
errs.append(f'不应进包: {rel}')
|
|
230
|
+
return errs
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def main():
|
|
234
|
+
check_only = '--check' in sys.argv[1:]
|
|
235
|
+
|
|
236
|
+
files = collect()
|
|
237
|
+
if not files:
|
|
238
|
+
print('错误:未收集到任何文件')
|
|
239
|
+
return 1
|
|
240
|
+
errs = verify(files)
|
|
241
|
+
meta, fm_errs = check_skill_md()
|
|
242
|
+
errs += fm_errs
|
|
243
|
+
|
|
244
|
+
print('── 发布前校验 ──')
|
|
245
|
+
print(f' 技能名 {NAME} · 版本 {meta.get("version") if meta else "?"} · '
|
|
246
|
+
f'description {meta.get("description_len") if meta else "?"} 字符')
|
|
247
|
+
print(f' 进包文件 {len(files)} 个')
|
|
248
|
+
if errs:
|
|
249
|
+
print(' 校验失败:')
|
|
250
|
+
for e in errs:
|
|
251
|
+
print(f' ✗ {e}')
|
|
252
|
+
return 1
|
|
253
|
+
print(' 校验通过:必需文件齐全 · frontmatter 合规 · 无临时/缓存路径')
|
|
254
|
+
|
|
255
|
+
if check_only:
|
|
256
|
+
print('\n(--check 模式:仅校验,不打包)')
|
|
257
|
+
return 0
|
|
258
|
+
|
|
259
|
+
OUT.parent.mkdir(parents=True, exist_ok=True)
|
|
260
|
+
with zipfile.ZipFile(OUT, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as z:
|
|
261
|
+
for rel, h in files:
|
|
262
|
+
z.write(h, f'{NAME}/{rel}')
|
|
263
|
+
|
|
264
|
+
entries, total = [], 0
|
|
265
|
+
for rel, h in files:
|
|
266
|
+
raw = h.read_bytes()
|
|
267
|
+
total += len(raw)
|
|
268
|
+
entries.append({'path': rel, 'bytes': len(raw),
|
|
269
|
+
'sha256': hashlib.sha256(raw).hexdigest()})
|
|
270
|
+
manifest = {
|
|
271
|
+
'skill': NAME,
|
|
272
|
+
'version': (meta or {}).get('version'),
|
|
273
|
+
'descriptionLength': (meta or {}).get('description_len'),
|
|
274
|
+
'builtAt': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
275
|
+
'zip': f'dist/{NAME}.zip',
|
|
276
|
+
'entries': len(entries),
|
|
277
|
+
'totalBytes': total,
|
|
278
|
+
'zipBytes': OUT.stat().st_size,
|
|
279
|
+
'files': entries,
|
|
280
|
+
}
|
|
281
|
+
MANIFEST.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding='utf-8')
|
|
282
|
+
|
|
283
|
+
print(f'\n── 打包完成 ──')
|
|
284
|
+
print(f' {OUT}')
|
|
285
|
+
print(f' 条目 {len(entries)} 个 · 原始 {total/1024:.0f} KB · 压缩 {OUT.stat().st_size/1024:.0f} KB')
|
|
286
|
+
print(f' {MANIFEST}(版本 / 条目 / SHA-256,随包留档)')
|
|
287
|
+
return 0
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
if __name__ == '__main__':
|
|
291
|
+
sys.exit(main())
|