@lark-apaas/coding-miaoda-sandbox-skills 0.1.0-dev.6f4e4bc → 0.1.0-dev.72ac425

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 (71) hide show
  1. package/miaoda/ai-data-processing/SKILL.md +4 -1
  2. package/miaoda/charts-skill/SKILL.md +1 -1
  3. package/miaoda/creative-to-fullstack/SKILL.md +38 -226
  4. package/miaoda/feishu/SKILL.md +4 -4
  5. package/miaoda/forms-skill/SKILL.md +9 -0
  6. package/miaoda/lark-apps-db/SKILL.md +6 -4
  7. package/miaoda/lark-apps-db/references/full-reference.md +13 -4
  8. package/miaoda/lark-apps-ops/SKILL.md +2 -1
  9. package/miaoda/lark-apps-ops/references/lark-apps-export.md +46 -0
  10. package/miaoda/lark-design-prototype/DESIGN.md +603 -0
  11. package/miaoda/lark-design-prototype/SKILL.md +85 -0
  12. package/miaoda/lark-design-prototype/references/assets/card-illustration-library.md +113 -0
  13. package/miaoda/lark-design-prototype/references/case-matching.md +53 -0
  14. package/miaoda/lark-design-prototype/references/cases/conversational-ai-home.md +27 -0
  15. package/miaoda/lark-design-prototype/references/cases/data-table.md +30 -0
  16. package/miaoda/lark-design-prototype/references/cases/official-home.md +26 -0
  17. package/miaoda/lark-design-prototype/references/cases/workspace-home.md +34 -0
  18. package/miaoda/lark-design-prototype/references/color-roles.md +163 -0
  19. package/miaoda/lark-design-prototype/references/component-selection.md +134 -0
  20. package/miaoda/lark-design-prototype/references/design-quality-checklist.md +156 -0
  21. package/miaoda/lark-design-prototype/references/form-shell-patterns.md +77 -0
  22. package/miaoda/lark-design-prototype/references/icon-semantics.md +237 -0
  23. package/miaoda/lark-design-prototype/references/layout-interaction.md +164 -0
  24. package/miaoda/lark-design-prototype/references/page-contract.md +281 -0
  25. package/miaoda/lark-design-prototype/references/product-patterns.md +93 -0
  26. package/miaoda/lark-design-prototype/references/prompt-expansion.md +107 -0
  27. package/miaoda/lark-design-prototype/references/restoration-traps.md +113 -0
  28. package/miaoda/lark-design-prototype/references/token-semantics.md +112 -0
  29. package/miaoda/lark-design-prototype/references/visual-brief.md +125 -0
  30. package/miaoda/lark-design-prototype/references/visual-style-prompts.md +61 -0
  31. package/miaoda/lark-design-prototype/scripts/icon-query.mjs +272 -0
  32. package/miaoda/lark-design-prototype/scripts/token-query.mjs +76 -0
  33. package/miaoda/lark-design-prototype/scripts/verify-static-html.mjs +117 -0
  34. package/miaoda/performance-review/SKILL.md +3 -3
  35. package/miaoda/semantic-search/SKILL.md +6 -3
  36. package/miaoda/testing-guide/SKILL.md +116 -12
  37. package/miaoda-modern/charts-skill/SKILL.md +1 -1
  38. package/miaoda-modern/forms-skill/SKILL.md +33 -3
  39. package/miaoda-modern/lark-apps-ops/SKILL.md +2 -1
  40. package/miaoda-modern/lark-apps-ops/references/lark-apps-export.md +46 -0
  41. package/miaoda-modern/lark-design-prototype/DESIGN.md +603 -0
  42. package/miaoda-modern/lark-design-prototype/SKILL.md +85 -0
  43. package/miaoda-modern/lark-design-prototype/references/assets/card-illustration-library.md +113 -0
  44. package/miaoda-modern/lark-design-prototype/references/case-matching.md +53 -0
  45. package/miaoda-modern/lark-design-prototype/references/cases/conversational-ai-home.md +27 -0
  46. package/miaoda-modern/lark-design-prototype/references/cases/data-table.md +30 -0
  47. package/miaoda-modern/lark-design-prototype/references/cases/official-home.md +26 -0
  48. package/miaoda-modern/lark-design-prototype/references/cases/workspace-home.md +34 -0
  49. package/miaoda-modern/lark-design-prototype/references/color-roles.md +163 -0
  50. package/miaoda-modern/lark-design-prototype/references/component-selection.md +134 -0
  51. package/miaoda-modern/lark-design-prototype/references/design-quality-checklist.md +156 -0
  52. package/miaoda-modern/lark-design-prototype/references/form-shell-patterns.md +77 -0
  53. package/miaoda-modern/lark-design-prototype/references/icon-semantics.md +237 -0
  54. package/miaoda-modern/lark-design-prototype/references/layout-interaction.md +164 -0
  55. package/miaoda-modern/lark-design-prototype/references/page-contract.md +281 -0
  56. package/miaoda-modern/lark-design-prototype/references/product-patterns.md +93 -0
  57. package/miaoda-modern/lark-design-prototype/references/prompt-expansion.md +107 -0
  58. package/miaoda-modern/lark-design-prototype/references/restoration-traps.md +113 -0
  59. package/miaoda-modern/lark-design-prototype/references/token-semantics.md +112 -0
  60. package/miaoda-modern/lark-design-prototype/references/visual-brief.md +125 -0
  61. package/miaoda-modern/lark-design-prototype/references/visual-style-prompts.md +61 -0
  62. package/miaoda-modern/lark-design-prototype/scripts/icon-query.mjs +272 -0
  63. package/miaoda-modern/lark-design-prototype/scripts/token-query.mjs +76 -0
  64. package/miaoda-modern/lark-design-prototype/scripts/verify-static-html.mjs +117 -0
  65. package/miaoda-modern/performance-review/SKILL.md +3 -3
  66. package/miaoda-modern/reviewer-usage/SKILL.md +2 -0
  67. package/package.json +1 -1
  68. package/shared/attachment/SKILL.md +5 -1
  69. package/miaoda/creative-to-fullstack/references/artifact-signals.md +0 -46
  70. package/miaoda/creative-to-fullstack/references/ui-to-function.md +0 -134
  71. package/miaoda-modern/testing-guide/SKILL.md +0 -218
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs/promises';
3
+ import { createHash } from 'node:crypto';
4
+ import path from 'node:path';
5
+
6
+ const SVG_BASE = 'https://cdn-tos-cn.bytedance.net/obj/archi/ee/es-design-base/svgs';
7
+ const REMOTE_CATALOG =
8
+ 'https://lf3-static.bytednsdoc.com/obj/eden-cn/pshi/lark-design-prototype/1.0.4/db71a6cd-33f97519-0d446cd5/icons.catalog.json';
9
+ const REMOTE_CATALOG_SHA256 = 'db71a6cd6ac90c77629e8ce0fc0905ca1488a6601800ff7533385490b10eac9d';
10
+
11
+ function usage() {
12
+ return `Usage:
13
+ node icon-query.mjs [--catalog <remote|catalog.json|url>] --query "<keywords>" [--family outlined] [--style v2-outlined|source-outlined|source-filled|source-colorful] [--shape normal|round] [--prefer-v2] [--require-v2] [--limit 5]
14
+
15
+ Returns a compact JSON array with name, hash, family, version, shape, score, description, and final svgUrl.
16
+ Never print the raw icon catalog during page generation.`;
17
+ }
18
+
19
+ function argValue(name, fallback = '') {
20
+ const index = process.argv.indexOf(name);
21
+ return index === -1 ? fallback : process.argv[index + 1] || fallback;
22
+ }
23
+
24
+ function argFlag(name) {
25
+ return process.argv.includes(name);
26
+ }
27
+
28
+ function tokenize(text) {
29
+ const rawTokens = String(text || '')
30
+ .toLowerCase()
31
+ .split(/[\s,,、::;;/|()()\[\]{}'"`<>.!??。_-]+/)
32
+ .map((item) => item.trim())
33
+ .filter(Boolean);
34
+ const aliases = {
35
+ 搜索: ['search'],
36
+ 查找: ['search'],
37
+ 通知: ['notification', 'bell'],
38
+ 提醒: ['reminder', 'bell'],
39
+ 主页: ['home'],
40
+ 首页: ['home'],
41
+ 模板: ['template'],
42
+ 文档: ['doc', 'document', 'file'],
43
+ document: ['doc'],
44
+ 文件: ['file'],
45
+ 文件夹: ['folder'],
46
+ 表格: ['sheet', 'table', 'bitable'],
47
+ spreadsheet: ['sheet'],
48
+ table: ['sheet'],
49
+ 图表: ['chart'],
50
+ 看板: ['dashboard', 'chart'],
51
+ 帮助: ['help', 'faq'],
52
+ 指南: ['guide', 'info'],
53
+ 飞书: ['feishu', 'lark', 'logo', 'logotype'],
54
+ 品牌: ['brand', 'logo', 'logotype'],
55
+ 标识: ['logo', 'logotype'],
56
+ logo: ['brand', 'logotype'],
57
+ };
58
+ const expanded = new Set(rawTokens);
59
+ for (const token of rawTokens) {
60
+ for (const alias of aliases[token] || []) {
61
+ expanded.add(alias);
62
+ }
63
+ }
64
+ return [...expanded];
65
+ }
66
+
67
+ async function readJsonFile(filePath) {
68
+ const text = await fs.readFile(filePath, 'utf8');
69
+ return JSON.parse(text);
70
+ }
71
+
72
+ async function readCatalog(source) {
73
+ const resolved = !source || source === 'remote' ? REMOTE_CATALOG : source;
74
+ if (/^https?:\/\//.test(resolved)) {
75
+ const response = await fetch(resolved);
76
+ if (!response.ok) {
77
+ throw new Error(`Catalog request failed with HTTP ${response.status}`);
78
+ }
79
+ const text = await response.text();
80
+ if (resolved === REMOTE_CATALOG) {
81
+ const actualHash = createHash('sha256').update(text).digest('hex');
82
+ if (actualHash !== REMOTE_CATALOG_SHA256) {
83
+ throw new Error(
84
+ `Catalog integrity check failed: expected ${REMOTE_CATALOG_SHA256}, got ${actualHash}`,
85
+ );
86
+ }
87
+ }
88
+ return { catalog: JSON.parse(text), catalogSource: resolved };
89
+ }
90
+ const filePath = path.resolve(resolved);
91
+ return { catalog: await readJsonFile(filePath), catalogSource: filePath };
92
+ }
93
+
94
+ function detectVersion(name) {
95
+ return /(?:^|[-_])v2(?:[-_]|$)/i.test(name) ? 'v2' : 'non-v2';
96
+ }
97
+
98
+ function detectShape(icon) {
99
+ const name = icon.name.toLowerCase();
100
+ if (name.includes('file-round-') || name.includes('wiki-round-')) return 'round';
101
+ if (icon.family === 'colorful' && /(?:icon_|wiki-)?file[-_]/.test(name)) return 'normal';
102
+ return 'none';
103
+ }
104
+
105
+ function detectStyleType(icon) {
106
+ const version = detectVersion(icon.name);
107
+ const shape = detectShape(icon);
108
+ if (icon.family === 'outlined') {
109
+ return version === 'v2' ? 'v2-outlined' : 'non-v2-outlined';
110
+ }
111
+ if (icon.family === 'filled') {
112
+ return 'source-filled';
113
+ }
114
+ if (icon.family === 'colorful' && /(?:feishu|lark).*logo|logotype|logo/i.test(icon.name)) {
115
+ return 'brand-colorful';
116
+ }
117
+ if (icon.family === 'colorful' && version === 'v2' && shape === 'round') {
118
+ return 'file-v2-colorful-round';
119
+ }
120
+ if (icon.family === 'colorful' && version === 'v2' && shape === 'normal') {
121
+ return 'file-v2-colorful-normal';
122
+ }
123
+ return icon.family;
124
+ }
125
+
126
+ function normalizeOptions() {
127
+ const style = argValue('--style');
128
+ const options = {
129
+ family: argValue('--family', 'outlined'),
130
+ style,
131
+ shape: argValue('--shape'),
132
+ preferV2: argFlag('--prefer-v2'),
133
+ requireV2: argFlag('--require-v2'),
134
+ requireNonV2: argFlag('--require-non-v2'),
135
+ include: argValue('--name-include'),
136
+ exclude: argValue('--name-exclude'),
137
+ };
138
+
139
+ if (style === 'v2-outlined') {
140
+ options.family = 'outlined';
141
+ options.requireV2 = true;
142
+ } else if (style === 'non-v2-outlined') {
143
+ options.family = 'outlined';
144
+ options.requireNonV2 = true;
145
+ } else if (style === 'file-v2-colorful-normal') {
146
+ options.family = 'colorful';
147
+ options.requireV2 = true;
148
+ options.shape = 'normal';
149
+ options.include ||= 'file';
150
+ } else if (style === 'file-v2-colorful-round') {
151
+ options.family = 'colorful';
152
+ options.requireV2 = true;
153
+ options.shape = 'round';
154
+ options.include ||= 'file-round';
155
+ } else if (style === 'source-outlined') {
156
+ options.family = 'outlined';
157
+ options.preferV2 = true;
158
+ } else if (style === 'source-filled') {
159
+ options.family = 'filled';
160
+ } else if (style === 'brand-colorful' || style === 'source-colorful') {
161
+ options.family = 'colorful';
162
+ if (style === 'source-colorful') {
163
+ options.preferV2 = true;
164
+ }
165
+ }
166
+ return options;
167
+ }
168
+
169
+ function flattenCatalog(catalog, options) {
170
+ const rows = [];
171
+ for (const category of catalog) {
172
+ for (const [family, icons] of Object.entries(category.icons || {})) {
173
+ if (options.family && family !== options.family) {
174
+ continue;
175
+ }
176
+ for (const icon of icons || []) {
177
+ const row = {
178
+ category: category.id || '',
179
+ family,
180
+ name: icon.name,
181
+ hash: icon.hash,
182
+ darkHash: icon.darkHash,
183
+ description: icon.description || '',
184
+ };
185
+ row.version = detectVersion(row.name);
186
+ row.shape = detectShape(row);
187
+ row.styleType = detectStyleType(row);
188
+ rows.push(row);
189
+ }
190
+ }
191
+ }
192
+ return rows;
193
+ }
194
+
195
+ function passesFilters(icon, options) {
196
+ const name = icon.name.toLowerCase();
197
+ if (options.requireV2 && icon.version !== 'v2') return false;
198
+ if (options.requireNonV2 && icon.version !== 'non-v2') return false;
199
+ if (options.shape && icon.shape !== options.shape) return false;
200
+ if (options.include && !name.includes(options.include.toLowerCase())) return false;
201
+ if (options.exclude && name.includes(options.exclude.toLowerCase())) return false;
202
+ const sourceStyles = new Set(['source-outlined', 'source-filled', 'source-colorful']);
203
+ if (options.style && !sourceStyles.has(options.style) && icon.styleType !== options.style)
204
+ return false;
205
+ return true;
206
+ }
207
+
208
+ function scoreIcon(icon, queryTokens, options) {
209
+ const name = icon.name.toLowerCase();
210
+ const description = icon.description.toLowerCase();
211
+ let score = 0;
212
+ for (const token of queryTokens) {
213
+ if (name.includes(token)) score += 5;
214
+ if (description.includes(token)) score += 3;
215
+ if (name === `icon_${token}_outlined`) score += 8;
216
+ if (name === `icon_${token}-v2_outlined`) score += 8;
217
+ if (name === `icon_${token}_filled`) score += 8;
218
+ if (name === `icon_file-${token}-v2_colorful`) score += 8;
219
+ }
220
+ if (score === 0) return 0;
221
+ if (icon.family === 'outlined') score += 1;
222
+ if ((options.preferV2 || options.style?.includes('v2')) && icon.version === 'v2') score += 4;
223
+ if (
224
+ options.style === 'brand-colorful' &&
225
+ /feishu|lark|logotype|logo/i.test(`${name} ${description}`)
226
+ )
227
+ score += 10;
228
+
229
+ const objectTokens = ['doc', 'document', 'file', 'card', 'sheet', 'bitable', 'ai'];
230
+ const queryHasObject = objectTokens.some((token) => queryTokens.includes(token));
231
+ if (
232
+ !queryHasObject &&
233
+ objectTokens.some((token) => name.includes(`_${token}`) || name.includes(`-${token}`))
234
+ ) {
235
+ score -= 3;
236
+ }
237
+ return score;
238
+ }
239
+
240
+ const catalogSource = argValue('--catalog', 'remote');
241
+ const query = argValue('--query');
242
+ const limit = Math.max(1, Number.parseInt(argValue('--limit', '5'), 10) || 5);
243
+
244
+ if (!query) {
245
+ console.error(usage());
246
+ process.exit(2);
247
+ }
248
+
249
+ const options = normalizeOptions();
250
+ const queryTokens = tokenize(query);
251
+ const { catalog, catalogSource: usedCatalogSource } = await readCatalog(catalogSource);
252
+ const matches = flattenCatalog(catalog, options)
253
+ .filter((icon) => passesFilters(icon, options))
254
+ .map((icon) => ({ ...icon, score: scoreIcon(icon, queryTokens, options) }))
255
+ .filter((icon) => icon.score > 0)
256
+ .sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
257
+ .slice(0, limit)
258
+ .map((icon) => ({
259
+ name: icon.name,
260
+ hash: icon.hash,
261
+ darkHash: icon.darkHash || undefined,
262
+ family: icon.family,
263
+ version: icon.version,
264
+ shape: icon.shape,
265
+ styleType: icon.styleType,
266
+ score: icon.score,
267
+ description: icon.description.slice(0, 180),
268
+ svgUrl: `${SVG_BASE}/${icon.name}.${icon.hash}.svg`,
269
+ catalogSource: usedCatalogSource,
270
+ }));
271
+
272
+ console.log(JSON.stringify(matches, null, 2));
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import { createHash } from 'node:crypto';
3
+
4
+ const SOURCES = {
5
+ semantic: {
6
+ url: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/pshi/lark-design-prototype/1.0.4/db71a6cd-33f97519-0d446cd5/semantic.json',
7
+ sha256: '33f975193bcd0b5b12df9003e6f8586bf4662336e0bd5cf73ea1f362cfa4eb50',
8
+ },
9
+ keys: {
10
+ url: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/pshi/lark-design-prototype/1.0.4/db71a6cd-33f97519-0d446cd5/keys.json',
11
+ sha256: '0d446cd57b0beaaaa9ba485009b31a73be77d5f87a8481ec032c54f33b3bb471',
12
+ },
13
+ };
14
+
15
+ function usage() {
16
+ return `Usage:
17
+ node token-query.mjs --query "<token name or intent>" [--source semantic|keys] [--limit 5]
18
+
19
+ Fetches the versioned public token resource, verifies SHA-256, and returns compact matches.`;
20
+ }
21
+
22
+ function argValue(name, fallback = '') {
23
+ const index = process.argv.indexOf(name);
24
+ return index === -1 ? fallback : process.argv[index + 1] || fallback;
25
+ }
26
+
27
+ function tokenize(value) {
28
+ return String(value || '')
29
+ .toLowerCase()
30
+ .split(/[\s,,、::;;/|()()\[\]{}'"`<>.!??。_-]+/)
31
+ .map((item) => item.trim())
32
+ .filter(Boolean);
33
+ }
34
+
35
+ function scoreEntry([key, value], query, queryTokens) {
36
+ const normalizedKey = key.toLowerCase();
37
+ const searchText = `${key} ${value?.$description || ''} ${value?.$extensions?.id || ''} ${
38
+ value?.$extensions?.name || ''
39
+ }`.toLowerCase();
40
+ let score = normalizedKey === query ? 100 : normalizedKey.includes(query) ? 40 : 0;
41
+ for (const token of queryTokens) {
42
+ if (normalizedKey.includes(token)) score += 10;
43
+ if (searchText.includes(token)) score += 3;
44
+ }
45
+ return score;
46
+ }
47
+
48
+ const sourceName = argValue('--source', 'semantic');
49
+ const source = SOURCES[sourceName];
50
+ const query = argValue('--query').trim().toLowerCase();
51
+ const limit = Math.max(1, Number.parseInt(argValue('--limit', '5'), 10) || 5);
52
+
53
+ if (!source || !query) {
54
+ console.error(usage());
55
+ process.exit(2);
56
+ }
57
+
58
+ const response = await fetch(source.url);
59
+ if (!response.ok) {
60
+ throw new Error(`Token request failed with HTTP ${response.status}`);
61
+ }
62
+ const text = await response.text();
63
+ const actualHash = createHash('sha256').update(text).digest('hex');
64
+ if (actualHash !== source.sha256) {
65
+ throw new Error(`Token integrity check failed: expected ${source.sha256}, got ${actualHash}`);
66
+ }
67
+
68
+ const queryTokens = tokenize(query);
69
+ const matches = Object.entries(JSON.parse(text))
70
+ .map((entry) => ({ entry, score: scoreEntry(entry, query, queryTokens) }))
71
+ .filter(({ score }) => score > 0)
72
+ .sort((a, b) => b.score - a.score || a.entry[0].localeCompare(b.entry[0]))
73
+ .slice(0, limit)
74
+ .map(({ entry: [key, value] }) => ({ key, ...value, source: source.url }));
75
+
76
+ console.log(JSON.stringify(matches, null, 2));
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+
6
+ function usage() {
7
+ return `Usage:
8
+ node verify-static-html.mjs <file.html> [--sidebar]
9
+
10
+ Runs token-light pre-browser checks for Lark-style static HTML deliverables.
11
+ Use --sidebar only when the page contains a navigation sidebar. Shadow declarations are reported for contextual review.`;
12
+ }
13
+
14
+ const htmlPath = process.argv[2];
15
+ if (!htmlPath) {
16
+ console.error(usage());
17
+ process.exit(2);
18
+ }
19
+
20
+ const absoluteHtmlPath = path.resolve(htmlPath);
21
+ const html = await fs.readFile(absoluteHtmlPath, 'utf8');
22
+
23
+ const hardFailures = [];
24
+ const warnings = [];
25
+ const passed = [];
26
+
27
+ function pass(name, ok, failMessage, warn = false) {
28
+ if (ok) {
29
+ passed.push(name);
30
+ } else if (warn) {
31
+ warnings.push(failMessage);
32
+ } else {
33
+ hardFailures.push(failMessage);
34
+ }
35
+ }
36
+
37
+ pass(
38
+ 'no gradients',
39
+ !/(linear-gradient|radial-gradient|conic-gradient)/i.test(html),
40
+ 'Page UI contains gradients.',
41
+ );
42
+ const shadows = [...html.matchAll(/box-shadow\s*:\s*([^;}"'<>]+)/gi)]
43
+ .map((match) => match[1].trim())
44
+ .filter((value) => !/^none(?:\s*!important)?$/i.test(value));
45
+ pass(
46
+ 'no shadows requiring contextual review',
47
+ shadows.length === 0,
48
+ 'Shadow declarations found; verify they belong to permitted floating layers, not ordinary cards.',
49
+ true,
50
+ );
51
+ if (process.argv.includes('--sidebar')) {
52
+ pass(
53
+ 'fixed sidebar background',
54
+ html.includes('#f9f9f9'),
55
+ 'Sidebar background #f9f9f9 is missing.',
56
+ );
57
+ pass(
58
+ 'fixed selected sidebar state',
59
+ html.includes('#1f23290d'),
60
+ 'Selected sidebar state #1f23290d is missing.',
61
+ );
62
+ }
63
+ pass('has table', /<table[\s>]/i.test(html), 'No table found.', true);
64
+ pass('has search input', /type=["']search["']/i.test(html), 'No search input found.', true);
65
+ pass(
66
+ 'has local responsive CSS',
67
+ /@media\s*\(/i.test(html),
68
+ 'No responsive media query found.',
69
+ true,
70
+ );
71
+
72
+ const imageRefs = [...html.matchAll(/<img\b[^>]*\bsrc=["']([^"']+)["']/gi)].map(
73
+ (match) => match[1],
74
+ );
75
+ const localImageRefs = imageRefs.filter(
76
+ (src) => !/^(https?:)?\/\//.test(src) && !src.startsWith('data:'),
77
+ );
78
+ const missingImages = [];
79
+ for (const src of localImageRefs) {
80
+ try {
81
+ await fs.access(new URL(src, pathToFileURL(absoluteHtmlPath)));
82
+ } catch {
83
+ missingImages.push(src);
84
+ }
85
+ }
86
+
87
+ pass(
88
+ 'all local images exist',
89
+ missingImages.length === 0,
90
+ `Missing local image assets: ${missingImages.join(', ')}`,
91
+ );
92
+
93
+ const tableHeaders = [...html.matchAll(/<th\b[^>]*>([\s\S]*?)<\/th>/gi)]
94
+ .map((match) => match[1].replace(/<[^>]+>/g, '').trim())
95
+ .filter(Boolean);
96
+
97
+ console.log(
98
+ JSON.stringify(
99
+ {
100
+ file: absoluteHtmlPath,
101
+ passed,
102
+ warnings,
103
+ hardFailures,
104
+ stats: {
105
+ images: imageRefs.length,
106
+ localImages: localImageRefs.length,
107
+ tableHeaders,
108
+ },
109
+ },
110
+ null,
111
+ 2,
112
+ ),
113
+ );
114
+
115
+ if (hardFailures.length > 0) {
116
+ process.exit(1);
117
+ }
@@ -59,11 +59,11 @@ const userMap = new Map(users.map(u => [u.id, u]));
59
59
  - N+1 queries: per-item DB calls inside loops — use batch or JOIN
60
60
  - List endpoints without pagination — will break as data grows
61
61
  - OFFSET pagination on large datasets — use cursor pagination (WHERE id > cursor)
62
- - Multiple independent DB queries running serially — use Promise.all
62
+ - Multiple independent DB queries running serially — use Promise.all. The platform connection pool caps real concurrency at **7**; beyond that queries queue. It only pays off for IO-bound queries — CPU-heavy ones (large aggregations, big `generate_series`) still take the same total time, so for those the only fix is fewer/cheaper queries
63
63
  - External API calls without timeout
64
- - WHERE/ORDER BY columns missing indexes — **cross-check with schema definition: for every ORDER BY or WHERE column in service queries, verify a matching index exists in schema.ts**
64
+ - WHERE/ORDER BY columns missing indexes — **cross-check against the live DDL: for every ORDER BY or WHERE column in service queries, verify a matching index exists via `npx -y @lark-apaas/miaoda-data-cli db schema <table>`. Do NOT use `schema.ts` for this — it is a generated mirror that silently drops partial-index `WHERE` clauses and CHECK constraints, so an existing partial index reads as missing (and a partial unique index reads as full-table unique)**
65
65
  - Aggregation done in JS instead of SQL layer — fetch all rows then compute in app is a common anti-pattern
66
- - **Sorting fields used in list/ranking endpoints must have database indexes — read schema.ts and verify**
66
+ - **Sorting fields used in list/ranking endpoints must have database indexes — verify with `db schema <table>` (the live DDL), not `schema.ts`**
67
67
  - **Date-range loop expansion in JS (CRITICAL)**: code shaped like `for (let d = new Date(start); d <= end; d.setDate(d.getDate()+1)) for (item of items) ...` is O(N×days). For 1000 records over 90 days = 90,000 ops per request. Must be pushed to SQL using `generate_series(start, end, '1 day') + unnest(array_field) + GROUP BY ... HAVING COUNT(*) > N` — let the database do the date×row cross product
68
68
  - **Multiple endpoints sharing the same base data should be merged**: e.g. Dashboard frontend calling 4 separate stats endpoints, each internally re-querying the full teacher table to build a lookup map. Merge into a single overview endpoint that runs the 4 stats in parallel internally and shares one base-data query.
69
69
  - **Stats / Dashboard endpoints must have a default time window**: any query that scans the entire fact table for aggregation (`select ... from coursePlan` with no `where`) will degrade as data grows. Add a default `where startDate >= now() - interval '90 day'` (or business-appropriate window) so it can hit `idx_*_start_date`. Without a time window, even with indexes the planner may still seq-scan. If "all history" is required, use a materialized view or pre-aggregated table refreshed on schedule, never a live full-table scan.
@@ -3,6 +3,7 @@ name: reviewer-usage
3
3
  description: "Reviewer SubAgent dispatch & post-return protocol — required reading BEFORE invoking Reviewer. Covers when you MUST escalate to Reviewer after repeated self-fix failures, how many Reviewers to dispatch, how to split scopes, what the prompt must contain, and how to act on the per-issue policy tags Reviewer returns. 触发词:派 Reviewer、code review、审查、排查、review and fix、反复失败、修不好、repeated-fix"
4
4
  available-agents:
5
5
  - Code
6
+ - DesignModern
6
7
  ---
7
8
 
8
9
  # Reviewer Usage Guide
@@ -92,6 +93,7 @@ No AskUser, no confirmation, no asking the user.
92
93
  - **STEP 3a (echo)**: emit a short user-facing summary listing the `policy: AskUser` issues, grouped by 🔴 Critical / 🟠 Bug / 🟡 Suggestion, with file:line. ≤25 lines (collapse the rest as `... (其余 N 项见下方表单)`). The user cannot see Reviewer's report directly — without this echo they cannot decide.
93
94
  - **STEP 3b (call ask_user_question)**: in the same turn, immediately call `ask_user_question` — `header`: `Review 确认`; `description`: paste the same list as STEP 3a; ONE Radio `question`: "Reviewer 另发现以下超出本次任务范围的问题,是否一并处理?"; use the exact `options` and `recommended` label that the ACTION header specified.
94
95
  - No FINAL reply between 3a and 3b.
96
+ - **STEP 3b applies only if `ask_user_question` is in your tool list.** Some chains deliberately omit it (no confirmation step by design). If you do not have that tool: do STEP 3a only and stop there — this bucket degrades to report-only, and Step 4 does not apply. Do NOT seek confirmation by any other means (no free-text question, no other tool), and do NOT promote these issues to auto-fix. State plainly which issues were reported but not acted on; never present "not confirmed" as "no problem".
95
97
 
96
98
  ### Step 4 — Act on the user's choice
97
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-miaoda-sandbox-skills",
3
- "version": "0.1.0-dev.6f4e4bc",
3
+ "version": "0.1.0-dev.72ac425",
4
4
  "description": "Miaoda 合并沙箱 skills 包(包含原 miaoda-skills 的 miaoda / miaoda-modern / miaoda-design / shared 四条业务线);发布公网 npm,随沙箱运行时经 update-skills 同步到 .agent/skills/",
5
5
  "type": "module",
6
6
  "files": [
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: attachment
3
- description: "Use when user mentions attachments, uploaded files, or shared files. Handles preprocessing: listing, extracting archives, and reading files in .agent/{conversation_id}/attachments directory."
3
+ description: "附件预处理 / Attachment Preprocessing. Use when user mentions attachments, uploaded files, or shared files. 当用户提到附件、上传文件或共享文件时使用;仅负责列出、解压和读取 .agent/{conversation_id}/attachments 下的文件,后续处理由文件内容和用户意图决定。"
4
+ metadata:
5
+ display-names:
6
+ zh-CN: 附件预处理
7
+ en-US: Attachment Preprocessing
4
8
  ---
5
9
 
6
10
  # Attachment Preprocessing
@@ -1,46 +0,0 @@
1
- # 创意产物类型信号表与分类型拆分细则
2
-
3
- 创意模式产物是 design-html 技术栈的静态工程:`index.html` 为入口,可能带 `components/` 或 `screens/` 目录(`.jsx` 经 `<script type="text/babel">` 在浏览器内编译)。先读 `index.html` 全文 + 目录清单,再按下表判类型。**支持生成的只有前三行**(产品设计=多屏设计稿/移动端 mockup、可交互原型);报表/deck/动画为产品范围外,判中即停止并告知用户(入口层正常会拦截,此为防御兜底)。若会话上下文提供了「当前选中的设计稿文件」路径,判型与拆分优先以该文件为主入口。
4
-
5
- ## 类型判定信号表
6
-
7
- | 类型 | 判定信号(任一命中) | 实现形态 |
8
- |------|---------------------|----------|
9
- | 演示稿(deck) | `<deck-stage>` / 翻页控件 / 每屏是「第 N 页」式幻灯结构 | **范围外,停止并告知** |
10
- | 多屏设计稿(artboard) | `<DCArtboard>` / `design-canvas` 缩放壳 / 多画板平铺 + 画板 label | 每画板 → 路由页 |
11
- | 移动端 mockup | 设备框组件(iOS/Android 壳:`width:390` + 圆角 + 状态栏/灵动岛) + `screens/` 或 `screen-*.jsx` 多屏 | 每屏 → 路由页;框内 tab → 应用导航 |
12
- | 交互原型 | `useState` / 事件处理驱动页面切换,无画布壳,本身像单页应用 | 按其内部「视图状态」拆路由页 |
13
- | 报表看板 | echarts / 图表库 + 指标卡片布局 | **范围外,停止并告知** |
14
- | 纯动画/视觉演示 | 大量 keyframes/canvas 动画、无业务 UI | **范围外,停止并告知** |
15
-
16
- 混合信号时以「业务 UI 占比」为准:有表单/列表/详情等业务界面 → 按设计稿/原型类正常生成(防止 deck 里嵌了真实业务界面设计被误杀);纯展示 → 判为范围外。
17
-
18
- ## 分类型拆分细则
19
-
20
- ### 多屏设计稿 / 移动端 mockup
21
-
22
- 1. 枚举所有屏:`data-screen-label` 属性、画板 label、`screen-*.jsx` 文件名,三者取有值的作为页面名与路由名来源。
23
- 2. 稿内导航(底部 tab / 侧边栏 / 顶部菜单)转为工程导航结构;未出现在导航里的屏(如详情页、由弹层提级的独立页)按从属关系挂到父页面路由下。
24
- 3. 导航项逐项承接源稿:数量、文案、顺序与源稿一致。源稿导航中没有对应画板的项不得删除,也不得添加源稿没有的导航项;这类项的点击去向在方案中写明(例如指向相关页面的对应区块,或在方案的未决事项中说明),不得静默决定。
25
- 4. 设备框(手机壳、浏览器壳、`IOSFrame` 这类组件)是预览容器:取 `children` 内容做响应式页面;状态栏、灵动岛、Home 指示条随壳丢弃。
26
- 5. 稿内固定尺寸(390×844、1920×1080 加 scale 缩放)改为真响应式:内容宽度用容器约束(如 `max-width` + 居中),移动优先或桌面优先取决于原稿形态。
27
-
28
- ### 交互原型
29
-
30
- 1. 找出所有「视图状态」:`useState` 切换的 tab/step/modal 中承载独立业务界面的 → 路由页;纯交互细节(折叠、hover)→ 保留为组件内交互。
31
- 2. 原型里已经写了的交互逻辑(校验提示、乐观更新、步骤流转)是功能需求的最强信号,逐条纳入第 2 步的可见功能清单。
32
-
33
- ### 报表看板
34
-
35
- 范围外类型,不做生成。若混合产物中局部包含图表且整体被判为设计稿/原型类,图表部分按 `ui-to-function.md` 的"统计卡片/图表"映射行处理。
36
-
37
- ## 稿内机制识别速查
38
-
39
- | 稿内出现 | 含义 | 处置 |
40
- |----------|------|------|
41
- | `<script type="text/babel">` + `@babel/standalone` | 浏览器内编译 JSX 的预览机制 | 内容重写为工程组件,机制丢弃 |
42
- | `unpkg-sdk/react@*/umd/*.js`(miaoda.feishu.cn 镜像) | CDN React 运行时 | 丢弃,用工程自带 React |
43
- | `Object.assign(window, { Xxx })` | 组件挂全局供跨 `<script>` 引用 | 转译为模块 import/export |
44
- | `data-miaoda-*` / `data-screen-label` | 创意平台标注属性 | 属性丢弃;label 值用作路由/导航命名后即弃 |
45
- | `miaoda.feishu.cn/fonts/css2?family=...` | 字体镜像 | 保留,生产可用 |
46
- | tweaks / EDITMODE 面板 | 设计期调参 | 当前参数值固化进主题 token,面板删除 |