@lark-apaas/coding-steering 0.1.32-dev.6f4e4bc → 0.1.32-dev.88fb097

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 (42) hide show
  1. package/package.json +1 -1
  2. package/steering/design-html/skills/charts/SKILL.md +11 -4
  3. package/steering/design-html/skills/data-viz/SKILL.md +1 -1
  4. package/steering/design-html/skills/hi-fi-design/SKILL.md +1 -1
  5. package/steering/design-html/skills/interactive-prototype/SKILL.md +1 -17
  6. package/steering/design-html/skills/pptx-style-extract/SKILL.md +2 -2
  7. package/steering/design-html/skills/pptx-style-extract/scripts/draft.py +56 -19
  8. package/steering/design-html/skills/pptx-style-extract/scripts/test_design_consumer_contract.py +7 -0
  9. package/steering/design-html/skills/pptx-style-extract/scripts/test_layout_css.py +199 -0
  10. package/steering/design-html/skills/pptx-style-extract/scripts/test_logo_scope.py +308 -9
  11. package/steering/design-html/skills/pptx-style-extract/scripts/verify_layout_assets.py +262 -47
  12. package/steering/design-html/skills/slide-deck/SKILL.md +5 -3
  13. package/steering/design-html/skills/social-card/SKILL.md +263 -0
  14. package/steering/nestjs-react-fullstack/skills/app-init-feasibility-guide/SKILL.md +3 -0
  15. package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +2 -1
  16. package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +2 -0
  17. package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +6 -0
  18. package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +56 -14
  19. package/steering/nestjs-react-fullstack/skills/contacts-service/SKILL.md +67 -4
  20. package/steering/nestjs-react-fullstack/skills/nestjs-cache/SKILL.md +0 -1
  21. package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +75 -0
  22. package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +10 -4
  23. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +57 -14
  24. package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +1 -1
  25. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/SKILL.md +78 -0
  26. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/assets/eslint.mcp-ui.config.cjs +19 -0
  27. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/assets/ui-tsconfig.json +19 -0
  28. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/client-onboarding.md +90 -0
  29. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/debugging.md +81 -0
  30. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/mcp-apps.md +197 -0
  31. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/tool-authoring.md +249 -0
  32. package/steering/nestjs-react-fullstack/skills_common/user-identity/SKILL.md +3 -1
  33. package/steering/nestjs-react-fullstack/skills_local/client-builtins-user-service/SKILL.md +2 -0
  34. package/steering/nestjs-react-fullstack/skills_local/code-fix/SKILL.md +6 -0
  35. package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +49 -22
  36. package/steering/nestjs-react-fullstack/skills_local/plugin-guide/SKILL.md +7 -3
  37. package/steering/nestjs-react-fullstack/skills_local/plugin-guide/references/plugin-coding-guide.md +57 -14
  38. package/steering/vite-react/skills/plugin-guide/SKILL.md +1 -0
  39. package/steering/vite-react/skills/react-three-fiber/SKILL.md +1 -0
  40. package/steering/vite-react/tech.md +3 -0
  41. package/steering/design-html/skills/preflight/SKILL.md +0 -51
  42. package/steering/design-html/skills/preflight/scripts/probe.sh +0 -108
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """Regression tests for template asset placement in generated deck HTML."""
3
3
  import os
4
+ import shutil
4
5
  import subprocess
5
6
  import sys
6
7
  import tempfile
@@ -12,7 +13,22 @@ from verify_layout_assets import validate_layout_assets
12
13
 
13
14
 
14
15
  class LayoutAssetContractTest(unittest.TestCase):
16
+ def write_asset(self, root, relative_path, contents, copied_prefix):
17
+ source = os.path.join(root, 'assets', relative_path)
18
+ copied = os.path.join(root, copied_prefix, relative_path)
19
+ os.makedirs(os.path.dirname(source), exist_ok=True)
20
+ os.makedirs(os.path.dirname(copied), exist_ok=True)
21
+ with open(source, 'wb') as stream:
22
+ stream.write(contents)
23
+ shutil.copyfile(source, copied)
24
+
15
25
  def write_pack(self, root):
26
+ self.write_asset(
27
+ root,
28
+ 'logos/1.png',
29
+ b'logo',
30
+ 'assets/pptx-volcengine',
31
+ )
16
32
  with open(os.path.join(root, 'design.md'), 'w', encoding='utf-8') as stream:
17
33
  stream.write(
18
34
  '---\n'
@@ -50,6 +66,12 @@ class LayoutAssetContractTest(unittest.TestCase):
50
66
  return path
51
67
 
52
68
  def write_texture_pack(self, root):
69
+ self.write_asset(
70
+ root,
71
+ 'textures/1.webp',
72
+ b'texture',
73
+ 'assets/pptx-claude',
74
+ )
53
75
  with open(os.path.join(root, 'design.md'), 'w', encoding='utf-8') as stream:
54
76
  stream.write(
55
77
  '---\n'
@@ -77,6 +99,12 @@ class LayoutAssetContractTest(unittest.TestCase):
77
99
  )
78
100
 
79
101
  def write_background_pack(self, root):
102
+ self.write_asset(
103
+ root,
104
+ 'backgrounds/content.webp',
105
+ b'background',
106
+ 'assets/pptx-claude',
107
+ )
80
108
  with open(os.path.join(root, 'design.md'), 'w', encoding='utf-8') as stream:
81
109
  stream.write(
82
110
  '---\n'
@@ -100,6 +128,31 @@ class LayoutAssetContractTest(unittest.TestCase):
100
128
  '---\n'
101
129
  )
102
130
 
131
+ def write_theme_pack(self, root):
132
+ with open(os.path.join(root, 'design.md'), 'w', encoding='utf-8') as stream:
133
+ stream.write(
134
+ '---\n'
135
+ 'themes: [dark, light]\n'
136
+ 'default-theme: dark\n'
137
+ 'layouts: layouts.md\n'
138
+ '---\n'
139
+ )
140
+ with open(os.path.join(root, 'layouts.md'), 'w', encoding='utf-8') as stream:
141
+ stream.write(
142
+ '---\n'
143
+ 'canvas: 1920x1080\n'
144
+ 'layouts:\n'
145
+ ' dark-content:\n'
146
+ ' role: content\n'
147
+ ' themes: [dark]\n'
148
+ ' slots: []\n'
149
+ ' light-content:\n'
150
+ ' role: content\n'
151
+ ' themes: [light]\n'
152
+ ' slots: []\n'
153
+ '---\n'
154
+ )
155
+
103
156
  def test_logo_is_limited_to_every_archetype_with_its_slot(self):
104
157
  with tempfile.TemporaryDirectory() as root:
105
158
  self.write_pack(root)
@@ -107,10 +160,14 @@ class LayoutAssetContractTest(unittest.TestCase):
107
160
  root,
108
161
  '<deck-stage>'
109
162
  '<section data-pptx-layout="cover">'
110
- '<img src="assets/pptx-volcengine/logos/1.png"></section>'
163
+ '<img data-pptx-asset="logo-1" '
164
+ 'style="position:absolute;left:64px;top:64px;width:224px;height:48px" '
165
+ 'src="assets/pptx-volcengine/logos/1.png"></section>'
111
166
  '<section data-pptx-layout="content"><h1>正文</h1></section>'
112
167
  '<section data-pptx-layout="closing">'
113
- '<img src="assets/pptx-volcengine/logos/1.png"></section>'
168
+ '<img data-pptx-asset="logo-1" '
169
+ 'style="position:absolute;left:64px;top:64px;width:224px;height:48px" '
170
+ 'src="assets/pptx-volcengine/logos/1.png"></section>'
114
171
  '</deck-stage>',
115
172
  )
116
173
 
@@ -126,7 +183,9 @@ class LayoutAssetContractTest(unittest.TestCase):
126
183
  root,
127
184
  '<deck-stage>'
128
185
  '<section data-pptx-layout="content">'
129
- '<img src="assets/pptx-volcengine/logos/1.png"></section>'
186
+ '<img data-pptx-asset="logo-1" '
187
+ 'style="position:absolute;left:64px;top:64px;width:224px;height:48px" '
188
+ 'src="assets/pptx-volcengine/logos/1.png"></section>'
130
189
  '</deck-stage>',
131
190
  )
132
191
 
@@ -142,7 +201,9 @@ class LayoutAssetContractTest(unittest.TestCase):
142
201
  html = self.write_html(
143
202
  root,
144
203
  '<deck-stage><section data-pptx-layout="cover">'
145
- '<section class="content"><img src="assets/pptx-volcengine/logos/1.png">'
204
+ '<section class="content"><img data-pptx-asset="logo-1" '
205
+ 'style="position:absolute;left:64px;top:64px;width:224px;height:48px" '
206
+ 'src="assets/pptx-volcengine/logos/1.png">'
146
207
  '</section></section></deck-stage>',
147
208
  )
148
209
 
@@ -164,6 +225,102 @@ class LayoutAssetContractTest(unittest.TestCase):
164
225
  validate_layout_assets(root, html, 'assets/pptx-volcengine'),
165
226
  )
166
227
 
228
+ def test_missing_deck_slides_is_rejected_even_without_fixed_assets(self):
229
+ with tempfile.TemporaryDirectory() as root:
230
+ self.write_theme_pack(root)
231
+ html = self.write_html(root, '<main><section>普通页面</section></main>')
232
+
233
+ self.assertEqual(
234
+ ['没有识别到 deck-stage 的直属 slide section,无法核验模板页型'],
235
+ validate_layout_assets(root, html, 'assets/pptx-theme'),
236
+ )
237
+
238
+ def test_layout_outside_default_theme_requires_explicit_theme(self):
239
+ with tempfile.TemporaryDirectory() as root:
240
+ self.write_theme_pack(root)
241
+ html = self.write_html(
242
+ root,
243
+ '<deck-stage>'
244
+ '<section data-pptx-layout="light-content">正文</section>'
245
+ '</deck-stage>',
246
+ )
247
+
248
+ self.assertEqual(
249
+ ['第 1 页页型 light-content 不支持当前主题 dark;'
250
+ '允许主题: light。若确需切换,显式声明 data-pptx-theme'],
251
+ validate_layout_assets(root, html, 'assets/pptx-theme'),
252
+ )
253
+
254
+ def test_explicit_non_default_theme_must_match_layout(self):
255
+ with tempfile.TemporaryDirectory() as root:
256
+ self.write_theme_pack(root)
257
+ html = self.write_html(
258
+ root,
259
+ '<deck-stage>'
260
+ '<section data-pptx-layout="light-content" '
261
+ 'data-pptx-theme="light">正文</section>'
262
+ '</deck-stage>',
263
+ )
264
+
265
+ self.assertEqual(
266
+ [],
267
+ validate_layout_assets(root, html, 'assets/pptx-theme'),
268
+ )
269
+
270
+ def test_unknown_explicit_theme_is_rejected(self):
271
+ with tempfile.TemporaryDirectory() as root:
272
+ self.write_theme_pack(root)
273
+ html = self.write_html(
274
+ root,
275
+ '<deck-stage>'
276
+ '<section data-pptx-layout="dark-content" '
277
+ 'data-pptx-theme="brand-new">正文</section>'
278
+ '</deck-stage>',
279
+ )
280
+
281
+ self.assertEqual(
282
+ ['第 1 页声明了不存在的模板主题: brand-new'],
283
+ validate_layout_assets(root, html, 'assets/pptx-theme'),
284
+ )
285
+
286
+ def test_pack_without_fixed_assets_still_accepts_valid_layout(self):
287
+ with tempfile.TemporaryDirectory() as root:
288
+ self.write_theme_pack(root)
289
+ html = self.write_html(
290
+ root,
291
+ '<deck-stage>'
292
+ '<section data-pptx-layout="dark-content">正文</section>'
293
+ '</deck-stage>',
294
+ )
295
+
296
+ self.assertEqual(
297
+ [],
298
+ validate_layout_assets(root, html, 'assets/pptx-theme'),
299
+ )
300
+
301
+ def test_non_mapping_layout_entry_does_not_crash_theme_validation(self):
302
+ with tempfile.TemporaryDirectory() as root:
303
+ self.write_theme_pack(root)
304
+ with open(os.path.join(root, 'layouts.md'), 'w', encoding='utf-8') as stream:
305
+ stream.write(
306
+ '---\n'
307
+ 'canvas: 1920x1080\n'
308
+ 'layouts:\n'
309
+ ' malformed: unavailable\n'
310
+ '---\n'
311
+ )
312
+ html = self.write_html(
313
+ root,
314
+ '<deck-stage>'
315
+ '<section data-pptx-layout="malformed">正文</section>'
316
+ '</deck-stage>',
317
+ )
318
+
319
+ self.assertEqual(
320
+ [],
321
+ validate_layout_assets(root, html, 'assets/pptx-theme'),
322
+ )
323
+
167
324
  def test_logo_in_global_css_is_rejected(self):
168
325
  with tempfile.TemporaryDirectory() as root:
169
326
  self.write_pack(root)
@@ -209,7 +366,7 @@ class LayoutAssetContractTest(unittest.TestCase):
209
366
  self.assertEqual(1, len(problems))
210
367
  self.assertIn('cover', problems[0])
211
368
  self.assertIn('texture-1', problems[0])
212
- self.assertIn('必须使用', problems[0])
369
+ self.assertIn('缺少固定实例', problems[0])
213
370
 
214
371
  def test_layout_bound_texture_on_an_unowned_layout_is_rejected(self):
215
372
  with tempfile.TemporaryDirectory() as root:
@@ -217,7 +374,9 @@ class LayoutAssetContractTest(unittest.TestCase):
217
374
  html = self.write_html(
218
375
  root,
219
376
  '<deck-stage><section data-pptx-layout="content">'
220
- '<img src="assets/pptx-claude/textures/1.webp"></section></deck-stage>',
377
+ '<img data-pptx-asset="texture-1" '
378
+ 'style="position:absolute;left:1142px;top:0;width:778px;height:1080px" '
379
+ 'src="assets/pptx-claude/textures/1.webp"></section></deck-stage>',
221
380
  )
222
381
 
223
382
  problems = validate_layout_assets(root, html, 'assets/pptx-claude')
@@ -233,6 +392,7 @@ class LayoutAssetContractTest(unittest.TestCase):
233
392
  html = self.write_html(
234
393
  root,
235
394
  '<deck-stage><section data-pptx-layout="content" '
395
+ 'data-pptx-asset="bg-content-1" '
236
396
  'style="background-image:url('
237
397
  '\'assets/pptx-claude/backgrounds/content.webp?v=2#slide\')">'
238
398
  '正文</section></deck-stage>',
@@ -261,14 +421,15 @@ class LayoutAssetContractTest(unittest.TestCase):
261
421
  html = self.write_html(
262
422
  root,
263
423
  '<deck-stage><section data-pptx-layout="cover">'
264
- '<img src="assets/pptx-claude/textures/1.webp"></section></deck-stage>',
424
+ '<img data-pptx-asset="texture-1" '
425
+ 'style="position:absolute;left:0;top:0;width:100px;height:100px" '
426
+ 'src="assets/pptx-claude/textures/1.webp"></section></deck-stage>',
265
427
  )
266
428
 
267
429
  problems = validate_layout_assets(root, html, 'assets/pptx-claude')
268
430
 
269
431
  self.assertEqual(1, len(problems))
270
- self.assertIn('共 2 处', problems[0])
271
- self.assertIn('缺少 1 处', problems[0])
432
+ self.assertIn('位置尺寸应为 [1800, 980, 100, 100]', problems[0])
272
433
 
273
434
  def test_legacy_logo_scope_entrypoint_keeps_failure_exit_code(self):
274
435
  with tempfile.TemporaryDirectory() as root:
@@ -296,6 +457,144 @@ class LayoutAssetContractTest(unittest.TestCase):
296
457
  self.assertEqual(1, result.returncode)
297
458
  self.assertIn('PPTX_LAYOUT_ASSETS: FAIL', result.stdout)
298
459
 
460
+ def test_copied_asset_must_keep_the_source_pptx_bytes(self):
461
+ with tempfile.TemporaryDirectory() as root:
462
+ self.write_texture_pack(root)
463
+ copied = os.path.join(
464
+ root, 'assets', 'pptx-claude', 'textures', '1.webp')
465
+ with open(copied, 'wb') as stream:
466
+ stream.write(b'generated replacement')
467
+ html = self.write_html(
468
+ root,
469
+ '<deck-stage><section data-pptx-layout="cover">'
470
+ '<img data-pptx-asset="texture-1" '
471
+ 'style="position:absolute;left:1142px;top:0;width:778px;height:1080px" '
472
+ 'src="assets/pptx-claude/textures/1.webp"></section></deck-stage>',
473
+ )
474
+
475
+ problems = validate_layout_assets(root, html, 'assets/pptx-claude')
476
+
477
+ self.assertEqual(1, len(problems))
478
+ self.assertIn('已被替换或改写', problems[0])
479
+
480
+ def test_fixed_asset_must_keep_its_layout_box(self):
481
+ with tempfile.TemporaryDirectory() as root:
482
+ self.write_texture_pack(root)
483
+ html = self.write_html(
484
+ root,
485
+ '<deck-stage><section data-pptx-layout="cover">'
486
+ '<img data-pptx-asset="texture-1" '
487
+ 'style="position:absolute;left:0;top:0;width:778px;height:1080px" '
488
+ 'src="assets/pptx-claude/textures/1.webp"></section></deck-stage>',
489
+ )
490
+
491
+ problems = validate_layout_assets(root, html, 'assets/pptx-claude')
492
+
493
+ self.assertEqual(1, len(problems))
494
+ self.assertIn('位置尺寸必须为 [1142, 0, 778, 1080]', problems[0])
495
+
496
+ def test_fixed_asset_cannot_be_hidden(self):
497
+ with tempfile.TemporaryDirectory() as root:
498
+ self.write_texture_pack(root)
499
+ html = self.write_html(
500
+ root,
501
+ '<deck-stage><section data-pptx-layout="cover">'
502
+ '<div hidden><img data-pptx-asset="texture-1" '
503
+ 'style="position:absolute;left:1142px;top:0;width:778px;height:1080px" '
504
+ 'src="assets/pptx-claude/textures/1.webp"></div>'
505
+ '</section></deck-stage>',
506
+ )
507
+
508
+ problems = validate_layout_assets(root, html, 'assets/pptx-claude')
509
+
510
+ self.assertEqual(1, len(problems))
511
+ self.assertIn('不可隐藏', problems[0])
512
+
513
+ def test_zero_sized_fixed_asset_is_hidden(self):
514
+ with tempfile.TemporaryDirectory() as root:
515
+ self.write_texture_pack(root)
516
+ html = self.write_html(
517
+ root,
518
+ '<deck-stage><section data-pptx-layout="cover">'
519
+ '<img data-pptx-asset="texture-1" '
520
+ 'style="position:absolute;left:1142px;top:0;width:0;height:1080px" '
521
+ 'src="assets/pptx-claude/textures/1.webp"></section></deck-stage>',
522
+ )
523
+
524
+ problems = validate_layout_assets(root, html, 'assets/pptx-claude')
525
+
526
+ self.assertEqual(2, len(problems))
527
+ self.assertTrue(any('不可隐藏' in problem for problem in problems))
528
+ self.assertTrue(any('位置尺寸必须为' in problem for problem in problems))
529
+
530
+ def test_fixed_asset_reference_requires_an_instance_marker(self):
531
+ with tempfile.TemporaryDirectory() as root:
532
+ self.write_texture_pack(root)
533
+ html = self.write_html(
534
+ root,
535
+ '<deck-stage><section data-pptx-layout="cover">'
536
+ '<img style="position:absolute;left:1142px;top:0;width:778px;height:1080px" '
537
+ 'src="assets/pptx-claude/textures/1.webp"></section></deck-stage>',
538
+ )
539
+
540
+ problems = validate_layout_assets(root, html, 'assets/pptx-claude')
541
+
542
+ self.assertEqual(2, len(problems))
543
+ self.assertTrue(any(
544
+ '缺少 data-pptx-asset' in problem for problem in problems))
545
+ self.assertTrue(any(
546
+ '缺少固定实例 texture-1' in problem for problem in problems))
547
+
548
+ def test_fixed_asset_cannot_hide_a_generated_source_behind_the_original_url(self):
549
+ with tempfile.TemporaryDirectory() as root:
550
+ self.write_texture_pack(root)
551
+ html = self.write_html(
552
+ root,
553
+ '<deck-stage><section data-pptx-layout="cover">'
554
+ '<img data-pptx-asset="texture-1" '
555
+ 'style="position:absolute;left:1142px;top:0;width:778px;height:1080px;'
556
+ 'background-image:url(\'assets/pptx-claude/textures/1.webp\')" '
557
+ 'src="assets/generated/replacement.webp"></section></deck-stage>',
558
+ )
559
+
560
+ problems = validate_layout_assets(root, html, 'assets/pptx-claude')
561
+
562
+ self.assertEqual(1, len(problems))
563
+ self.assertIn('未引用对应的 PPTX 原素材', problems[0])
564
+
565
+ def test_fixed_asset_nested_in_flow_is_checked_at_its_box(self):
566
+ with tempfile.TemporaryDirectory() as root:
567
+ self.write_texture_pack(root)
568
+ with open(os.path.join(root, 'layouts.md'), 'w', encoding='utf-8') as stream:
569
+ stream.write(
570
+ '---\n'
571
+ 'canvas: 1920x1080\n'
572
+ 'layouts:\n'
573
+ ' cover:\n'
574
+ ' role: cover\n'
575
+ ' flow:\n'
576
+ ' top: 80\n'
577
+ ' margin: [80, 80]\n'
578
+ ' gap: 32\n'
579
+ ' regions:\n'
580
+ ' - kind: free\n'
581
+ ' items:\n'
582
+ ' - {role: texture, type: pic, box: [1142, 0, 778, 1080], asset: texture-1}\n'
583
+ '---\n'
584
+ )
585
+ html = self.write_html(
586
+ root,
587
+ '<deck-stage><section data-pptx-layout="cover">'
588
+ '<img data-pptx-asset="texture-1" '
589
+ 'style="position:absolute;left:1142px;top:0;width:778px;height:1080px" '
590
+ 'src="assets/pptx-claude/textures/1.webp"></section></deck-stage>',
591
+ )
592
+
593
+ self.assertEqual(
594
+ [],
595
+ validate_layout_assets(root, html, 'assets/pptx-claude'),
596
+ )
597
+
299
598
 
300
599
  if __name__ == '__main__':
301
600
  unittest.main()