@hupan56/wlkj 2.7.11 → 3.0.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 (152) hide show
  1. package/bin/cli.js +375 -78
  2. package/package.json +29 -29
  3. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +23 -0
  4. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +23 -0
  5. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +1 -0
  6. package/templates/.qoder/learning/eval-history.jsonl +28 -0
  7. package/templates/data/index/wiki-index.json +8 -0
  8. package/templates/qoder/agents/insight-planning.md +1 -1
  9. package/templates/qoder/agents/insight-research.md +28 -15
  10. package/templates/qoder/commands/optional/wl-insight.md +159 -161
  11. package/templates/qoder/commands/optional/wl-report.md +4 -4
  12. package/templates/qoder/commands/optional/wl-status.md +2 -2
  13. package/templates/qoder/commands/wl-code.md +2 -2
  14. package/templates/qoder/commands/wl-design.md +2 -2
  15. package/templates/qoder/commands/wl-init.md +3 -3
  16. package/templates/qoder/commands/wl-prd.md +2 -2
  17. package/templates/qoder/commands/wl-req.md +43 -0
  18. package/templates/qoder/commands/wl-search.md +8 -8
  19. package/templates/qoder/commands/wl-task.md +17 -17
  20. package/templates/qoder/commands/wl-test.md +41 -15
  21. package/templates/qoder/config.yaml +17 -1
  22. package/templates/qoder/hooks/session-start.py +12 -22
  23. package/templates/qoder/nul +4 -0
  24. package/templates/qoder/rules/wl-pipeline.md +22 -48
  25. package/templates/qoder/scripts/README.md +139 -0
  26. package/templates/qoder/scripts/common/autotest_auth.py +109 -0
  27. package/templates/qoder/scripts/common/bootstrap.py +145 -0
  28. package/templates/qoder/scripts/common/check_publish.py +98 -0
  29. package/templates/qoder/scripts/common/cmd_registry.py +112 -0
  30. package/templates/qoder/scripts/common/config.py +187 -0
  31. package/templates/qoder/scripts/common/contract.py +317 -0
  32. package/templates/qoder/scripts/common/developer.py +2 -1
  33. package/templates/qoder/scripts/common/feishu.py +10 -9
  34. package/templates/qoder/scripts/common/guard.py +159 -0
  35. package/templates/qoder/scripts/common/identity.py +121 -2
  36. package/templates/qoder/scripts/common/kg_capabilities.py +182 -0
  37. package/templates/qoder/scripts/common/mcp_base.py +268 -0
  38. package/templates/qoder/scripts/common/paths.py +187 -1
  39. package/templates/qoder/scripts/common/result.py +223 -0
  40. package/templates/qoder/scripts/common/roles.py +60 -0
  41. package/templates/qoder/scripts/common/task_utils.py +21 -9
  42. package/templates/qoder/scripts/common/test_extract.py +115 -0
  43. package/templates/qoder/scripts/kg/__init__.py +11 -0
  44. package/templates/qoder/scripts/kg/build_entity_registry.py +196 -0
  45. package/templates/qoder/scripts/kg/build_relations.py +127 -0
  46. package/templates/qoder/scripts/{build_style_index.py → kg/build_style_index.py} +39 -10
  47. package/templates/qoder/scripts/kg/build_workflows.py +144 -0
  48. package/templates/qoder/scripts/{context_pack.py → kg/context_pack.py} +18 -11
  49. package/templates/qoder/scripts/{enrich_prompt.py → kg/enrich_prompt.py} +232 -226
  50. package/templates/qoder/scripts/{extract_api_params.py → kg/extract.py} +398 -246
  51. package/templates/qoder/scripts/{kg.py → kg/kg.py} +638 -708
  52. package/templates/qoder/scripts/{kg_build.py → kg/kg_build.py} +618 -612
  53. package/templates/qoder/scripts/{kg_build_db.py → kg/kg_build_db.py} +333 -327
  54. package/templates/qoder/scripts/{kg_duckdb.py → kg/kg_duckdb.py} +38 -37
  55. package/templates/qoder/scripts/{kg_incremental.py → kg/kg_incremental.py} +420 -393
  56. package/templates/qoder/scripts/{kg_link_db.py → kg/kg_link_db.py} +230 -224
  57. package/templates/qoder/scripts/{kg_semantic.py → kg/kg_semantic.py} +156 -150
  58. package/templates/qoder/scripts/kg/prefetch.py +359 -0
  59. package/templates/qoder/scripts/{search_index.py → kg/search_index.py} +70 -14
  60. package/templates/qoder/scripts/mcp/__init__.py +11 -0
  61. package/templates/qoder/scripts/{kg_mcp_server.py → mcp/kg_mcp_server.py} +77 -272
  62. package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → mcp/lanhu_stdio_wrapper.py} +125 -119
  63. package/templates/qoder/scripts/{check_mcp.py → mcp/mcp_doctor.py} +515 -298
  64. package/templates/qoder/scripts/{mcp_launcher.py → mcp/mcp_launcher.py} +442 -414
  65. package/templates/qoder/scripts/{mysql_mcp_server.py → mcp/mysql_mcp_server.py} +347 -396
  66. package/templates/qoder/scripts/{zentao_mcp_server.py → mcp/zentao_mcp_server.py} +384 -424
  67. package/templates/qoder/scripts/report/__init__.py +11 -0
  68. package/templates/qoder/scripts/{add_session.py → report/add_session.py} +250 -244
  69. package/templates/qoder/scripts/{archive_prd.py → report/archive_prd.py} +383 -377
  70. package/templates/qoder/scripts/{eval_prd.py → report/eval_prd.py} +73 -11
  71. package/templates/qoder/scripts/{export.py → report/export.py} +63 -0
  72. package/templates/qoder/scripts/{fill_prototype.py → report/fill_prototype.py} +6 -0
  73. package/templates/qoder/scripts/{gen_design_doc.py → report/gen_design_doc.py} +400 -394
  74. package/templates/qoder/scripts/{learn.py → report/learn.py} +152 -146
  75. package/templates/qoder/scripts/{learn_aggregate.py → report/learn_aggregate.py} +207 -201
  76. package/templates/qoder/scripts/{report.py → report/report.py} +287 -281
  77. package/templates/qoder/scripts/report/req.py +222 -0
  78. package/templates/qoder/scripts/report/role.py +33 -0
  79. package/templates/qoder/scripts/{status.py → report/status.py} +634 -628
  80. package/templates/qoder/scripts/setup/__init__.py +11 -0
  81. package/templates/qoder/scripts/setup/carriers.py +662 -0
  82. package/templates/qoder/scripts/{init_doctor.py → setup/init_doctor.py} +63 -26
  83. package/templates/qoder/scripts/{install_qoderwork.py → setup/install_qoderwork.py} +36 -26
  84. package/templates/qoder/scripts/{platform_doctor.py → setup/platform_doctor.py} +265 -259
  85. package/templates/qoder/scripts/{repo_root.py → setup/repo_root.py} +112 -106
  86. package/templates/qoder/scripts/{setup.py → setup/setup.py} +147 -0
  87. package/templates/qoder/scripts/{setup_lanhu.py → setup/setup_lanhu.py} +973 -963
  88. package/templates/qoder/scripts/task/__init__.py +11 -0
  89. package/templates/qoder/scripts/{git_sync.py → task/git_sync.py} +52 -31
  90. package/templates/qoder/scripts/{syncgate.py → task/syncgate.py} +6 -0
  91. package/templates/qoder/scripts/task/task.py +221 -0
  92. package/templates/qoder/scripts/task/task_lifecycle.py +596 -0
  93. package/templates/qoder/scripts/task/task_query.py +161 -0
  94. package/templates/qoder/scripts/task/task_relations.py +424 -0
  95. package/templates/qoder/scripts/{team_sync.py → task/team_sync.py} +93 -20
  96. package/templates/qoder/scripts/test/__init__.py +11 -0
  97. package/templates/qoder/scripts/{autotest.py → test/autotest.py} +1174 -1751
  98. package/templates/qoder/scripts/{autotest_batch.py → test/autotest_batch.py} +242 -224
  99. package/templates/qoder/scripts/test/autotest_data.py +675 -0
  100. package/templates/qoder/scripts/{autotest_run.py → test/autotest_run.py} +309 -297
  101. package/templates/qoder/scripts/{benchmark.py → test/benchmark.py} +6 -0
  102. package/templates/qoder/scripts/{kg_auto_login.py → test/kg_auto_login.py} +202 -196
  103. package/templates/qoder/scripts/{kg_test_runner.py → test/kg_test_runner.py} +7 -1
  104. package/templates/qoder/scripts/{page_probe.py → test/page_probe.py} +465 -459
  105. package/templates/qoder/scripts/wlkj.py +116 -0
  106. package/templates/qoder/settings.json +1 -10
  107. package/templates/qoder/skills/design-import/SKILL.md +226 -226
  108. package/templates/qoder/skills/design-review/SKILL.md +82 -82
  109. package/templates/qoder/skills/prd-generator/SKILL.md +26 -16
  110. package/templates/qoder/skills/prd-review/SKILL.md +5 -5
  111. package/templates/qoder/skills/prototype-generator/SKILL.md +256 -256
  112. package/templates/qoder/skills/spec-coder/SKILL.md +4 -4
  113. package/templates/qoder/skills/spec-generator/SKILL.md +4 -4
  114. package/templates/qoder/skills/test-generator/SKILL.md +5 -5
  115. package/templates/qoder/skills/wl-code/SKILL.md +4 -4
  116. package/templates/qoder/skills/wl-commit/SKILL.md +4 -4
  117. package/templates/qoder/skills/wl-design/SKILL.md +3 -3
  118. package/templates/qoder/skills/wl-init/SKILL.md +8 -8
  119. package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
  120. package/templates/qoder/skills/wl-prd-full/SKILL.md +6 -6
  121. package/templates/qoder/skills/wl-prd-quick/SKILL.md +6 -6
  122. package/templates/qoder/skills/wl-prd-review/SKILL.md +4 -4
  123. package/templates/qoder/skills/wl-report/SKILL.md +7 -7
  124. package/templates/qoder/skills/wl-search/SKILL.md +13 -13
  125. package/templates/qoder/skills/wl-spec/SKILL.md +5 -5
  126. package/templates/qoder/skills/wl-status/SKILL.md +5 -5
  127. package/templates/qoder/skills/wl-task/SKILL.md +6 -6
  128. package/templates/qoder/skills/wl-test/SKILL.md +102 -39
  129. package/templates/root/AGENTS.md +39 -40
  130. package/templates/qoder/hooks/inject-workflow-state.py +0 -169
  131. package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
  132. package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
  133. package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
  134. package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
  135. package/templates/qoder/scripts/check_carriers.py +0 -238
  136. package/templates/qoder/scripts/check_mcp_launch.py +0 -183
  137. package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
  138. package/templates/qoder/scripts/collect_prds.py +0 -31
  139. package/templates/qoder/scripts/common/mentions.py +0 -134
  140. package/templates/qoder/scripts/common/utf8.py +0 -38
  141. package/templates/qoder/scripts/extract_routes.py +0 -54
  142. package/templates/qoder/scripts/extract_routes_tree.py +0 -78
  143. package/templates/qoder/scripts/handoff.py +0 -22
  144. package/templates/qoder/scripts/init_developer.py +0 -76
  145. package/templates/qoder/scripts/parse_prds.py +0 -33
  146. package/templates/qoder/scripts/role.py +0 -51
  147. package/templates/qoder/scripts/sync_carriers.py +0 -259
  148. package/templates/qoder/scripts/task.py +0 -1261
  149. package/templates/qoder/scripts/workspace_init.py +0 -102
  150. package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
  151. package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
  152. /package/templates/qoder/scripts/{secure-ls.js → test/secure-ls.js} +0 -0
@@ -1,394 +1,400 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- gen_design_doc.py - 自动生成 DESIGN.md (给 AI 的设计系统说明书)
5
-
6
- ui-style.json + ref-vben-style.json + ref-icon.json 等索引数据
7
- 组装成一个单一文件,AI 出原型前必须先读这个文件。
8
-
9
- 解决了业界最佳实践的核心思路:
10
- - v0.dev: AI Registry 用正确 token 生成
11
- - design.md (GitHub): 从 URL 提取 → DESIGN.md → AI 读
12
- - 我们: 从真实代码索引 DESIGN.md → AI 读 → 不再猜风格
13
-
14
- Usage:
15
- python gen_design_doc.py # 生成到 data/design/DESIGN.md
16
- python gen_design_doc.py --stdout # 输出到 stdout (给 AI 直接读)
17
- """
18
-
19
- import os
20
- import sys
21
- import json
22
- import re
23
- import time
24
-
25
- # UTF-8 stdio
26
- try:
27
- sys.stdout.reconfigure(encoding='utf-8', errors='replace')
28
- except (AttributeError, TypeError, OSError, IOError):
29
- try:
30
- sys.stdout.reconfigure(encoding='utf-8')
31
- except Exception:
32
- pass
33
-
34
- BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
35
- INDEX_DIR = os.path.join(BASE, 'data', 'index')
36
- OUTPUT_DIR = os.path.join(BASE, 'data', 'design')
37
-
38
-
39
- def load_json(filename):
40
- path = os.path.join(INDEX_DIR, filename)
41
- if not os.path.isfile(path):
42
- return {}
43
- try:
44
- with open(path, encoding='utf-8') as f:
45
- return json.load(f)
46
- except Exception:
47
- return {}
48
-
49
-
50
- def gen_design_doc():
51
- """生成 DESIGN.md 内容。返回字符串。"""
52
- # 加载数据源
53
- ui_style = load_json('ui-style.json')
54
- vben = load_json('ref-vben-style.json')
55
- icon = load_json('ref-icon.json')
56
- chart_style = load_json('ref-chart-style.json')
57
-
58
- lines = []
59
- lines.append('# DESIGN.md - AI Prototype Design System Reference')
60
- lines.append('')
61
- lines.append('> **This file is the SINGLE SOURCE OF TRUTH for AI when generating prototypes.')
62
- lines.append('> AI MUST read this file before generating ANY prototype. Do NOT invent styles.**')
63
- lines.append('')
64
- lines.append('> Auto-generated by gen_design_doc.py from real code indexes.')
65
- lines.append(f'> Generated: {time.strftime("%Y-%m-%d %H:%M")}')
66
- lines.append('')
67
- lines.append('---')
68
- lines.append('')
69
-
70
- # ========== 1. Layout Fingerprint ==========
71
- lines.append('## 1. Layout Fingerprint (from real pages)')
72
- lines.append('')
73
- for proj_name, pdata in (ui_style.get('projects') or {}).items():
74
- fp = pdata.get('layout_fingerprint')
75
- if not fp:
76
- continue
77
- lines.append(f'### {proj_name}')
78
- lines.append('')
79
- sidebar_w = fp.get('sidebar_width') or '?'
80
- radius_v = fp.get('radius') or '?'
81
- lines.append(f'- **Layout mode**: {fp.get("layout", "unknown")} (determines sidebar/header structure)')
82
- lines.append(f'- **Stack**: {fp.get("stack", "unknown")}')
83
- lines.append(f'- **Theme**: {fp.get("theme_mode", "light")}')
84
- lines.append(f'- **Sidebar**: width={sidebar_w}px, semi_dark={fp.get("semi_dark_sidebar", False)}')
85
- lines.append(f'- **Topbar**: dark header with logo + tools')
86
- lines.append(f'- **Access mode**: {fp.get("access_mode", "unknown")} (menus come from backend API)')
87
- lines.append(f'- **Border radius**: {radius_v}rem')
88
-
89
- if fp.get('layout') == 'mixed-nav':
90
- lines.append('')
91
- lines.append('**mixed-nav layout means:**')
92
- lines.append('- Top bar: logo + notification + avatar (dark background)')
93
- lines.append('- Left sidebar: module-level menus (with icons)')
94
- lines.append('- Content area: breadcrumb + page header + content')
95
- lines.append('- Menu depth: 3-4 levels supported (ParentView nesting)')
96
- lines.append('')
97
-
98
- # ========== 2. Design Tokens ==========
99
- lines.append('## 2. Design Tokens (CSS Variables)')
100
- lines.append('')
101
- lines.append('**MUST use these exact values. NEVER hardcode #1890ff (Ant Design default).**')
102
- lines.append('')
103
-
104
- # From vben reference
105
- dt = vben.get('design_tokens', {})
106
- if dt:
107
- lines.append('### Color Tokens (from vben-style-reference)')
108
- lines.append('')
109
- color_tokens = {k: v for k, v in dt.items()
110
- if 'color' in k.lower() or 'background' in k.lower()
111
- or 'foreground' in k.lower() or 'primary' in k.lower()
112
- or 'destructive' in k.lower() or 'success' in k.lower()
113
- or 'warning' in k.lower() or 'border' in k.lower()
114
- or 'input' in k.lower() or 'muted' in k.lower()
115
- or 'card' in k.lower() or 'popover' in k.lower()
116
- or 'radius' in k.lower() or 'font' in k.lower()}
117
- for k, v in color_tokens.items():
118
- lines.append(f' {k}: {v}')
119
- lines.append('')
120
-
121
- # From layout fingerprint (authoritative)
122
- fp_primary = None
123
- fp_destructive = None
124
- fp_success = None
125
- fp_warning = None
126
- # 优先取有 color_primary 的指纹 (fywl-ui 有完整颜色, Carmg-H5 可能没有)
127
- for proj_name, pdata in (ui_style.get('projects') or {}).items():
128
- fp = pdata.get('layout_fingerprint', {})
129
- if fp and fp.get('color_primary'):
130
- fp_primary = fp.get('color_primary')
131
- fp_destructive = fp.get('color_destructive')
132
- fp_success = fp.get('color_success')
133
- fp_warning = fp.get('color_warning')
134
- break
135
-
136
- lines.append('### Authoritative Colors (from layout_fingerprint - HIGHEST priority)')
137
- lines.append('')
138
- lines.append('```css')
139
- lines.append(':root {')
140
- if fp_primary:
141
- lines.append(f' --primary: {fp_primary}')
142
- if fp_destructive:
143
- lines.append(f' --destructive: {fp_destructive}')
144
- if fp_success:
145
- lines.append(f' --success: {fp_success}')
146
- if fp_warning:
147
- lines.append(f' --warning: {fp_warning}')
148
- lines.append(' --background-deep: hsl(216, 20.11%, 95.47%)')
149
- lines.append(' --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif')
150
- lines.append(' --radius: 0.25rem')
151
- lines.append('}')
152
- lines.append('```')
153
- lines.append('')
154
-
155
- lines.append('### FORBIDDEN Colors (NEVER use)')
156
- lines.append('')
157
- lines.append('- `#1890ff` - Ant Design default, NOT this project\'s primary')
158
- lines.append('- Any color not listed above without citing a source file')
159
- lines.append('')
160
-
161
- # ========== 3. Page Types ==========
162
- lines.append('## 3. Page Types (how this system structures pages)')
163
- lines.append('')
164
- for proj_name, pdata in (ui_style.get('projects') or {}).items():
165
- pt = pdata.get('page_types', {})
166
- if not pt:
167
- continue
168
- lines.append(f'### {proj_name} page type distribution')
169
- lines.append('')
170
- for page_type, count in sorted(pt.items(), key=lambda x: -x[1]):
171
- lines.append(f'- **{page_type}**: {count} pages')
172
- lines.append('')
173
-
174
- lines.append('### Common page patterns (extracted from real pages)')
175
- lines.append('')
176
- # page_examples 动态提取每种页面类型的真实布局模式
177
- _page_type_labels = {
178
- 'table-page': 'Table page (search + grid + pagination)',
179
- 'form-page': 'Form page (structured input, NOT a fixed template)',
180
- 'detail-page': 'Detail page (read-only data display)',
181
- 'modal': 'Modal/Drawer (popup form or detail)',
182
- 'dashboard': 'Dashboard (summary cards + charts)',
183
- }
184
- # 统计每种类型的真实布局模式
185
- for proj_name, pdata in (ui_style.get('projects') or {}).items():
186
- pe = pdata.get('page_examples') or {}
187
- for pt_key in ['table-page', 'form-page', 'detail-page', 'modal', 'dashboard']:
188
- examples = pe.get(pt_key, [])
189
- real_examples = [e for e in examples if isinstance(e, dict) and (e.get('layout') or e.get('form_fields') or e.get('table_columns') or e.get('behaviors') or e.get('form_components'))]
190
- if not real_examples:
191
- continue
192
- label = _page_type_labels.get(pt_key, pt_key)
193
- lines.append(f'- **{label}** ({proj_name}, {len(real_examples)} examples)')
194
- # 提取布局模式
195
- ex0 = real_examples[0]
196
- layout = ex0.get('layout', {})
197
- if layout:
198
- if layout.get('grid_cols'):
199
- lines.append(f' - Grid columns: {layout["grid_cols"]} (real pattern from {ex0["file"].split("/")[-1]})')
200
- if layout.get('label_width'):
201
- lines.append(f' - Label width: {layout["label_width"]}px')
202
- # 表单组件
203
- fc = ex0.get('form_components', [])
204
- if fc:
205
- lines.append(f' - Form components: {", ".join(fc[:6])}')
206
- # 交互行为 (侧边栏折叠/表格分页/表单模式)
207
- behaviors = ex0.get('behaviors', {})
208
- if behaviors:
209
- bh_strs = []
210
- for bk, bv in behaviors.items():
211
- bh_strs.append(f'{bk}={bv}')
212
- lines.append(f' - Behaviors: {", ".join(bh_strs)}')
213
- # 表单字段数
214
- ff_count = len(ex0.get('form_fields', []))
215
- if ff_count:
216
- lines.append(f' - Field count: {ff_count} fields (typical)')
217
- sample_fields = [f.get('label', f.get('field', '')) for f in ex0.get('form_fields', [])[:5]]
218
- lines.append(f' - Sample fields: {", ".join(sample_fields)}')
219
- # 表格列数
220
- tc_count = len(ex0.get('table_columns', []))
221
- if tc_count:
222
- lines.append(f' - Column count: {tc_count} columns (typical)')
223
- sample_cols = [c.get('title', c.get('field', '')) for c in ex0.get('table_columns', [])[:5]]
224
- lines.append(f' - Sample columns: {", ".join(sample_cols)}')
225
- lines.append(f' - Reference file: {ex0.get("file", "?")}')
226
- lines.append('')
227
- break # 只取第一个有数据的项目 (通常是 fywl-ui)
228
-
229
- lines.append('### Form page behavior modes (NOT a fixed template - choose by requirement)')
230
- lines.append('')
231
- lines.append('Form pages vary by section count. DO NOT use one fixed template. Match the mode to the requirement:')
232
- lines.append('- **Single-section form**: one form, 2-3 grid columns, label 80-140px, bottom save/reset buttons')
233
- lines.append('- **Multi-section form**: tabs or accordion separating sections, each section 2-3 columns')
234
- lines.append('- **Step wizard form**: horizontal step indicator + per-step form + back/next buttons')
235
- lines.append('- **Drawer form**: right-side drawer popup, single column, compact layout')
236
- lines.append('')
237
- lines.append('> fill_prototype.py extracts real grid_cols/label_width from page_examples.')
238
- lines.append('> The form layout adapts to the matched example, NOT a hardcoded shell.')
239
- lines.append('')
240
-
241
- # ========== 4. Top Fields ==========
242
- lines.append('## 4. Top Used Fields (real field names -> Chinese labels)')
243
- lines.append('')
244
- lines.append('These are the most common fields across all pages. Use them for new pages.')
245
- lines.append('')
246
- fm = ui_style.get('field_map') or {}
247
- top_fields = sorted(fm.items(), key=lambda x: len(x[1].get('titles', set()) if isinstance(x[1].get('titles'), set) else x[1].get('titles', [])), reverse=True)[:30]
248
- for field_name, info in top_fields:
249
- titles = info.get('titles', set())
250
- if isinstance(titles, set):
251
- title = next(iter(titles), field_name)
252
- elif isinstance(titles, list) and titles:
253
- title = titles[0]
254
- else:
255
- title = field_name
256
- count = info.get('count', 0)
257
- lines.append(f'- `{field_name}` -> {title} (used {count}x)')
258
- lines.append('')
259
-
260
- # ========== 4.5 Feature Modules (from prd-features.json) ==========
261
- pf = load_json('prd-features.json')
262
- features = pf.get('features', {}) if isinstance(pf, dict) else {}
263
- if features:
264
- lines.append('## 4.5 Feature Modules (系统有哪些功能模块)')
265
- lines.append('')
266
- lines.append('出原型时, 先确定需求属于哪个模块, 参考该模块的现有页面。')
267
- lines.append('')
268
- for feat_key, feat_info in list(features.items())[:12]:
269
- cn = feat_info.get('cn', feat_key)
270
- page_count = feat_info.get('page_count', 0)
271
- api_count = feat_info.get('api_count', 0)
272
- pages = feat_info.get('pages', [])
273
- sample_pages = ', '.join(pages[:2]) if pages else ''
274
- lines.append(f'- **{cn}** ({feat_key}): {page_count} pages, {api_count} APIs')
275
- if sample_pages:
276
- lines.append(f' - 标杆页面: {sample_pages}')
277
- lines.append('')
278
-
279
- # ========== 4.6 Real Buttons (from entity-registry.json) ==========
280
- er = load_json('entity-registry.json')
281
- entities = er.get('entities', {}) if isinstance(er, dict) else {}
282
- button_samples = []
283
- for eid, info in list(entities.items())[:500]:
284
- if not eid.startswith('B:'):
285
- continue
286
- props = info.get('properties', {}) if isinstance(info, dict) else {}
287
- btn_text = props.get('button_text', '')
288
- if btn_text and btn_text not in [b['text'] for b in button_samples]:
289
- button_samples.append({'text': btn_text, 'endpoint': props.get('endpoint', '')})
290
- if len(button_samples) >= 15:
291
- break
292
- if button_samples:
293
- lines.append('## 4.6 Real Button Texts (系统里真实的按钮文案)')
294
- lines.append('')
295
- lines.append('出原型时, 按钮文案用这些真实的, 不要编"新增/编辑"。')
296
- lines.append('')
297
- for b in button_samples:
298
- ep = ' -> ' + b['endpoint'] if b['endpoint'] else ''
299
- lines.append(f'- {b["text"]}{ep}')
300
- lines.append('')
301
-
302
- # ========== 5. Components ==========
303
- lines.append('## 5. UI Components (from real code)')
304
- lines.append('')
305
- cc = ui_style.get('common_components') or {}
306
- lines.append('### Most used Ant Design Vue components')
307
- lines.append('')
308
- for comp, count in list(cc.items())[:15]:
309
- lines.append(f'- {comp}: {count}')
310
- lines.append('')
311
-
312
- fc = ui_style.get('form_components') or {}
313
- lines.append('### Form components')
314
- lines.append('')
315
- for comp, count in list(fc.items())[:10]:
316
- lines.append(f'- {comp}: {count}')
317
- lines.append('')
318
-
319
- # ========== 6. Icons ==========
320
- lines.append('## 6. Icons')
321
- lines.append('')
322
- lines.append('**MUST use real icon system. NEVER use emoji.**')
323
- lines.append('')
324
- lines.append('- **Web (fywl-ui)**: Ant Design inline SVG icons')
325
- lines.append(' - Source: `data/index/ref-icon.json`')
326
- lines.append(' - Usage: `<svg viewBox="0 0 1024 1024" fill="currentColor">...</svg>`')
327
- lines.append(' - Size: 14-16px for menu items, 18-20px for actions')
328
- lines.append('- **APP (Carmg-H5)**: Vant font icons')
329
- lines.append(' - CDN: https://cdn.jsdelivr.net/npm/vant@4/lib/index.css')
330
- lines.append(' - Usage: `<i class="van-icon van-icon-xxx"></i>`')
331
- lines.append('')
332
-
333
- # ========== 7. Dashboard / Chart styles ==========
334
- chart_data = chart_style
335
- if chart_data:
336
- lines.append('## 7. Dashboard / Chart Styles')
337
- lines.append('')
338
- lines.append('Two in-system chart idioms:')
339
- lines.append('- **Dark screen** (monitor): background `#011845`, accent `#53CFF0`/`#FFE646`')
340
- lines.append('- **Light card** (workbench): white cards, blue accent `#2E61E0`')
341
- lines.append('')
342
- lines.append('Source: `data/index/ref-chart-style.json`')
343
- lines.append('')
344
-
345
- # ========== 8. Prototype Rules ==========
346
- lines.append('## 8. Prototype Rules (AI MUST follow)')
347
- lines.append('')
348
- lines.append('1. **Token priority**: layout_fingerprint > vben-style-reference > code > PDF')
349
- lines.append('2. **Only change the diff**: everything else identical to existing page')
350
- lines.append('3. **Single file**: one HTML with all CSS and JS inline')
351
- lines.append('4. **Interactive**: buttons click, forms fill, tables scroll')
352
- lines.append('5. **Icons**: real SVG/Webfont only, NEVER emoji')
353
- lines.append('6. **Colors**: from this file only, NEVER invent')
354
- lines.append('7. **Layout**: sidebar_width/topbar_height from layout_fingerprint')
355
- lines.append('8. **Source citation**: header comment MUST cite which files provided the style')
356
- lines.append('')
357
-
358
- # ========== 9. How to generate ==========
359
- lines.append('## 9. How to Generate a Prototype')
360
- lines.append('')
361
- lines.append('```bash')
362
- lines.append('# Step 1: Run fill_prototype (generates 80% draft with real data)')
363
- lines.append('python .qoder/scripts/fill_prototype.py <keyword> --platform <web|app>')
364
- lines.append('')
365
- lines.append('# Step 2: AI reads the draft + this DESIGN.md, does 20% adjustment')
366
- lines.append('# - Confirm table columns match requirement')
367
- lines.append('# - Add interaction details (sidebar collapse, modal, tab switch)')
368
- lines.append('# - Do NOT change tokens/colors/layout parameters')
369
- lines.append('```')
370
- lines.append('')
371
-
372
- return '\n'.join(lines)
373
-
374
-
375
- def main():
376
- stdout_mode = '--stdout' in sys.argv
377
- content = gen_design_doc()
378
-
379
- if stdout_mode:
380
- print(content)
381
- return 0
382
-
383
- # 写到 data/design/DESIGN.md
384
- os.makedirs(OUTPUT_DIR, exist_ok=True)
385
- output_path = os.path.join(OUTPUT_DIR, 'DESIGN.md')
386
- with open(output_path, 'w', encoding='utf-8') as f:
387
- f.write(content)
388
- print(f'DESIGN.md generated: {output_path}')
389
- print(f' Size: {len(content)} chars, {content.count(chr(10))} lines')
390
- return 0
391
-
392
-
393
- if __name__ == '__main__':
394
- sys.exit(main())
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
4
+ import os as _o, sys as _s
5
+ _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
6
+ if _cp not in _s.path: _s.path.insert(0, _cp)
7
+ from bootstrap import setup; setup()
8
+
9
+ """
10
+ gen_design_doc.py - 自动生成 DESIGN.md (给 AI 的设计系统说明书)
11
+
12
+ 从 ui-style.json + ref-vben-style.json + ref-icon.json 等索引数据
13
+ 组装成一个单一文件,AI 出原型前必须先读这个文件。
14
+
15
+ 解决了业界最佳实践的核心思路:
16
+ - v0.dev: AI Registry 用正确 token 生成
17
+ - design.md (GitHub): 从 URL 提取 → DESIGN.md → AI 读
18
+ - 我们: 从真实代码索引 → DESIGN.md → AI 读 → 不再猜风格
19
+
20
+ Usage:
21
+ python gen_design_doc.py # 生成到 data/design/DESIGN.md
22
+ python gen_design_doc.py --stdout # 输出到 stdout (给 AI 直接读)
23
+ """
24
+
25
+ import os
26
+ import sys
27
+ import json
28
+ import re
29
+ import time
30
+
31
+ # UTF-8 stdio
32
+ try:
33
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
34
+ except (AttributeError, TypeError, OSError, IOError):
35
+ try:
36
+ sys.stdout.reconfigure(encoding='utf-8')
37
+ except Exception:
38
+ pass
39
+
40
+ BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
41
+ INDEX_DIR = os.path.join(BASE, 'data', 'index')
42
+ OUTPUT_DIR = os.path.join(BASE, 'data', 'design')
43
+
44
+
45
+ def load_json(filename):
46
+ path = os.path.join(INDEX_DIR, filename)
47
+ if not os.path.isfile(path):
48
+ return {}
49
+ try:
50
+ with open(path, encoding='utf-8') as f:
51
+ return json.load(f)
52
+ except Exception:
53
+ return {}
54
+
55
+
56
+ def gen_design_doc():
57
+ """生成 DESIGN.md 内容。返回字符串。"""
58
+ # 加载数据源
59
+ ui_style = load_json('ui-style.json')
60
+ vben = load_json('ref-vben-style.json')
61
+ icon = load_json('ref-icon.json')
62
+ chart_style = load_json('ref-chart-style.json')
63
+
64
+ lines = []
65
+ lines.append('# DESIGN.md - AI Prototype Design System Reference')
66
+ lines.append('')
67
+ lines.append('> **This file is the SINGLE SOURCE OF TRUTH for AI when generating prototypes.')
68
+ lines.append('> AI MUST read this file before generating ANY prototype. Do NOT invent styles.**')
69
+ lines.append('')
70
+ lines.append('> Auto-generated by gen_design_doc.py from real code indexes.')
71
+ lines.append(f'> Generated: {time.strftime("%Y-%m-%d %H:%M")}')
72
+ lines.append('')
73
+ lines.append('---')
74
+ lines.append('')
75
+
76
+ # ========== 1. Layout Fingerprint ==========
77
+ lines.append('## 1. Layout Fingerprint (from real pages)')
78
+ lines.append('')
79
+ for proj_name, pdata in (ui_style.get('projects') or {}).items():
80
+ fp = pdata.get('layout_fingerprint')
81
+ if not fp:
82
+ continue
83
+ lines.append(f'### {proj_name}')
84
+ lines.append('')
85
+ sidebar_w = fp.get('sidebar_width') or '?'
86
+ radius_v = fp.get('radius') or '?'
87
+ lines.append(f'- **Layout mode**: {fp.get("layout", "unknown")} (determines sidebar/header structure)')
88
+ lines.append(f'- **Stack**: {fp.get("stack", "unknown")}')
89
+ lines.append(f'- **Theme**: {fp.get("theme_mode", "light")}')
90
+ lines.append(f'- **Sidebar**: width={sidebar_w}px, semi_dark={fp.get("semi_dark_sidebar", False)}')
91
+ lines.append(f'- **Topbar**: dark header with logo + tools')
92
+ lines.append(f'- **Access mode**: {fp.get("access_mode", "unknown")} (menus come from backend API)')
93
+ lines.append(f'- **Border radius**: {radius_v}rem')
94
+
95
+ if fp.get('layout') == 'mixed-nav':
96
+ lines.append('')
97
+ lines.append('**mixed-nav layout means:**')
98
+ lines.append('- Top bar: logo + notification + avatar (dark background)')
99
+ lines.append('- Left sidebar: module-level menus (with icons)')
100
+ lines.append('- Content area: breadcrumb + page header + content')
101
+ lines.append('- Menu depth: 3-4 levels supported (ParentView nesting)')
102
+ lines.append('')
103
+
104
+ # ========== 2. Design Tokens ==========
105
+ lines.append('## 2. Design Tokens (CSS Variables)')
106
+ lines.append('')
107
+ lines.append('**MUST use these exact values. NEVER hardcode #1890ff (Ant Design default).**')
108
+ lines.append('')
109
+
110
+ # From vben reference
111
+ dt = vben.get('design_tokens', {})
112
+ if dt:
113
+ lines.append('### Color Tokens (from vben-style-reference)')
114
+ lines.append('')
115
+ color_tokens = {k: v for k, v in dt.items()
116
+ if 'color' in k.lower() or 'background' in k.lower()
117
+ or 'foreground' in k.lower() or 'primary' in k.lower()
118
+ or 'destructive' in k.lower() or 'success' in k.lower()
119
+ or 'warning' in k.lower() or 'border' in k.lower()
120
+ or 'input' in k.lower() or 'muted' in k.lower()
121
+ or 'card' in k.lower() or 'popover' in k.lower()
122
+ or 'radius' in k.lower() or 'font' in k.lower()}
123
+ for k, v in color_tokens.items():
124
+ lines.append(f' {k}: {v}')
125
+ lines.append('')
126
+
127
+ # From layout fingerprint (authoritative)
128
+ fp_primary = None
129
+ fp_destructive = None
130
+ fp_success = None
131
+ fp_warning = None
132
+ # 优先取有 color_primary 的指纹 (fywl-ui 有完整颜色, Carmg-H5 可能没有)
133
+ for proj_name, pdata in (ui_style.get('projects') or {}).items():
134
+ fp = pdata.get('layout_fingerprint', {})
135
+ if fp and fp.get('color_primary'):
136
+ fp_primary = fp.get('color_primary')
137
+ fp_destructive = fp.get('color_destructive')
138
+ fp_success = fp.get('color_success')
139
+ fp_warning = fp.get('color_warning')
140
+ break
141
+
142
+ lines.append('### Authoritative Colors (from layout_fingerprint - HIGHEST priority)')
143
+ lines.append('')
144
+ lines.append('```css')
145
+ lines.append(':root {')
146
+ if fp_primary:
147
+ lines.append(f' --primary: {fp_primary}')
148
+ if fp_destructive:
149
+ lines.append(f' --destructive: {fp_destructive}')
150
+ if fp_success:
151
+ lines.append(f' --success: {fp_success}')
152
+ if fp_warning:
153
+ lines.append(f' --warning: {fp_warning}')
154
+ lines.append(' --background-deep: hsl(216, 20.11%, 95.47%)')
155
+ lines.append(' --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif')
156
+ lines.append(' --radius: 0.25rem')
157
+ lines.append('}')
158
+ lines.append('```')
159
+ lines.append('')
160
+
161
+ lines.append('### FORBIDDEN Colors (NEVER use)')
162
+ lines.append('')
163
+ lines.append('- `#1890ff` - Ant Design default, NOT this project\'s primary')
164
+ lines.append('- Any color not listed above without citing a source file')
165
+ lines.append('')
166
+
167
+ # ========== 3. Page Types ==========
168
+ lines.append('## 3. Page Types (how this system structures pages)')
169
+ lines.append('')
170
+ for proj_name, pdata in (ui_style.get('projects') or {}).items():
171
+ pt = pdata.get('page_types', {})
172
+ if not pt:
173
+ continue
174
+ lines.append(f'### {proj_name} page type distribution')
175
+ lines.append('')
176
+ for page_type, count in sorted(pt.items(), key=lambda x: -x[1]):
177
+ lines.append(f'- **{page_type}**: {count} pages')
178
+ lines.append('')
179
+
180
+ lines.append('### Common page patterns (extracted from real pages)')
181
+ lines.append('')
182
+ # page_examples 动态提取每种页面类型的真实布局模式
183
+ _page_type_labels = {
184
+ 'table-page': 'Table page (search + grid + pagination)',
185
+ 'form-page': 'Form page (structured input, NOT a fixed template)',
186
+ 'detail-page': 'Detail page (read-only data display)',
187
+ 'modal': 'Modal/Drawer (popup form or detail)',
188
+ 'dashboard': 'Dashboard (summary cards + charts)',
189
+ }
190
+ # 统计每种类型的真实布局模式
191
+ for proj_name, pdata in (ui_style.get('projects') or {}).items():
192
+ pe = pdata.get('page_examples') or {}
193
+ for pt_key in ['table-page', 'form-page', 'detail-page', 'modal', 'dashboard']:
194
+ examples = pe.get(pt_key, [])
195
+ real_examples = [e for e in examples if isinstance(e, dict) and (e.get('layout') or e.get('form_fields') or e.get('table_columns') or e.get('behaviors') or e.get('form_components'))]
196
+ if not real_examples:
197
+ continue
198
+ label = _page_type_labels.get(pt_key, pt_key)
199
+ lines.append(f'- **{label}** ({proj_name}, {len(real_examples)} examples)')
200
+ # 提取布局模式
201
+ ex0 = real_examples[0]
202
+ layout = ex0.get('layout', {})
203
+ if layout:
204
+ if layout.get('grid_cols'):
205
+ lines.append(f' - Grid columns: {layout["grid_cols"]} (real pattern from {ex0["file"].split("/")[-1]})')
206
+ if layout.get('label_width'):
207
+ lines.append(f' - Label width: {layout["label_width"]}px')
208
+ # 表单组件
209
+ fc = ex0.get('form_components', [])
210
+ if fc:
211
+ lines.append(f' - Form components: {", ".join(fc[:6])}')
212
+ # 交互行为 (侧边栏折叠/表格分页/表单模式)
213
+ behaviors = ex0.get('behaviors', {})
214
+ if behaviors:
215
+ bh_strs = []
216
+ for bk, bv in behaviors.items():
217
+ bh_strs.append(f'{bk}={bv}')
218
+ lines.append(f' - Behaviors: {", ".join(bh_strs)}')
219
+ # 表单字段数
220
+ ff_count = len(ex0.get('form_fields', []))
221
+ if ff_count:
222
+ lines.append(f' - Field count: {ff_count} fields (typical)')
223
+ sample_fields = [f.get('label', f.get('field', '')) for f in ex0.get('form_fields', [])[:5]]
224
+ lines.append(f' - Sample fields: {", ".join(sample_fields)}')
225
+ # 表格列数
226
+ tc_count = len(ex0.get('table_columns', []))
227
+ if tc_count:
228
+ lines.append(f' - Column count: {tc_count} columns (typical)')
229
+ sample_cols = [c.get('title', c.get('field', '')) for c in ex0.get('table_columns', [])[:5]]
230
+ lines.append(f' - Sample columns: {", ".join(sample_cols)}')
231
+ lines.append(f' - Reference file: {ex0.get("file", "?")}')
232
+ lines.append('')
233
+ break # 只取第一个有数据的项目 (通常是 fywl-ui)
234
+
235
+ lines.append('### Form page behavior modes (NOT a fixed template - choose by requirement)')
236
+ lines.append('')
237
+ lines.append('Form pages vary by section count. DO NOT use one fixed template. Match the mode to the requirement:')
238
+ lines.append('- **Single-section form**: one form, 2-3 grid columns, label 80-140px, bottom save/reset buttons')
239
+ lines.append('- **Multi-section form**: tabs or accordion separating sections, each section 2-3 columns')
240
+ lines.append('- **Step wizard form**: horizontal step indicator + per-step form + back/next buttons')
241
+ lines.append('- **Drawer form**: right-side drawer popup, single column, compact layout')
242
+ lines.append('')
243
+ lines.append('> fill_prototype.py extracts real grid_cols/label_width from page_examples.')
244
+ lines.append('> The form layout adapts to the matched example, NOT a hardcoded shell.')
245
+ lines.append('')
246
+
247
+ # ========== 4. Top Fields ==========
248
+ lines.append('## 4. Top Used Fields (real field names -> Chinese labels)')
249
+ lines.append('')
250
+ lines.append('These are the most common fields across all pages. Use them for new pages.')
251
+ lines.append('')
252
+ fm = ui_style.get('field_map') or {}
253
+ top_fields = sorted(fm.items(), key=lambda x: len(x[1].get('titles', set()) if isinstance(x[1].get('titles'), set) else x[1].get('titles', [])), reverse=True)[:30]
254
+ for field_name, info in top_fields:
255
+ titles = info.get('titles', set())
256
+ if isinstance(titles, set):
257
+ title = next(iter(titles), field_name)
258
+ elif isinstance(titles, list) and titles:
259
+ title = titles[0]
260
+ else:
261
+ title = field_name
262
+ count = info.get('count', 0)
263
+ lines.append(f'- `{field_name}` -> {title} (used {count}x)')
264
+ lines.append('')
265
+
266
+ # ========== 4.5 Feature Modules (from prd-features.json) ==========
267
+ pf = load_json('prd-features.json')
268
+ features = pf.get('features', {}) if isinstance(pf, dict) else {}
269
+ if features:
270
+ lines.append('## 4.5 Feature Modules (系统有哪些功能模块)')
271
+ lines.append('')
272
+ lines.append('出原型时, 先确定需求属于哪个模块, 参考该模块的现有页面。')
273
+ lines.append('')
274
+ for feat_key, feat_info in list(features.items())[:12]:
275
+ cn = feat_info.get('cn', feat_key)
276
+ page_count = feat_info.get('page_count', 0)
277
+ api_count = feat_info.get('api_count', 0)
278
+ pages = feat_info.get('pages', [])
279
+ sample_pages = ', '.join(pages[:2]) if pages else ''
280
+ lines.append(f'- **{cn}** ({feat_key}): {page_count} pages, {api_count} APIs')
281
+ if sample_pages:
282
+ lines.append(f' - 标杆页面: {sample_pages}')
283
+ lines.append('')
284
+
285
+ # ========== 4.6 Real Buttons (from entity-registry.json) ==========
286
+ er = load_json('entity-registry.json')
287
+ entities = er.get('entities', {}) if isinstance(er, dict) else {}
288
+ button_samples = []
289
+ for eid, info in list(entities.items())[:500]:
290
+ if not eid.startswith('B:'):
291
+ continue
292
+ props = info.get('properties', {}) if isinstance(info, dict) else {}
293
+ btn_text = props.get('button_text', '')
294
+ if btn_text and btn_text not in [b['text'] for b in button_samples]:
295
+ button_samples.append({'text': btn_text, 'endpoint': props.get('endpoint', '')})
296
+ if len(button_samples) >= 15:
297
+ break
298
+ if button_samples:
299
+ lines.append('## 4.6 Real Button Texts (系统里真实的按钮文案)')
300
+ lines.append('')
301
+ lines.append('出原型时, 按钮文案用这些真实的, 不要编"新增/编辑"。')
302
+ lines.append('')
303
+ for b in button_samples:
304
+ ep = ' -> ' + b['endpoint'] if b['endpoint'] else ''
305
+ lines.append(f'- {b["text"]}{ep}')
306
+ lines.append('')
307
+
308
+ # ========== 5. Components ==========
309
+ lines.append('## 5. UI Components (from real code)')
310
+ lines.append('')
311
+ cc = ui_style.get('common_components') or {}
312
+ lines.append('### Most used Ant Design Vue components')
313
+ lines.append('')
314
+ for comp, count in list(cc.items())[:15]:
315
+ lines.append(f'- {comp}: {count}')
316
+ lines.append('')
317
+
318
+ fc = ui_style.get('form_components') or {}
319
+ lines.append('### Form components')
320
+ lines.append('')
321
+ for comp, count in list(fc.items())[:10]:
322
+ lines.append(f'- {comp}: {count}')
323
+ lines.append('')
324
+
325
+ # ========== 6. Icons ==========
326
+ lines.append('## 6. Icons')
327
+ lines.append('')
328
+ lines.append('**MUST use real icon system. NEVER use emoji.**')
329
+ lines.append('')
330
+ lines.append('- **Web (fywl-ui)**: Ant Design inline SVG icons')
331
+ lines.append(' - Source: `data/index/ref-icon.json`')
332
+ lines.append(' - Usage: `<svg viewBox="0 0 1024 1024" fill="currentColor">...</svg>`')
333
+ lines.append(' - Size: 14-16px for menu items, 18-20px for actions')
334
+ lines.append('- **APP (Carmg-H5)**: Vant font icons')
335
+ lines.append(' - CDN: https://cdn.jsdelivr.net/npm/vant@4/lib/index.css')
336
+ lines.append(' - Usage: `<i class="van-icon van-icon-xxx"></i>`')
337
+ lines.append('')
338
+
339
+ # ========== 7. Dashboard / Chart styles ==========
340
+ chart_data = chart_style
341
+ if chart_data:
342
+ lines.append('## 7. Dashboard / Chart Styles')
343
+ lines.append('')
344
+ lines.append('Two in-system chart idioms:')
345
+ lines.append('- **Dark screen** (monitor): background `#011845`, accent `#53CFF0`/`#FFE646`')
346
+ lines.append('- **Light card** (workbench): white cards, blue accent `#2E61E0`')
347
+ lines.append('')
348
+ lines.append('Source: `data/index/ref-chart-style.json`')
349
+ lines.append('')
350
+
351
+ # ========== 8. Prototype Rules ==========
352
+ lines.append('## 8. Prototype Rules (AI MUST follow)')
353
+ lines.append('')
354
+ lines.append('1. **Token priority**: layout_fingerprint > vben-style-reference > code > PDF')
355
+ lines.append('2. **Only change the diff**: everything else identical to existing page')
356
+ lines.append('3. **Single file**: one HTML with all CSS and JS inline')
357
+ lines.append('4. **Interactive**: buttons click, forms fill, tables scroll')
358
+ lines.append('5. **Icons**: real SVG/Webfont only, NEVER emoji')
359
+ lines.append('6. **Colors**: from this file only, NEVER invent')
360
+ lines.append('7. **Layout**: sidebar_width/topbar_height from layout_fingerprint')
361
+ lines.append('8. **Source citation**: header comment MUST cite which files provided the style')
362
+ lines.append('')
363
+
364
+ # ========== 9. How to generate ==========
365
+ lines.append('## 9. How to Generate a Prototype')
366
+ lines.append('')
367
+ lines.append('```bash')
368
+ lines.append('# Step 1: Run fill_prototype (generates 80% draft with real data)')
369
+ lines.append('python .qoder/scripts/fill_prototype.py <keyword> --platform <web|app>')
370
+ lines.append('')
371
+ lines.append('# Step 2: AI reads the draft + this DESIGN.md, does 20% adjustment')
372
+ lines.append('# - Confirm table columns match requirement')
373
+ lines.append('# - Add interaction details (sidebar collapse, modal, tab switch)')
374
+ lines.append('# - Do NOT change tokens/colors/layout parameters')
375
+ lines.append('```')
376
+ lines.append('')
377
+
378
+ return '\n'.join(lines)
379
+
380
+
381
+ def main():
382
+ stdout_mode = '--stdout' in sys.argv
383
+ content = gen_design_doc()
384
+
385
+ if stdout_mode:
386
+ print(content)
387
+ return 0
388
+
389
+ # 写到 data/design/DESIGN.md
390
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
391
+ output_path = os.path.join(OUTPUT_DIR, 'DESIGN.md')
392
+ with open(output_path, 'w', encoding='utf-8') as f:
393
+ f.write(content)
394
+ print(f'DESIGN.md generated: {output_path}')
395
+ print(f' Size: {len(content)} chars, {content.count(chr(10))} lines')
396
+ return 0
397
+
398
+
399
+ if __name__ == '__main__':
400
+ sys.exit(main())