@topmindspace/tms-skills 0.1.1 → 0.1.3

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 (64) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +13 -3
  3. package/bin/tms-skills.js +7 -2
  4. package/package.json +1 -1
  5. package/top-ppt-html/README.md +69 -148
  6. package/top-ppt-html/SKILL.md +42 -23
  7. package/top-ppt-html/agents/openai.yaml +5 -0
  8. package/top-ppt-html/assets/theme-overview-architecture.png +0 -0
  9. package/top-ppt-html/assets/theme-overview-research.png +0 -0
  10. package/top-ppt-html/assets/theme-overview.png +0 -0
  11. package/top-ppt-html/evals/trigger-queries.json +181 -0
  12. package/top-ppt-html/package.json +3 -2
  13. package/top-ppt-html/references/chart-decision-tree.md +42 -0
  14. package/top-ppt-html/references/charts-discipline.md +3 -1
  15. package/top-ppt-html/references/charts.md +24 -15
  16. package/top-ppt-html/references/content-rules.md +54 -34
  17. package/top-ppt-html/references/default-surface.md +62 -0
  18. package/top-ppt-html/references/design-system.md +2 -2
  19. package/top-ppt-html/references/failure-modes.md +14 -1
  20. package/top-ppt-html/references/icons.md +56 -277
  21. package/top-ppt-html/references/illustration-layout.md +39 -0
  22. package/top-ppt-html/references/layout-grammar.md +16 -6
  23. package/top-ppt-html/references/modes.md +16 -11
  24. package/top-ppt-html/references/outline-design.md +24 -0
  25. package/top-ppt-html/references/page-type-matrix.md +35 -0
  26. package/top-ppt-html/references/playbook.md +42 -86
  27. package/top-ppt-html/references/pptx-export.md +20 -2
  28. package/top-ppt-html/references/presentation-craft.md +77 -0
  29. package/top-ppt-html/references/tech-design.md +11 -5
  30. package/top-ppt-html/scripts/audit_docs.py +1 -1
  31. package/top-ppt-html/scripts/audit_skill.py +45 -6
  32. package/top-ppt-html/scripts/build_examples.py +39 -2260
  33. package/top-ppt-html/scripts/build_pptx.js +6 -0
  34. package/top-ppt-html/scripts/check_triggers.py +153 -0
  35. package/top-ppt-html/scripts/checks_html.py +39 -0
  36. package/top-ppt-html/scripts/cross_verify.py +5 -1
  37. package/top-ppt-html/scripts/extract_snippet.py +2 -2
  38. package/top-ppt-html/scripts/layout-constants.json +111 -18
  39. package/top-ppt-html/scripts/lib_layout_regions.js +7 -9
  40. package/top-ppt-html/scripts/negative_tests.py +137 -1
  41. package/top-ppt-html/scripts/package_skill.py +29 -10
  42. package/top-ppt-html/scripts/quality_gate.py +11 -3
  43. package/top-ppt-html/scripts/recommend_layout.py +384 -0
  44. package/top-ppt-html/scripts/regression.py +1 -1
  45. package/top-ppt-html/scripts/smoke_pptx.sh +30 -0
  46. package/top-ppt-html/scripts/sync_runtime.py +22 -25
  47. package/top-ppt-html/scripts/validate_pptx.py +69 -0
  48. package/top-ppt-html/scripts/validate_report.py +336 -10
  49. package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.html +0 -3926
  50. package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.model.json +0 -168
  51. package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.html +0 -4325
  52. package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.model.json +0 -321
  53. package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.html +0 -4325
  54. package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.model.json +0 -321
  55. package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.html +0 -5527
  56. package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.model.json +0 -914
  57. package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.html +0 -5527
  58. package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.model.json +0 -914
  59. package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.html +0 -5527
  60. package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.model.json +0 -914
  61. package/top-ppt-html/references/design-system-engine.md +0 -235
  62. package/top-ppt-html/references/industry-benchmark.md +0 -105
  63. package/top-ppt-html/references/reform-plan.md +0 -252
  64. package/top-ppt-html/scripts/layout_slots.json +0 -830
@@ -20,6 +20,10 @@
20
20
  N12 模型 theme 与 data-theme 矛盾
21
21
  N13 模型 mode 与 data-mode 矛盾
22
22
  N14 内部锚点断裂(链接目标 id 不存在)
23
+ L4 截断迹象(列表项省略号砍义)
24
+ L5 溢出未拆页
25
+ L6 混排缺对齐(ALIGN_RHYTHM)
26
+ N18 配图页缺 caption/图注(IMAGE_CAPTION)
23
27
 
24
28
  用法: python scripts/negative_tests.py
25
29
  """
@@ -62,6 +66,25 @@ def expect_fail(name: str, path: Path, keyword: str) -> bool:
62
66
  return ok
63
67
 
64
68
 
69
+
70
+ def expect_layout_qa_fail(name: str, path: Path, keyword: str) -> bool:
71
+ """Run validate_report --strict --layout-qa and require FAIL containing keyword."""
72
+ r = subprocess.run(
73
+ [PY, str(ROOT / 'scripts' / 'validate_report.py'), str(path),
74
+ '--strict', '--layout-qa'],
75
+ capture_output=True, text=True, encoding='utf-8', errors='replace')
76
+ out = (r.stdout or '') + (r.stderr or '')
77
+ hit = [ln for ln in out.splitlines()
78
+ if keyword in ln and ('[FAIL]' in ln or 'FAIL' in ln)]
79
+ ok = r.returncode != 0 and bool(hit)
80
+ print(f' [{"OK" if ok else "FAIL"}] {name}')
81
+ if hit:
82
+ print(f' 抓到: {hit[0].strip()[:120]}')
83
+ elif not ok:
84
+ print(f' layout-qa 未触发(期望含「{keyword}」)')
85
+ return ok
86
+
87
+
65
88
  def main() -> int:
66
89
  base = ROOT / 'assets' / 'examples' / '2026-09-09-research-mckinsey.html'
67
90
  if not base.exists():
@@ -154,6 +177,15 @@ def main() -> int:
154
177
  '"footnote": "<strong>x</strong> ', 1)
155
178
  cases.append(('N17 模型夹带标签', t17, 'HTML_TAG_IN_TEXT'))
156
179
 
180
+ # N18 配图页缺 caption(有 <img>/media 无图注类)
181
+ t18 = txt.replace(
182
+ '</body>',
183
+ '<section class="band" data-page-type="image">'
184
+ '<div class="media media--full">'
185
+ '<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="注入无图注">'
186
+ '</div></section></body>', 1)
187
+ cases.append(('N18 配图缺 caption', t18, 'IMAGE_CAPTION'))
188
+
157
189
  ok = True
158
190
  for name, content, keyword in cases:
159
191
  if content == txt:
@@ -163,7 +195,111 @@ def main() -> int:
163
195
  p.write_text(content, encoding='utf-8')
164
196
  ok = expect_fail(name, p, keyword) and ok
165
197
 
166
- ok = _pptx_notes_case() and ok
198
+
199
+ # ── Batch 2 · layout-qa 专用负例 ─────────────────────────────────────
200
+ # L1 管线产物缺 data-skel:先造带 skel 的页,再撕掉其中一页的标记
201
+ import tempfile
202
+ scaf = OUT / 'L1-scaffold.html'
203
+ subprocess.run(
204
+ [PY, str(ROOT / 'scripts' / 'scaffold_report.py'),
205
+ '--mode', 'presentation', '--style', 'business-blue', '--theme', 'light',
206
+ '--title', 'LayoutQA Neg', '--sections', '5', '--out', str(scaf)],
207
+ capture_output=True, text=True)
208
+ if scaf.exists() and 'data-skel="' in scaf.read_text(encoding='utf-8'):
209
+ t_l1 = scaf.read_text(encoding='utf-8')
210
+ # 撕掉第一个内容页的 data-skel
211
+ t_l1b = re.sub(r'(id="s1") data-skel="P\d+"', r'\1', t_l1, count=1)
212
+ p_l1 = OUT / 'L1-missing-skel.html'
213
+ p_l1.write_text(t_l1b, encoding='utf-8')
214
+ ok = expect_layout_qa_fail('L1 缺 data-skel', p_l1, 'LAYOUT_QA_MISSING_SKEL') and ok
215
+ # L2 连续 3 页同一 skel
216
+ t_l2 = t_l1
217
+ # force s1,s2,s3 all P4
218
+ t_l2 = re.sub(r'(id="s1"[^>]*data-skel=")P\d+"', r'\1P4"', t_l2, count=1)
219
+ t_l2 = re.sub(r'(id="s2"[^>]*data-skel=")P\d+"', r'\1P4"', t_l2, count=1)
220
+ t_l2 = re.sub(r'(id="s3"[^>]*data-skel=")P\d+"', r'\1P4"', t_l2, count=1)
221
+ # also rewrite if attribute order is data-skel before id
222
+ t_l2 = re.sub(r'(data-skel=")P\d+(" data-page-type="[^"]+" id="s1")', r'\1P4\2', t_l2)
223
+ t_l2 = re.sub(r'(data-skel=")P\d+("([^>]*) id="s1")', r'\1P4\2', t_l2)
224
+ # simpler: replace first three data-skel values
225
+ def _force_p4(src, n=3):
226
+ out, c = [], 0
227
+ for part in re.split(r'(data-skel="P\d+")', src):
228
+ if part.startswith('data-skel="') and c < n:
229
+ out.append('data-skel="P4"'); c += 1
230
+ else:
231
+ out.append(part)
232
+ return ''.join(out)
233
+ t_l2 = _force_p4(t_l1, 3)
234
+ p_l2 = OUT / 'L2-skel-streak.html'
235
+ p_l2.write_text(t_l2, encoding='utf-8')
236
+ ok = expect_layout_qa_fail('L2 连续同 skel', p_l2, 'LAYOUT_QA_SKEL_STREAK') and ok
237
+ # L3 极偏 donut in REPORT_MODEL
238
+ t_l3 = t_l1
239
+ if 'window.REPORT_MODEL' in t_l3:
240
+ # inject a skewed donut section into model JSON
241
+ inj = (
242
+ '{"type":"donut","title":"极偏环",'
243
+ '"chart":{"type":"donut","labels":["A","B"],"values":[0.5,99.5]}},'
244
+ )
245
+ t_l3 = re.sub(r'("sections"\s*:\s*\[)', r'\1' + inj, t_l3, count=1)
246
+ p_l3 = OUT / 'L3-skew-donut.html'
247
+ p_l3.write_text(t_l3, encoding='utf-8')
248
+ ok = expect_layout_qa_fail('L3 极偏 donut', p_l3, 'LAYOUT_QA_SKEW_DONUT') and ok
249
+
250
+ # L4 截断迹象:列表项以省略号砍义
251
+ t_l4 = t_l1
252
+ inj4 = '<div class="card"><ul><li class="card__li">这项证据其实很长但被故意截断了…</li></ul></div>'
253
+ t_l4 = re.sub(r'(id="s1"[^>]*>)', lambda m: m.group(1) + inj4, t_l4, count=1)
254
+ p_l4 = OUT / 'L4-truncation.html'
255
+ p_l4.write_text(t_l4, encoding='utf-8')
256
+ ok = expect_layout_qa_fail('L4 截断迹象', p_l4, 'LAYOUT_QA_TRUNCATION') and ok
257
+
258
+ # L5 溢出未拆页:塞入超长正文使页高估算爆掉且无续页信号
259
+ t_l5 = t_l1
260
+ wall = '论证要点' + ('详细证据与口径说明,必须完整保留不得删减。' * 40)
261
+ t_l5 = re.sub(
262
+ r'(id="s1"[^>]*>)',
263
+ lambda m: m.group(1) + '<div class="card"><p>' + wall + '</p></div>'
264
+ + '<div class="card"><p>' + wall + '</p></div>'
265
+ + '<div class="card"><p>' + wall + '</p></div>',
266
+ t_l5, count=1)
267
+ p_l5 = OUT / 'L5-overflow-nosplit.html'
268
+ p_l5.write_text(t_l5, encoding='utf-8')
269
+ ok = expect_layout_qa_fail('L5 溢出未拆页', p_l5, 'LAYOUT_QA_OVERFLOW_NO_SPLIT') and ok
270
+
271
+ # L6 混排缺对齐:图+卡同页但无对齐类
272
+ t_l6 = t_l1
273
+ inj6 = (
274
+ '<div class="grid g-2">'
275
+ '<div class="fig"><svg class="chart" data-chart="bar" style="height:200px"></svg></div>'
276
+ '<div class="card"><ul><li>证据甲</li><li>证据乙</li></ul></div>'
277
+ '</div>'
278
+ )
279
+ t_l6 = re.sub(r'(id="s1"[^>]*>)', lambda m: m.group(1) + inj6, t_l6, count=1)
280
+ # 撕掉 s1 段内对齐类,确保 ALIGN_RHYTHM 能抓住
281
+ def _strip_align_s1(src: str) -> str:
282
+ m = re.search(r'(<section[^>]*id="s1"[^>]*>)([\s\S]*?)(</section>)', src)
283
+ if not m:
284
+ # attribute order may be data-skel before id
285
+ m = re.search(r'(<section[^>]*id="s1"[^>]*>)([\s\S]*?)(</section>)', src)
286
+ if not m:
287
+ return src
288
+ body = m.group(2)
289
+ body = re.sub(r'\ba-start\b', '', body)
290
+ body = re.sub(r'\ba-c\b', '', body)
291
+ body = re.sub(r'\ba-end\b', '', body)
292
+ body = body.replace('align-items:start', '').replace('align-items: center', '')
293
+ return src[:m.start()] + m.group(1) + body + m.group(3) + src[m.end():]
294
+ t_l6 = _strip_align_s1(t_l6)
295
+ p_l6 = OUT / 'L6-align-rhythm.html'
296
+ p_l6.write_text(t_l6, encoding='utf-8')
297
+ ok = expect_layout_qa_fail('L6 混排缺对齐', p_l6, 'LAYOUT_QA_ALIGN_RHYTHM') and ok
298
+
299
+ else:
300
+ print(' [SKIP] layout-qa 负例(scaffold 未产出 data-skel)')
301
+
302
+ ok = _pptx_notes_case() and ok
167
303
  ok = _chart_data_case() and ok
168
304
  ok = _title_pattern_case() and ok
169
305
  ok = _font_scale_case() and ok
@@ -17,14 +17,14 @@
17
17
  README.md 人类视角的简介 / 安装 / 开发 / 打包
18
18
  package.json Node 依赖声明(pptxgenjs)与常用命令
19
19
  assets/templates/ 三份模式模板 + engine.css / ui.js(公共片段注入源)
20
- assets/examples/ 9 份示例 HTML + 9 份 REPORT_MODEL(复制起点 · 覆盖全部 9 风格)
20
+ assets/examples/ 3 份黄金样张 HTML + model(每模式 1;风格见 style-gallery)
21
21
  assets/pptx-export.js PPTX 预览运行时(注入源)
22
22
  assets/style-gallery.html 风格 × 模式 × 亮暗主题交互画廊
23
23
  assets/theme-overview*.png 3 张主题参考图(整体 + research + architecture;演示态与整体图相同)
24
- references/*.md 15 篇规范(playbook.md = 唯一常读入口 L1;其余 L2 按需加载)
24
+ references/*.md 规范全文(Batch3 起 ≥20 篇;playbook.md = L1;其余 L2 按需)
25
25
  scripts/ 生成 / 校验 / 回归 / 维护工具(py + js + json 单源)
26
26
  scripts/extract_snippet.py L2 节级片段抽取(减少整读大规范)
27
- scripts/layout_slots.json 页型布局 IR(高频页型语义槽位)
27
+ # layoutSlots 已并入 layout-constants.json(Batch 3)
28
28
 
29
29
  ────────────────────────────────────────────────────────────────────────
30
30
  不进包(构建产物 / 本地状态 / 缓存,与 .gitignore 口径一致)
@@ -42,7 +42,7 @@
42
42
  ────────────────────────────────────────────────────────────────────────
43
43
  · SKILL.md frontmatter 含 name / description;name 与分发包目录名一致;
44
44
  description 为单行双引号字符串且 ≤ 1024 字符(平台截断阈值)
45
- · references(含 layout-grammar)、3 份模式模板 + engine.css / ui.js、9 组示例、3 张参考图齐全
45
+ · references(含 layout-grammar)、3 份模式模板 + engine.css / ui.js、3 组黄金样张、3 张参考图齐全
46
46
  · 关键脚本齐全(单源注入 / 审计 / 示例重建 / 回归 / 双校验器 / 精导 / 打包)
47
47
  """
48
48
  import fnmatch
@@ -87,6 +87,7 @@ INCLUDE = [
87
87
  'references/*',
88
88
  'scripts/*',
89
89
  'evals/*',
90
+ 'agents/*',
90
91
  ]
91
92
 
92
93
  # 出包规则(命中即跳过;与 .gitignore 口径一致)
@@ -118,37 +119,45 @@ REQUIRED = [
118
119
  'assets/templates/ui.js',
119
120
  'references/playbook.md',
120
121
  'references/layout-grammar.md',
122
+ 'references/default-surface.md',
123
+ 'references/presentation-craft.md',
124
+ 'references/illustration-layout.md',
125
+ 'references/page-type-matrix.md',
126
+ 'references/chart-decision-tree.md',
121
127
  'references/modes.md', 'references/outline-design.md', 'references/design-system.md',
122
128
  'references/styles.md', 'references/content-rules.md', 'references/components.md',
123
129
  'references/charts.md',
124
130
  'references/infographics.md', 'references/icons.md', 'references/pptx-export.md',
125
131
  'references/high-fidelity.md', 'references/failure-modes.md',
126
- 'references/tech-design.md', 'references/industry-benchmark.md',
132
+ 'references/tech-design.md',
127
133
  'scripts/env_probe.py', 'scripts/audit_docs.py', 'scripts/audit_skill.py',
128
134
  'scripts/scaffold_report.py',
135
+ 'scripts/render_from_model.py',
136
+ 'scripts/recommend_layout.py',
129
137
  'scripts/sync_runtime.py', 'scripts/audit_styles.py', 'scripts/build_examples.py',
130
138
  'scripts/regression.py', 'scripts/validate_report.py', 'scripts/validate_pptx.py',
131
139
  'scripts/extract_model.py', 'scripts/build_pptx.js', 'scripts/gen_channel_a.js',
132
140
  'scripts/cross_verify.py', 'scripts/prepare_images.py', 'scripts/probe_image_export.py',
133
141
  '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',
142
+ 'scripts/layout-constants.json', 'scripts/model-schema.json',
135
143
  'scripts/lib_layout_regions.js', 'scripts/extract_snippet.py', 'scripts/quality_gate.py',
136
144
  'scripts/checks_html.py', 'scripts/section-file-map.json',
137
145
  'scripts/measure_height.py', 'scripts/audit_css.py', 'scripts/negative_tests.py',
138
146
  'references/components-atoms.md', 'references/layouts-research.md',
139
147
  'references/layouts-architecture.md', 'references/layouts-combo.md',
140
148
  'references/charts-basic.md', 'references/charts-extended.md', 'references/charts-discipline.md',
141
- 'references/design-system-engine.md',
142
149
  'references/infographics-stats.md', 'references/infographics-structure.md',
143
150
  'evals/prompts.csv', 'evals/run_evals.py', 'evals/rubric.schema.json',
144
151
  'evals/trace.example.json',
152
+ 'evals/trigger-queries.json',
153
+ 'scripts/check_triggers.py',
145
154
  ]
146
155
 
147
156
  # 必须达到最小数量的集合(名称含日期,故按数量校验)
148
157
  MIN_COUNTS = {
149
- 'assets/examples/*.html': 9,
150
- 'assets/examples/*.model.json': 9,
151
- 'references/*.md': 15,
158
+ 'assets/examples/*.html': 3, # Batch3 每模式 1 份黄金样张
159
+ 'assets/examples/*.model.json': 3,
160
+ 'references/*.md': 25, # + page-type-matrix + chart-decision-tree;≥25 防误删
152
161
  }
153
162
 
154
163
 
@@ -252,6 +261,16 @@ def main():
252
261
  return 1
253
262
  print(' 校验通过:必需文件齐全 · frontmatter 合规 · 无临时/缓存路径')
254
263
 
264
+ # P0-1 trigger heuristic gate(廉价、无 LLM)
265
+ trig = ROOT / 'scripts' / 'check_triggers.py'
266
+ if trig.exists():
267
+ import subprocess
268
+ r = subprocess.run([sys.executable, str(trig)], cwd=str(ROOT))
269
+ if r.returncode != 0:
270
+ print(' ✗ trigger coverage 失败(evals/trigger-queries.json)')
271
+ return 1
272
+ print(' ✓ trigger coverage 通过')
273
+
255
274
  if check_only:
256
275
  print('\n(--check 模式:仅校验,不打包)')
257
276
  return 0
@@ -4,6 +4,7 @@
4
4
 
5
5
  用法:
6
6
  python scripts/quality_gate.py report.html
7
+ # Mode A / presentation 自动对 validate_report 附加 --layout-qa
7
8
  python scripts/quality_gate.py report.html --pptx report.pptx --model report.model.json
8
9
  python scripts/quality_gate.py report.html --trace trace.json --json
9
10
  python scripts/quality_gate.py report.html --require-rubric 60
@@ -241,10 +242,17 @@ def main() -> int:
241
242
  print(f'交付质量门禁 · {html.name}')
242
243
  print('-' * 60)
243
244
 
244
- # ① HTML strict
245
- rc, out, el = run([sys.executable, str(ROOT / 'scripts' / 'validate_report.py'), str(html), '--strict'])
245
+ # ① HTML strict(Mode A / presentation 自动附带 --layout-qa)
246
+ head_snip = html.read_text(encoding='utf-8')[:2500]
247
+ mode_m = re.search(r'data-mode="([^"]+)"', head_snip)
248
+ mode = mode_m.group(1) if mode_m else 'presentation'
249
+ vcmd = [sys.executable, str(ROOT / 'scripts' / 'validate_report.py'), str(html), '--strict']
250
+ if mode == 'presentation':
251
+ vcmd.append('--layout-qa')
252
+ rc, out, el = run(vcmd)
253
+ gate_name = 'validate_report --strict' + (' --layout-qa' if mode == 'presentation' else '')
246
254
  tail = [ln for ln in out.splitlines() if ln.startswith('PASS ') or ln.startswith('结论')]
247
- gate('validate_report --strict', rc == 0, tail[-1] if tail else f'exit {rc}', el)
255
+ gate(gate_name, rc == 0, tail[-1] if tail else f'exit {rc}', el)
248
256
 
249
257
  # ② PPTX strict(可选)
250
258
  if args.pptx: