@dev-workflow/skill 0.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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -0
  3. package/bin/cli.mjs +9 -0
  4. package/package.json +39 -0
  5. package/skills/artifact/README.md +23 -0
  6. package/skills/execution/devFlow/SKILL.md +341 -0
  7. package/skills/execution/devFlow/agents/openai.yaml +7 -0
  8. package/skills/execution/devFlow/domains/backend/references/api-tech.md +173 -0
  9. package/skills/execution/devFlow/domains/backend/scripts/check_api_tech_doc.mjs +170 -0
  10. package/skills/execution/devFlow/domains/backend/scripts/check_api_tech_doc.test.mjs +89 -0
  11. package/skills/execution/devFlow/domains/backend/templates/api-tech.md +164 -0
  12. package/skills/execution/devFlow/domains/frontend/references/contract-check.md +270 -0
  13. package/skills/execution/devFlow/domains/frontend/references/foundation-freeze.md +92 -0
  14. package/skills/execution/devFlow/domains/frontend/references/lightweight-flow.md +55 -0
  15. package/skills/execution/devFlow/domains/frontend/references/page-build.md +268 -0
  16. package/skills/execution/devFlow/domains/frontend/references/page-tech.md +414 -0
  17. package/skills/execution/devFlow/domains/frontend/references/product-design-specs/field-common.md +69 -0
  18. package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-complex.md +105 -0
  19. package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-id-code.md +44 -0
  20. package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-number-date.md +106 -0
  21. package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-relation.md +144 -0
  22. package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-selectors.md +102 -0
  23. package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-text.md +130 -0
  24. package/skills/execution/devFlow/domains/frontend/references/product-design-specs/index.md +42 -0
  25. package/skills/execution/devFlow/domains/frontend/references/product-design-specs/interaction.md +61 -0
  26. package/skills/execution/devFlow/domains/frontend/scripts/check_page_tech_doc.mjs +82 -0
  27. package/skills/execution/devFlow/domains/frontend/scripts/contract_check_static.mjs +145 -0
  28. package/skills/execution/devFlow/domains/frontend/scripts/generate_foundation_summary.mjs +224 -0
  29. package/skills/execution/devFlow/domains/frontend/templates/contract-report.md +66 -0
  30. package/skills/execution/devFlow/domains/frontend/templates/mini-plan-l0.md +7 -0
  31. package/skills/execution/devFlow/domains/frontend/templates/mini-plan-l1.md +9 -0
  32. package/skills/execution/devFlow/domains/frontend/templates/page-build/component.tsx.tpl +8 -0
  33. package/skills/execution/devFlow/domains/frontend/templates/page-build/constants.ts.tpl +5 -0
  34. package/skills/execution/devFlow/domains/frontend/templates/page-build/index.ts.tpl +1 -0
  35. package/skills/execution/devFlow/domains/frontend/templates/page-build/route.tsx.tpl +11 -0
  36. package/skills/execution/devFlow/domains/frontend/templates/page-build/service.ts.tpl +17 -0
  37. package/skills/execution/devFlow/domains/frontend/templates/page-build/types.ts.tpl +11 -0
  38. package/skills/execution/devFlow/domains/frontend/templates/page-tech.md +103 -0
  39. package/skills/execution/devFlow/domains/requirement/references/prd-review.md +129 -0
  40. package/skills/execution/devFlow/domains/requirement/templates/prd-review.md +57 -0
  41. package/skills/execution/figmaSync/SKILL.md +349 -0
  42. package/skills/execution/figmaSync/chapters/apply.md +177 -0
  43. package/skills/execution/figmaSync/chapters/plan.md +359 -0
  44. package/skills/execution/figmaSync/chapters/prepare.md +47 -0
  45. package/skills/execution/figmaSync/examples/native-css.md +129 -0
  46. package/skills/execution/figmaSync/examples/plan-doc-sample.md +39 -0
  47. package/skills/execution/figmaSync/scripts/_shared/session-log.mjs +82 -0
  48. package/skills/execution/figmaSync/scripts/_shared/token-resolver.mjs +132 -0
  49. package/skills/execution/figmaSync/scripts/_shared/token-source.mjs +145 -0
  50. package/skills/execution/figmaSync/scripts/figma-sync-report.mjs +448 -0
  51. package/skills/execution/figmaSync/scripts/icon-inventory.mjs +165 -0
  52. package/skills/execution/figmaSync/scripts/lookup-var.mjs +184 -0
  53. package/skills/execution/figmaSync/scripts/match-token.mjs +210 -0
  54. package/skills/execution/figmaSync/scripts/prepare-check.mjs +290 -0
  55. package/skills/execution/figmaSync/scripts/verify-plan.mjs +458 -0
  56. package/skills/execution/figmaSync/snapshots/README.md +13 -0
  57. package/skills/execution/figmaSync/snapshots/last-sync.md +1538 -0
  58. package/skills/execution/figmaSync/templates/PLAN.md.tpl +227 -0
  59. package/skills/review/consistency-checker/SKILL.md +112 -0
  60. package/skills/review/consistency-checker/rules/README.md +16 -0
  61. package/src/cli/agents.mjs +16 -0
  62. package/src/cli/copy.mjs +95 -0
  63. package/src/cli/install.mjs +131 -0
  64. package/src/cli/prompts.mjs +66 -0
  65. package/tools/lark/README.md +78 -0
  66. package/tools/lark/references/lark-doc.md +156 -0
  67. package/tools/lark/references/lark-read.md +235 -0
  68. package/tools/lark/references/prepare.md +147 -0
  69. package/tools/lark/scripts/lark_api.mjs +404 -0
  70. package/tools/lark/scripts/lark_api.test.mjs +63 -0
  71. package/tools/lark/scripts/lark_check_permissions.mjs +52 -0
  72. package/tools/lark/scripts/lark_publish_doc.mjs +299 -0
  73. package/tools/lark/scripts/lark_read_docx.mjs +328 -0
  74. package/tools/lark/scripts/markdown_to_lark_blocks.mjs +397 -0
@@ -0,0 +1,397 @@
1
+ import { readFileSync } from 'node:fs'
2
+
3
+ import { parseArgs, printJson } from './lark_api.mjs'
4
+
5
+ const MAX_TEXT_LENGTH = 1800
6
+ const MAX_TABLE_CELLS = 45
7
+ // 飞书 docx 单次创建表格 row_size 上限为 9,超过返回 1770001 invalid param。
8
+ const MAX_TABLE_ROWS = 9
9
+ const CODE_LANGUAGE_MAP = {
10
+ javascript: 3,
11
+ js: 3,
12
+ typescript: 3,
13
+ ts: 3,
14
+ json: 19,
15
+ bash: 14,
16
+ sh: 14,
17
+ shell: 14
18
+ }
19
+
20
+ function splitLongText(text) {
21
+ if (text.length <= MAX_TEXT_LENGTH) return [text]
22
+
23
+ const chunks = []
24
+ let rest = text
25
+
26
+ while (rest.length > MAX_TEXT_LENGTH) {
27
+ let cut = rest.lastIndexOf('\n', MAX_TEXT_LENGTH)
28
+ if (cut < MAX_TEXT_LENGTH * 0.5) cut = rest.lastIndexOf('。', MAX_TEXT_LENGTH)
29
+ if (cut < MAX_TEXT_LENGTH * 0.5) cut = MAX_TEXT_LENGTH
30
+
31
+ chunks.push(rest.slice(0, cut).trim())
32
+ rest = rest.slice(cut).trim()
33
+ }
34
+
35
+ if (rest) chunks.push(rest)
36
+ return chunks
37
+ }
38
+
39
+ function createTextBlock(text) {
40
+ return {
41
+ block_type: 2,
42
+ text: {
43
+ elements: createTextElements(text)
44
+ }
45
+ }
46
+ }
47
+
48
+ function createBulletBlock(text) {
49
+ return {
50
+ block_type: 12,
51
+ bullet: {
52
+ elements: createTextElements(text)
53
+ }
54
+ }
55
+ }
56
+
57
+ function createOrderedBlock(text, sequence) {
58
+ return {
59
+ block_type: 13,
60
+ ordered: {
61
+ elements: createTextElements(text),
62
+ style: {
63
+ sequence: String(sequence)
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ function createCalloutBlock(text, emoji) {
70
+ const block = {
71
+ block_type: 19,
72
+ callout: { background_color: 1, border_color: 1, emoji_id: emoji || 'memo' }
73
+ }
74
+ // 文本作为子块由 writeBlocks 经 descendant API 写入,避免污染 JSON 序列化。
75
+ Object.defineProperty(block, '__calloutText', { value: text })
76
+ return block
77
+ }
78
+
79
+ function createQuoteBlock(text) {
80
+ const block = { block_type: 34, quote_container: {} }
81
+ Object.defineProperty(block, '__quoteText', { value: text })
82
+ return block
83
+ }
84
+
85
+ function createHeadingBlock(level, text) {
86
+ const blockType = level === 1 ? 3 : level === 2 ? 4 : 5
87
+ const key = level === 1 ? 'heading1' : level === 2 ? 'heading2' : 'heading3'
88
+
89
+ return {
90
+ block_type: blockType,
91
+ [key]: {
92
+ elements: createTextElements(text)
93
+ }
94
+ }
95
+ }
96
+
97
+ function createTextElements(text) {
98
+ const elements = []
99
+ // 同时识别行内代码 `code` 与粗体 **bold**。
100
+ const pattern = /(`[^`\n]+`)|(\*\*[^*\n]+\*\*)/g
101
+ let lastIndex = 0
102
+ let match
103
+
104
+ while ((match = pattern.exec(text))) {
105
+ if (match.index > lastIndex) {
106
+ elements.push({ text_run: { content: text.slice(lastIndex, match.index) } })
107
+ }
108
+
109
+ const token = match[0]
110
+ if (token.startsWith('`')) {
111
+ elements.push({
112
+ text_run: { content: token.slice(1, -1), text_element_style: { inline_code: true } }
113
+ })
114
+ } else {
115
+ elements.push({
116
+ text_run: { content: token.slice(2, -2), text_element_style: { bold: true } }
117
+ })
118
+ }
119
+
120
+ lastIndex = pattern.lastIndex
121
+ }
122
+
123
+ if (lastIndex < text.length) {
124
+ elements.push({ text_run: { content: text.slice(lastIndex) } })
125
+ }
126
+
127
+ return elements.length ? elements : [{ text_run: { content: '' } }]
128
+ }
129
+
130
+ function normalizeLanguage(language) {
131
+ const key = language.trim().toLowerCase()
132
+ return CODE_LANGUAGE_MAP[key] || CODE_LANGUAGE_MAP.text
133
+ }
134
+
135
+ function createCodeBlock(language, code) {
136
+ const block = {
137
+ block_type: 14,
138
+ code: {
139
+ elements: [{ text_run: { content: code } }]
140
+ }
141
+ }
142
+ const languageCode = normalizeLanguage(language)
143
+
144
+ if (languageCode) {
145
+ block.code.style = { language: languageCode }
146
+ }
147
+
148
+ return block
149
+ }
150
+
151
+ function splitTableRow(line) {
152
+ const trimmed = line.trim().replace(/^\|/, '').replace(/\|$/, '')
153
+ // 仅按未转义的 | 切分单元格,再把 \| 还原为字面 |(支持单元格内的联合类型如 'A' \| 'B')。
154
+ return trimmed.split(/(?<!\\)\|/).map((cell) => cell.trim().replace(/\\\|/g, '|'))
155
+ }
156
+
157
+ function isTableSeparator(line) {
158
+ const cells = splitTableRow(line)
159
+ return cells.length > 1 && cells.every((cell) => /^:?-{3,}:?$/.test(cell))
160
+ }
161
+
162
+ function isTableStart(lines, index) {
163
+ return (
164
+ index + 1 < lines.length && lines[index].includes('|') && isTableSeparator(lines[index + 1])
165
+ )
166
+ }
167
+
168
+ function collectTable(lines, startIndex) {
169
+ const rows = [splitTableRow(lines[startIndex])]
170
+ let index = startIndex + 2
171
+
172
+ while (index < lines.length && lines[index].trim() && lines[index].includes('|')) {
173
+ rows.push(splitTableRow(lines[index]))
174
+ index += 1
175
+ }
176
+
177
+ return { rows, nextIndex: index }
178
+ }
179
+
180
+ function createTableBlocks(rows) {
181
+ const columnSize = Math.max(...rows.map((row) => row.length))
182
+ const normalizedRows = rows.map((row) => [
183
+ ...row,
184
+ ...Array.from({ length: columnSize - row.length }, () => '')
185
+ ])
186
+ const maxRows = Math.max(
187
+ 2,
188
+ Math.min(MAX_TABLE_ROWS, Math.floor(MAX_TABLE_CELLS / columnSize))
189
+ )
190
+ const tables = []
191
+ const header = normalizedRows[0]
192
+ const bodyRows = normalizedRows.slice(1)
193
+ const chunks = []
194
+
195
+ if (normalizedRows.length <= maxRows) {
196
+ chunks.push(normalizedRows)
197
+ } else {
198
+ const bodyChunkSize = Math.max(1, maxRows - 1)
199
+
200
+ for (let index = 0; index < bodyRows.length; index += bodyChunkSize) {
201
+ chunks.push([header, ...bodyRows.slice(index, index + bodyChunkSize)])
202
+ }
203
+ }
204
+
205
+ for (const chunk of chunks) {
206
+ tables.push(createTableBlock(chunk, columnSize))
207
+ }
208
+
209
+ return tables
210
+ }
211
+
212
+ const TABLE_TOTAL_WIDTH = 760
213
+ const TABLE_MIN_COL_WIDTH = 56
214
+
215
+ function displayWidth(text) {
216
+ let width = 0
217
+ for (const ch of String(text)) {
218
+ width += /[一-鿿 -〿＀-￯]/.test(ch) ? 2 : 1
219
+ }
220
+ return width
221
+ }
222
+
223
+ function computeColumnWidths(rows, columnSize) {
224
+ // 满宽自适应:按每列最长内容(中文计 2 宽)分配,归一到 ~760,长枚举名少换行、行更矮。
225
+ const weights = Array.from({ length: columnSize }, (_, col) =>
226
+ Math.max(2, ...rows.map((row) => displayWidth(row[col] ?? '')))
227
+ )
228
+ const sum = weights.reduce((acc, value) => acc + value, 0) || columnSize
229
+ const widths = weights.map((weight) =>
230
+ Math.max(TABLE_MIN_COL_WIDTH, Math.round((TABLE_TOTAL_WIDTH * weight) / sum))
231
+ )
232
+ const diff = TABLE_TOTAL_WIDTH - widths.reduce((acc, value) => acc + value, 0)
233
+ widths[columnSize - 1] = Math.max(TABLE_MIN_COL_WIDTH, widths[columnSize - 1] + diff)
234
+ return widths
235
+ }
236
+
237
+ function createTableBlock(rows, columnSize) {
238
+ const block = {
239
+ block_type: 31,
240
+ table: {
241
+ property: {
242
+ row_size: rows.length,
243
+ column_size: columnSize
244
+ }
245
+ }
246
+ }
247
+
248
+ Object.defineProperty(block, '__tableRows', {
249
+ value: rows
250
+ })
251
+ // 表头底色与列宽建表时不被接受,由 writeBlocks 在建表后 PATCH 设置。
252
+ Object.defineProperty(block, '__columnWidth', {
253
+ value: computeColumnWidths(rows, columnSize)
254
+ })
255
+
256
+ return block
257
+ }
258
+
259
+ export function markdownToLarkBlocks(markdown) {
260
+ const lines = markdown.replace(/\r\n/g, '\n').split('\n')
261
+ const blocks = []
262
+ let paragraph = []
263
+ let codeFence = null
264
+ let codeLines = []
265
+ let orderedSequence = 1
266
+
267
+ const flushParagraph = () => {
268
+ const text = paragraph.join('\n').trim()
269
+ paragraph = []
270
+
271
+ if (!text) return
272
+
273
+ for (const chunk of splitLongText(text)) {
274
+ blocks.push(createTextBlock(chunk))
275
+ }
276
+ }
277
+
278
+ const flushCode = () => {
279
+ const language = codeFence ? codeFence.replace(/^```/, '').trim() : ''
280
+ const body = codeLines.join('\n')
281
+ codeFence = null
282
+ codeLines = []
283
+
284
+ for (const chunk of splitLongText(body)) {
285
+ blocks.push(createCodeBlock(language, chunk))
286
+ }
287
+ }
288
+
289
+ for (let index = 0; index < lines.length; index += 1) {
290
+ const line = lines[index]
291
+
292
+ if (line.startsWith('```')) {
293
+ if (codeFence) {
294
+ flushCode()
295
+ } else {
296
+ flushParagraph()
297
+ codeFence = line
298
+ codeLines = []
299
+ }
300
+ continue
301
+ }
302
+
303
+ if (isTableStart(lines, index)) {
304
+ flushParagraph()
305
+ orderedSequence = 1
306
+ const table = collectTable(lines, index)
307
+ blocks.push(...createTableBlocks(table.rows))
308
+ index = table.nextIndex - 1
309
+ continue
310
+ }
311
+
312
+ if (codeFence) {
313
+ codeLines.push(line)
314
+ continue
315
+ }
316
+
317
+ const heading = /^(#{1,3})\s+(.+)$/.exec(line)
318
+ if (heading) {
319
+ flushParagraph()
320
+ blocks.push(createHeadingBlock(heading[1].length, heading[2].trim()))
321
+ continue
322
+ }
323
+
324
+ // 高亮块 callout:`> [!TIP] 文本` / `> [!NOTE] ...` / `> [!WARNING] ...`。
325
+ const calloutMatch = /^>\s*\[!(\w+)\]\s*(.*)$/.exec(line)
326
+ if (calloutMatch) {
327
+ flushParagraph()
328
+ orderedSequence = 1
329
+ const type = calloutMatch[1].toUpperCase()
330
+ const emoji = type === 'WARNING' || type === 'WARN' ? 'warning' : type === 'TIP' ? 'bulb' : 'memo'
331
+ blocks.push(createCalloutBlock(calloutMatch[2].trim(), emoji))
332
+ continue
333
+ }
334
+
335
+ // 引用块:`> 文本` → 飞书 quote_container(引用样式),由 writeBlocks 经 descendant API 写入。
336
+ const quote = /^>\s?(.*)$/.exec(line)
337
+ if (quote) {
338
+ flushParagraph()
339
+ orderedSequence = 1
340
+ const text = quote[1].trim()
341
+ if (text) blocks.push(createQuoteBlock(text))
342
+ continue
343
+ }
344
+
345
+ if (line.trim() === '') {
346
+ flushParagraph()
347
+ orderedSequence = 1
348
+ continue
349
+ }
350
+
351
+ // 分割线:独立的 --- / *** / ___(非表格分隔行)→ 飞书 divider 块。
352
+ if (/^(-{3,}|\*{3,}|_{3,})$/.test(line.trim())) {
353
+ flushParagraph()
354
+ orderedSequence = 1
355
+ blocks.push({ block_type: 22, divider: {} })
356
+ continue
357
+ }
358
+
359
+ const bullet = /^\s*[-*]\s+(.+)$/.exec(line)
360
+ if (bullet) {
361
+ flushParagraph()
362
+ orderedSequence = 1
363
+ blocks.push(createBulletBlock(bullet[1].trim()))
364
+ continue
365
+ }
366
+
367
+ const ordered = /^\s*\d+[.)]\s+(.+)$/.exec(line)
368
+ if (ordered) {
369
+ flushParagraph()
370
+ blocks.push(createOrderedBlock(ordered[1].trim(), orderedSequence))
371
+ orderedSequence += 1
372
+ continue
373
+ }
374
+
375
+ orderedSequence = 1
376
+ paragraph.push(line)
377
+ }
378
+
379
+ if (codeFence) flushCode()
380
+ flushParagraph()
381
+
382
+ return blocks
383
+ }
384
+
385
+ async function main() {
386
+ const args = parseArgs()
387
+ const file = args.file
388
+ const markdown = file ? readFileSync(file, 'utf8') : readFileSync(0, 'utf8')
389
+ printJson({ blocks: markdownToLarkBlocks(markdown) })
390
+ }
391
+
392
+ if (import.meta.url === `file://${process.argv[1]}`) {
393
+ main().catch((error) => {
394
+ console.error(error.message)
395
+ process.exit(1)
396
+ })
397
+ }