@lark-apaas/coding-steering 0.1.32-beta.0 → 0.1.32-dev.3598eb3

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 (61) hide show
  1. package/package.json +6 -6
  2. package/steering/design-html/skills/charts/SKILL.md +4 -0
  3. package/steering/design-html/skills/pptx-style-extract/SKILL.md +71 -26
  4. package/steering/design-html/skills/pptx-style-extract/font-fallback.yaml +3 -3
  5. package/steering/design-html/skills/pptx-style-extract/scripts/census.py +26 -14
  6. package/steering/design-html/skills/pptx-style-extract/scripts/check_v2.py +153 -8
  7. package/steering/design-html/skills/pptx-style-extract/scripts/draft.py +2831 -293
  8. package/steering/design-html/skills/pptx-style-extract/scripts/extract.py +544 -22
  9. package/steering/design-html/skills/pptx-style-extract/scripts/ooxml.py +19 -2
  10. package/steering/design-html/skills/pptx-style-extract/scripts/package.py +991 -165
  11. package/steering/design-html/skills/pptx-style-extract/scripts/parts.py +22 -3
  12. package/steering/design-html/skills/pptx-style-extract/scripts/query.py +4 -9
  13. package/steering/design-html/skills/pptx-style-extract/scripts/render_pages.py +20 -12
  14. package/steering/design-html/skills/pptx-style-extract/scripts/test_asset_judgment_package.py +556 -0
  15. package/steering/design-html/skills/pptx-style-extract/scripts/test_background_composite.py +364 -0
  16. package/steering/design-html/skills/pptx-style-extract/scripts/test_color_contract.py +60 -0
  17. package/steering/design-html/skills/pptx-style-extract/scripts/test_design_consumer_contract.py +76 -0
  18. package/steering/design-html/skills/pptx-style-extract/scripts/test_flow_layout_contract.py +528 -0
  19. package/steering/design-html/skills/pptx-style-extract/scripts/test_layout_css.py +1814 -0
  20. package/steering/design-html/skills/pptx-style-extract/scripts/test_logo_scope.py +600 -0
  21. package/steering/design-html/skills/pptx-style-extract/scripts/test_rounded_contract.py +112 -0
  22. package/steering/design-html/skills/pptx-style-extract/scripts/test_text_role_contract.py +315 -0
  23. package/steering/design-html/skills/pptx-style-extract/scripts/verify_layout_assets.py +421 -0
  24. package/steering/design-html/skills/pptx-style-extract/scripts/verify_logo_scope.py +12 -0
  25. package/steering/design-html/skills/pptx-style-extract/v2-format-spec.md +27 -15
  26. package/steering/design-html/skills/preflight/scripts/probe.sh +0 -0
  27. package/steering/nestjs-react-fullstack/skills/app-init-feasibility-guide/SKILL.md +1 -0
  28. package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +6 -0
  29. package/steering/nestjs-react-fullstack/skills/authz-guide/SKILL.md +5 -5
  30. package/steering/nestjs-react-fullstack/skills/authz-guide/references/dynamic-permission-guide.md +1 -1
  31. package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +37 -113
  32. package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +13 -2
  33. package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +7 -7
  34. package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +149 -24
  35. package/steering/nestjs-react-fullstack/skills/connections-sdk/SKILL.md +202 -0
  36. package/steering/nestjs-react-fullstack/skills/nestjs-cache/SKILL.md +255 -0
  37. package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +158 -543
  38. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +15 -1
  39. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/table.md +30 -14
  40. package/steering/nestjs-react-fullstack/skills/raw-sql-boundary-audit/SKILL.md +63 -0
  41. package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +1 -1
  42. package/steering/nestjs-react-fullstack/skills_common/trigger-guide/SKILL.md +284 -12
  43. package/steering/nestjs-react-fullstack/skills_local/plugin-guide/SKILL.md +4 -0
  44. package/steering/vite-react/skills/plugin-guide/SKILL.md +3 -1
  45. package/steering/vite-react/skills/react-three-fiber/SKILL.md +4 -0
  46. package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +0 -139
  47. package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +0 -269
  48. package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +0 -214
  49. package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +0 -163
  50. package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +0 -311
  51. package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +0 -190
  52. package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +0 -160
  53. package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +0 -257
  54. package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +0 -104
  55. package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +0 -199
  56. package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +0 -128
  57. package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +0 -207
  58. package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +0 -165
  59. package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +0 -91
  60. package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +0 -165
  61. package/steering/nestjs-react-fullstack/skills_common/trigger-guide/references/trigger-lifecycle.md +0 -301
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env python3
2
+ """Regression tests for preserving per-container radii."""
3
+ import os
4
+ import tempfile
5
+ import unittest
6
+
7
+ from draft import emit_body, emit_frontmatter
8
+ from query import _recipe_css
9
+
10
+
11
+ def render_frontmatter(radii):
12
+ data = {
13
+ 'radii_census': radii,
14
+ 'spacing_candidates': {},
15
+ }
16
+ tokens = [('surface', {'hex': '#FFFFFF'})]
17
+
18
+ with tempfile.TemporaryDirectory() as output_dir:
19
+ emit_frontmatter(
20
+ data,
21
+ tokens,
22
+ [],
23
+ {},
24
+ [],
25
+ [],
26
+ output_dir,
27
+ )
28
+ with open(os.path.join(output_dir, 'frontmatter.yaml'), encoding='utf-8') as stream:
29
+ return stream.read()
30
+
31
+
32
+ def render_body():
33
+ data = {
34
+ 'canvas': {'px': [1920, 1080]},
35
+ 'form_hint': {'form': 2},
36
+ 'counts': {'slides': 1},
37
+ }
38
+
39
+ with tempfile.TemporaryDirectory() as output_dir:
40
+ emit_body(
41
+ data,
42
+ [],
43
+ [],
44
+ {},
45
+ [],
46
+ [],
47
+ [],
48
+ {},
49
+ output_dir,
50
+ )
51
+ with open(os.path.join(output_dir, 'body.md'), encoding='utf-8') as stream:
52
+ return stream.read()
53
+
54
+
55
+ class RoundedContractTest(unittest.TestCase):
56
+ def test_multiple_radius_tiers_are_not_collapsed_into_one_card_token(self):
57
+ frontmatter = render_frontmatter(
58
+ [
59
+ {'px': 6.9, 'n': 2},
60
+ {'px': 11.9, 'n': 2},
61
+ {'px': 14.4, 'n': 3},
62
+ ]
63
+ )
64
+
65
+ self.assertNotIn('rounded:', frontmatter)
66
+
67
+ def test_single_radius_tier_can_remain_a_global_token(self):
68
+ frontmatter = render_frontmatter([{'px': 12, 'n': 9}])
69
+
70
+ self.assertIn('rounded:\n card: 12px', frontmatter)
71
+
72
+ def test_rare_rounded_exceptions_do_not_override_a_zero_radius_majority(self):
73
+ frontmatter = render_frontmatter(
74
+ [
75
+ {'px': 0, 'n': 241},
76
+ {'px': 3.4, 'n': 4},
77
+ {'px': 6.9, 'n': 3},
78
+ {'px': 50.5, 'n': 1},
79
+ ]
80
+ )
81
+
82
+ self.assertNotIn('rounded:', frontmatter)
83
+
84
+ def test_generated_usage_defaults_unspecified_container_radius_to_zero(self):
85
+ body = render_body()
86
+
87
+ self.assertIn('没有 `border-radius` 就按 `0`', body)
88
+ self.assertIn('不得自行补圆角', body)
89
+
90
+ def test_recipe_does_not_promote_one_rounded_exception_to_the_whole_group(self):
91
+ css = _recipe_css(
92
+ {'type': 'solid', 'color': {'hex': '#FFFFFF'}},
93
+ None,
94
+ [0] * 70 + [50.5],
95
+ None,
96
+ )
97
+
98
+ self.assertFalse(any('border-radius' in declaration for declaration in css))
99
+
100
+ def test_recipe_keeps_a_radius_shared_by_the_whole_group(self):
101
+ css = _recipe_css(
102
+ {'type': 'solid', 'color': {'hex': '#FFFFFF'}},
103
+ None,
104
+ [6.9] * 17,
105
+ None,
106
+ )
107
+
108
+ self.assertIn('border-radius: 6.9px', css)
109
+
110
+
111
+ if __name__ == '__main__':
112
+ unittest.main()
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env python3
2
+ """Regression tests for inherited layout text and model-decided text roles."""
3
+ import json
4
+ import os
5
+ import re
6
+ import sys
7
+ import tempfile
8
+ import unittest
9
+
10
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
11
+
12
+ from draft import draft_layouts, emit_layouts, inherited_text_shapes
13
+ from package import build_layouts_md, split_top_blocks
14
+
15
+
16
+ def text_shape(part, layer, shape_id, box, text, placeholder):
17
+ return {
18
+ 'part': part,
19
+ 'layer': layer,
20
+ 'id': shape_id,
21
+ 'kind': 'sp',
22
+ 'name': 'Text Placeholder',
23
+ 'ph': placeholder,
24
+ 'box': box,
25
+ 'text': {
26
+ 'bodyPr': {},
27
+ 'lstStyle': {
28
+ 'lvl1pPr': {
29
+ 'sz_px': 48,
30
+ 'weight': 600,
31
+ 'color': {'resolved': '#C41230'},
32
+ },
33
+ },
34
+ 'paragraphs': [
35
+ {
36
+ 'runs': [{'text': text}],
37
+ },
38
+ ] if text else [],
39
+ },
40
+ }
41
+
42
+
43
+ class TextRoleContractTest(unittest.TestCase):
44
+ def test_empty_non_placeholder_layout_shape_is_not_a_text_slot(self):
45
+ layout_part = 'ppt/slideLayouts/slideLayout2.xml'
46
+ decorative_shape = text_shape(
47
+ layout_part,
48
+ 'layout',
49
+ '9',
50
+ {'x': 0, 'y': 0, 'w': 1920, 'h': 24},
51
+ '',
52
+ None,
53
+ )
54
+ decorative_shape['name'] = 'Decorative bar'
55
+
56
+ self.assertEqual(inherited_text_shapes([decorative_shape], []), [])
57
+
58
+ def test_empty_slide_inherits_text_slot_and_css_from_its_layout(self):
59
+ layout_part = 'ppt/slideLayouts/slideLayout2.xml'
60
+ slide_part = 'ppt/slides/slide1.xml'
61
+ shapes = [
62
+ text_shape(
63
+ layout_part,
64
+ 'layout',
65
+ '10',
66
+ {'x': 120, 'y': 80, 'w': 840, 'h': 120},
67
+ 'Example heading',
68
+ {'type': 'body', 'idx': '10'},
69
+ ),
70
+ text_shape(
71
+ slide_part,
72
+ 'slide',
73
+ '2',
74
+ None,
75
+ '',
76
+ {'type': 'body', 'idx': '10'},
77
+ ),
78
+ ]
79
+ data = {
80
+ 'canvas': {'px': [1920, 1080]},
81
+ 'form_hint': {'form': 0},
82
+ 'slides': [
83
+ {
84
+ 'part': slide_part,
85
+ 'layout': layout_part,
86
+ 'background': None,
87
+ },
88
+ ],
89
+ 'background_composites': {},
90
+ }
91
+
92
+ with tempfile.TemporaryDirectory() as output_dir:
93
+ os.makedirs(os.path.join(output_dir, 'ref'))
94
+ with open(
95
+ os.path.join(output_dir, 'ref', 'shapes.json'),
96
+ 'w',
97
+ encoding='utf-8',
98
+ ) as stream:
99
+ json.dump({'shapes': shapes}, stream)
100
+ archetypes, _, _ = draft_layouts(data, output_dir)
101
+
102
+ self.assertEqual(len(archetypes), 1)
103
+ self.assertEqual(len(archetypes[0]['slots']), 1)
104
+ slot = archetypes[0]['slots'][0]
105
+ self.assertEqual(slot['box'], [120, 80, 840, 120])
106
+ self.assertEqual(slot['txt'], 'Example heading')
107
+ self.assertEqual(slot['role'], 'body')
108
+ self.assertEqual(slot['type'], 'body')
109
+ self.assertTrue(slot['_needs_role'])
110
+ self.assertIn('font-size: 48px', slot['css'])
111
+ self.assertIn('font-weight: 600', slot['css'])
112
+ self.assertIn('color: #C41230', slot['css'])
113
+
114
+ def test_text_role_judgement_changes_semantics_without_dropping_slot(self):
115
+ archetype = {
116
+ 'name': 'layout-1',
117
+ 'zh': None,
118
+ 'role': 'content',
119
+ 'bg': None,
120
+ 'slots': [
121
+ {
122
+ 'role': 'body',
123
+ 'type': 'body',
124
+ 'box': [120, 80, 840, 120],
125
+ 'sz': 48,
126
+ 'txt': 'Example heading',
127
+ 'css': 'font-size: 48px; color: #C41230',
128
+ '_needs_role': True,
129
+ '_source_layer': 'layout',
130
+ '_placeholder': 'body/10',
131
+ },
132
+ ],
133
+ 'decor': [],
134
+ 'pages': [1],
135
+ 'rep': 1,
136
+ 'pic_n': 0,
137
+ 'confidence': 'low',
138
+ }
139
+
140
+ with tempfile.TemporaryDirectory() as output_dir:
141
+ emit_layouts([archetype], output_dir)
142
+ path = os.path.join(output_dir, 'layouts.yaml')
143
+ with open(path, encoding='utf-8') as stream:
144
+ draft = stream.read()
145
+
146
+ self.assertIn('默认均为 body', draft)
147
+ self.assertIn(
148
+ '# text-role: layout-1-text-1',
149
+ draft,
150
+ )
151
+ self.assertEqual(draft.count('box: [120, 80, 840, 120]'), 1)
152
+
153
+ decided = draft.replace(
154
+ 'layouts:',
155
+ 'text_roles:\n layout-1-text-1: title\nlayouts:',
156
+ )
157
+ layouts_md = build_layouts_md(split_top_blocks(decided), (1920, 1080))
158
+
159
+ self.assertNotIn('text_roles:', layouts_md)
160
+ self.assertEqual(layouts_md.count('box: [120, 80, 840, 120]'), 1)
161
+ self.assertIn(
162
+ 'role: title, box: [120, 80, 840, 120], type: title',
163
+ layouts_md,
164
+ )
165
+ self.assertIn('css: "font-size: 48px; color: #C41230"', layouts_md)
166
+
167
+ def test_header_role_uses_a_v2_slot_type(self):
168
+ draft = """names:
169
+ layout-1: 内容页
170
+ roles:
171
+ layout-1: content
172
+ text_roles:
173
+ layout-1-text-1: header
174
+ layouts:
175
+ layout-1:
176
+ slots:
177
+ # text-role: layout-1-text-1
178
+ - {role: body, box: [120, 80, 840, 120], type: body}
179
+ confidence: high
180
+ """
181
+
182
+ layouts_md = build_layouts_md(split_top_blocks(draft), (1920, 1080))
183
+
184
+ self.assertIn(
185
+ 'role: header, box: [120, 80, 840, 120], type: body',
186
+ layouts_md,
187
+ )
188
+ self.assertNotIn('type: header', layouts_md)
189
+
190
+ def test_decided_layout_role_replaces_the_draft_default(self):
191
+ draft = """names:
192
+ layout-1: 末页
193
+ roles:
194
+ layout-1: closing
195
+ layouts:
196
+ layout-1:
197
+ role: content
198
+ slots:
199
+ - {role: title, box: [120, 80, 840, 120], type: title}
200
+ confidence: high
201
+ """
202
+
203
+ layouts_md = build_layouts_md(split_top_blocks(draft), (1920, 1080))
204
+
205
+ self.assertIn(' role: closing', layouts_md)
206
+ self.assertNotIn(' role: content', layouts_md)
207
+ self.assertEqual(layouts_md.count(' role:'), 1)
208
+
209
+ def test_last_slide_is_kept_as_a_role_candidate_when_layout_limit_is_full(self):
210
+ shapes, slides = [], []
211
+ for index in range(1, 11):
212
+ slide_part = 'ppt/slides/slide%d.xml' % index
213
+ shapes.append(text_shape(
214
+ slide_part,
215
+ 'slide',
216
+ str(index),
217
+ {'x': 120, 'y': 80, 'w': 840, 'h': 120},
218
+ 'Slide %d' % index,
219
+ {'type': 'body', 'idx': str(index)},
220
+ ))
221
+ slides.append({
222
+ 'part': slide_part,
223
+ 'layout': 'ppt/slideLayouts/slideLayout1.xml',
224
+ 'background': '#%02x0000' % index,
225
+ })
226
+ data = {
227
+ 'canvas': {'px': [1920, 1080]},
228
+ 'form_hint': {'form': 0},
229
+ 'slides': slides,
230
+ 'background_composites': {},
231
+ }
232
+
233
+ with tempfile.TemporaryDirectory() as output_dir:
234
+ os.makedirs(os.path.join(output_dir, 'ref'))
235
+ with open(os.path.join(output_dir, 'ref', 'shapes.json'), 'w',
236
+ encoding='utf-8') as stream:
237
+ json.dump({'shapes': shapes}, stream)
238
+ archetypes, _, leftover = draft_layouts(data, output_dir)
239
+ emit_layouts(archetypes, output_dir)
240
+ with open(os.path.join(output_dir, 'layouts.yaml'), encoding='utf-8') as stream:
241
+ draft = stream.read()
242
+
243
+ last_archetype = next(
244
+ archetype for archetype in archetypes if archetype['pages'] == [10]
245
+ )
246
+ self.assertTrue(last_archetype['_last_page_candidate'])
247
+ self.assertNotIn(10, leftover)
248
+ self.assertIn(
249
+ '代表页 %s,共 1 页;' % last_archetype['rep'],
250
+ draft,
251
+ )
252
+ self.assertIn('末页候选,结合样张判断 closing 或实际角色', draft)
253
+ decided = draft.replace(
254
+ '%s: TODO角色' % last_archetype['name'],
255
+ '%s: closing' % last_archetype['name'],
256
+ )
257
+ layouts_md = build_layouts_md(split_top_blocks(decided), (1920, 1080))
258
+ self.assertRegex(
259
+ layouts_md,
260
+ r' %s:\n(?: .*\n)*? role: closing\n'
261
+ % last_archetype['name'],
262
+ )
263
+ self.assertRegex(
264
+ layouts_md,
265
+ r' %s:\n(?: .*\n)*? - \{role: body, box: \[120, 80, 840, 120\]'
266
+ % last_archetype['name'],
267
+ )
268
+ self.assertIn(
269
+ ' %s:' % last_archetype['name'],
270
+ layouts_md,
271
+ )
272
+
273
+ def test_template_layout_shape_without_ph_key_does_not_crash(self):
274
+ # form=3 模板里,版式层可能有「带 box、带文字、但没有 ph 键」的普通形状
275
+ # (非占位符的文本/装饰)。layouts_from_template 的入口筛选是
276
+ # `s.get('ph') or shape_text(s)`——有文字就放进来,随后按 ph 判类型时若用
277
+ # s['ph'] 直接下标就会 KeyError: 'ph',整份抽取在草案阶段崩掉(EXTRACT_PARTIAL)。
278
+ layout_part = 'ppt/slideLayouts/slideLayout1.xml'
279
+ shape = {
280
+ 'part': layout_part,
281
+ 'layer': 'layout',
282
+ 'id': '7',
283
+ 'kind': 'sp',
284
+ 'name': '页脚文字',
285
+ # 关键:没有 'ph' 键
286
+ 'box': {'x': 100, 'y': 980, 'w': 800, 'h': 60},
287
+ 'text': {
288
+ 'bodyPr': {},
289
+ 'lstStyle': {'lvl1pPr': {'sz_px': 20}},
290
+ 'paragraphs': [{'runs': [{'text': '内部资料'}]}],
291
+ },
292
+ }
293
+ data = {
294
+ 'canvas': {'px': [1920, 1080]},
295
+ 'form_hint': {'form': 3},
296
+ 'layouts': [{'part': layout_part}],
297
+ 'slides': [{'part': 'ppt/slides/slide1.xml', 'layout': layout_part,
298
+ 'background': None}],
299
+ 'background_composites': {},
300
+ }
301
+
302
+ with tempfile.TemporaryDirectory() as output_dir:
303
+ os.makedirs(os.path.join(output_dir, 'ref'))
304
+ with open(os.path.join(output_dir, 'ref', 'shapes.json'), 'w',
305
+ encoding='utf-8') as stream:
306
+ json.dump({'shapes': [shape]}, stream)
307
+ # 修复前这里抛 KeyError: 'ph'(draft.py 用 s['ph'] 直接下标),
308
+ # 抽取在草案阶段崩掉、退成 EXTRACT_PARTIAL。修复后应正常返回。
309
+ archetypes, pages, leftover = draft_layouts(data, output_dir)
310
+
311
+ self.assertIsInstance(archetypes, list)
312
+
313
+
314
+ if __name__ == '__main__':
315
+ unittest.main()