@hupan56/wlkj 2.5.0 → 2.7.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 (127) hide show
  1. package/bin/cli.js +289 -12
  2. package/package.json +1 -1
  3. package/templates/qoder/agents/insight-planning.md +67 -0
  4. package/templates/qoder/agents/insight-research.md +61 -0
  5. package/templates/qoder/agents/prd-quick.md +1 -0
  6. package/templates/qoder/agents/prd-reference.md +10 -2
  7. package/templates/qoder/commands/optional/wl-insight.md +275 -0
  8. package/templates/qoder/commands/{wl-report.md → optional/wl-report.md} +13 -5
  9. package/templates/qoder/commands/{wl-spec.md → optional/wl-spec.md} +1 -1
  10. package/templates/qoder/commands/{wl-status.md → optional/wl-status.md} +28 -2
  11. package/templates/qoder/commands/wl-code.md +10 -2
  12. package/templates/qoder/commands/wl-commit.md +1 -1
  13. package/templates/qoder/commands/wl-design-draw.md +78 -0
  14. package/templates/qoder/commands/wl-design-scan.md +108 -0
  15. package/templates/qoder/commands/wl-design-spec.md +154 -0
  16. package/templates/qoder/commands/wl-design.md +32 -0
  17. package/templates/qoder/commands/wl-init.md +24 -3
  18. package/templates/qoder/commands/wl-prd-full.md +226 -0
  19. package/templates/qoder/commands/wl-prd-quick.md +134 -0
  20. package/templates/qoder/commands/wl-prd-review.md +104 -0
  21. package/templates/qoder/commands/wl-prd.md +17 -288
  22. package/templates/qoder/commands/wl-search.md +66 -30
  23. package/templates/qoder/commands/wl-task.md +290 -59
  24. package/templates/qoder/commands/wl-test.md +92 -24
  25. package/templates/qoder/config.yaml +59 -15
  26. package/templates/qoder/hooks/inject-workflow-state.py +35 -9
  27. package/templates/qoder/hooks/session-start.py +144 -62
  28. package/templates/qoder/rules/wl-pipeline.md +216 -105
  29. package/templates/qoder/scripts/__pycache__/search_index.cpython-39.pyc +0 -0
  30. package/templates/qoder/scripts/archive_prd.py +377 -0
  31. package/templates/qoder/scripts/autotest.py +1715 -0
  32. package/templates/qoder/scripts/autotest_batch.py +224 -0
  33. package/templates/qoder/scripts/autotest_run.py +297 -0
  34. package/templates/qoder/scripts/benchmark.py +210 -209
  35. package/templates/qoder/scripts/build_style_index.py +444 -4
  36. package/templates/qoder/scripts/check_carriers.py +238 -0
  37. package/templates/qoder/scripts/check_mcp.py +298 -0
  38. package/templates/qoder/scripts/check_qoderwork_consistency.py +166 -0
  39. package/templates/qoder/scripts/common/developer.py +26 -19
  40. package/templates/qoder/scripts/common/events.py +46 -0
  41. package/templates/qoder/scripts/common/extract.py +419 -0
  42. package/templates/qoder/scripts/common/graph_traverse.py +533 -0
  43. package/templates/qoder/scripts/common/identity.py +6 -1
  44. package/templates/qoder/scripts/common/paths.py +89 -0
  45. package/templates/qoder/scripts/common/pip_install.py +144 -0
  46. package/templates/qoder/scripts/common/platform_guard.py +61 -0
  47. package/templates/qoder/scripts/common/search_engine.py +205 -205
  48. package/templates/qoder/scripts/common/terms.py +57 -0
  49. package/templates/qoder/scripts/common/ts_extract.py +536 -0
  50. package/templates/qoder/scripts/context_pack.py +73 -13
  51. package/templates/qoder/scripts/enrich_prompt.py +226 -0
  52. package/templates/qoder/scripts/eval_prd.py +318 -225
  53. package/templates/qoder/scripts/export.py +487 -487
  54. package/templates/qoder/scripts/extract_api_params.py +246 -0
  55. package/templates/qoder/scripts/extract_routes.py +54 -0
  56. package/templates/qoder/scripts/extract_routes_tree.py +78 -0
  57. package/templates/qoder/scripts/fill_prototype.py +707 -0
  58. package/templates/qoder/scripts/gen_design_doc.py +394 -0
  59. package/templates/qoder/scripts/git_sync.py +27 -15
  60. package/templates/qoder/scripts/init_doctor.py +306 -41
  61. package/templates/qoder/scripts/install_qoderwork.py +366 -9
  62. package/templates/qoder/scripts/kg.py +708 -0
  63. package/templates/qoder/scripts/kg_auto_login.py +196 -0
  64. package/templates/qoder/scripts/kg_build.py +612 -0
  65. package/templates/qoder/scripts/kg_build_db.py +327 -0
  66. package/templates/qoder/scripts/kg_duckdb.py +549 -0
  67. package/templates/qoder/scripts/kg_incremental.py +393 -0
  68. package/templates/qoder/scripts/kg_link_db.py +224 -0
  69. package/templates/qoder/scripts/kg_mcp_server.py +801 -0
  70. package/templates/qoder/scripts/kg_semantic.py +150 -0
  71. package/templates/qoder/scripts/kg_test_runner.py +241 -0
  72. package/templates/qoder/scripts/lanhu_stdio_wrapper.py +119 -0
  73. package/templates/qoder/scripts/learn.py +118 -39
  74. package/templates/qoder/scripts/learn_aggregate.py +201 -0
  75. package/templates/qoder/scripts/mcp_launcher.py +359 -0
  76. package/templates/qoder/scripts/mysql_mcp_server.py +396 -0
  77. package/templates/qoder/scripts/repo_root.py +106 -0
  78. package/templates/qoder/scripts/role.py +12 -0
  79. package/templates/qoder/scripts/run_weekly_update.bat +5 -0
  80. package/templates/qoder/scripts/run_weekly_update.sh +5 -0
  81. package/templates/qoder/scripts/search_index.py +307 -60
  82. package/templates/qoder/scripts/secure-ls.js +5640 -0
  83. package/templates/qoder/scripts/setup.py +706 -453
  84. package/templates/qoder/scripts/setup_lanhu.py +963 -0
  85. package/templates/qoder/scripts/status.py +250 -11
  86. package/templates/qoder/scripts/sync_carriers.py +259 -0
  87. package/templates/qoder/scripts/syncgate.py +5 -4
  88. package/templates/qoder/scripts/task.py +75 -0
  89. package/templates/qoder/scripts/team_sync.py +60 -4
  90. package/templates/qoder/scripts/workspace_init.py +1 -1
  91. package/templates/qoder/skills/design-import/SKILL.md +226 -0
  92. package/templates/qoder/skills/design-import/figma-workflow.md +81 -0
  93. package/templates/qoder/skills/design-review/SKILL.md +82 -25
  94. package/templates/qoder/skills/prd-generator/SKILL.md +185 -58
  95. package/templates/qoder/skills/prd-review/SKILL.md +18 -1
  96. package/templates/qoder/skills/prompt-enrich/SKILL.md +90 -0
  97. package/templates/qoder/skills/prototype-generator/SKILL.md +256 -141
  98. package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
  99. package/templates/qoder/skills/spec-coder/SKILL.md +18 -1
  100. package/templates/qoder/skills/spec-generator/SKILL.md +18 -1
  101. package/templates/qoder/skills/test-generator/SKILL.md +15 -2
  102. package/templates/qoder/skills/wl-code/SKILL.md +55 -36
  103. package/templates/qoder/skills/wl-commit/SKILL.md +89 -76
  104. package/templates/qoder/skills/wl-design/SKILL.md +55 -0
  105. package/templates/qoder/skills/wl-init/SKILL.md +76 -67
  106. package/templates/qoder/skills/wl-insight/SKILL.md +201 -81
  107. package/templates/qoder/skills/wl-prd-full/SKILL.md +69 -0
  108. package/templates/qoder/skills/wl-prd-quick/SKILL.md +49 -0
  109. package/templates/qoder/skills/wl-prd-review/SKILL.md +34 -0
  110. package/templates/qoder/skills/wl-report/SKILL.md +131 -107
  111. package/templates/qoder/skills/wl-search/SKILL.md +141 -75
  112. package/templates/qoder/skills/wl-spec/SKILL.md +49 -39
  113. package/templates/qoder/skills/wl-status/SKILL.md +83 -61
  114. package/templates/qoder/skills/wl-task/SKILL.md +132 -58
  115. package/templates/qoder/skills/wl-test/SKILL.md +406 -40
  116. package/templates/qoder/templates/prd-full-template.md +2 -0
  117. package/templates/qoder/templates/prd-quick-template.md +1 -0
  118. package/templates/qoder/templates/prototype-app.html +13 -8
  119. package/templates/qoder/templates/prototype-web.html +376 -93
  120. package/templates/root/AGENTS.md +89 -34
  121. package/templates/root/requirements.txt +21 -0
  122. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +259 -259
  123. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -186
  124. package/templates/qoder/agents/prd-planning.md +0 -56
  125. package/templates/qoder/agents/prd-research.md +0 -33
  126. package/templates/qoder/commands/wl-insight.md +0 -51
  127. package/templates/qoder/skills/wl-prd/SKILL.md +0 -89
@@ -0,0 +1,394 @@
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())
@@ -21,6 +21,7 @@ Config: .qoder/config.yaml -> git_sync section
21
21
  """
22
22
 
23
23
  import os
24
+ from common.paths import BUILD_CACHE_DIR
24
25
  import sys
25
26
  import json
26
27
  import subprocess
@@ -306,7 +307,7 @@ def build_prd_index():
306
307
  return {}
307
308
 
308
309
  # Load keyword index for code matching
309
- ki_path = os.path.join(INDEX_DIR, 'keyword-index.json')
310
+ ki_path = os.path.join(BUILD_CACHE_DIR, 'code-keyword.json')
310
311
  keyword_index = load_json(ki_path)
311
312
 
312
313
  prd_index = {}
@@ -341,7 +342,7 @@ def build_prd_index():
341
342
  print(f' {f}: Error - {str(e)[:80]}')
342
343
 
343
344
  # Save index
344
- save_json(os.path.join(INDEX_DIR, 'prd-index.json'), prd_index)
345
+ save_json(os.path.join(BUILD_CACHE_DIR, 'prd-index.json'), prd_index)
345
346
 
346
347
  print(f'\nPRD Index: {len(prd_index)} PRDs indexed')
347
348
  return prd_index
@@ -442,9 +443,9 @@ def update_indexes_incremental(changed_projects):
442
443
 
443
444
  os.makedirs(INDEX_DIR, exist_ok=True)
444
445
 
445
- module_map = load_json(os.path.join(INDEX_DIR, 'module-map.json'))
446
- keyword_index = load_json(os.path.join(INDEX_DIR, 'keyword-index.json'))
447
- api_index = load_json(os.path.join(INDEX_DIR, 'api-index.json'))
446
+ module_map = load_json(os.path.join(BUILD_CACHE_DIR, 'code-stats.json'))
447
+ keyword_index = load_json(os.path.join(BUILD_CACHE_DIR, 'code-keyword.json'))
448
+ api_index = load_json(os.path.join(BUILD_CACHE_DIR, 'code-api.json'))
448
449
 
449
450
  total_files = 0
450
451
 
@@ -505,9 +506,9 @@ def update_indexes_incremental(changed_projects):
505
506
  module_map[proj]['files'] = list(set(module_map[proj]['files']))
506
507
 
507
508
  # Save
508
- save_json(os.path.join(INDEX_DIR, 'module-map.json'), module_map)
509
- save_json(os.path.join(INDEX_DIR, 'keyword-index.json'), keyword_index)
510
- save_json(os.path.join(INDEX_DIR, 'api-index.json'), api_index)
509
+ save_json(os.path.join(BUILD_CACHE_DIR, 'code-stats.json'), module_map)
510
+ save_json(os.path.join(BUILD_CACHE_DIR, 'code-keyword.json'), keyword_index)
511
+ save_json(os.path.join(BUILD_CACHE_DIR, 'code-api.json'), api_index)
511
512
 
512
513
  print(f'\n Updated: {total_files} files indexed')
513
514
  print(f' Keywords: {len(keyword_index)}')
@@ -579,9 +580,9 @@ def build_full_indexes():
579
580
 
580
581
  print(f' Files: {len(module_map[project_name]["files"])}')
581
582
 
582
- save_json(os.path.join(INDEX_DIR, 'module-map.json'), module_map)
583
- save_json(os.path.join(INDEX_DIR, 'keyword-index.json'), keyword_index)
584
- save_json(os.path.join(INDEX_DIR, 'api-index.json'), api_index)
583
+ save_json(os.path.join(BUILD_CACHE_DIR, 'code-stats.json'), module_map)
584
+ save_json(os.path.join(BUILD_CACHE_DIR, 'code-keyword.json'), keyword_index)
585
+ save_json(os.path.join(BUILD_CACHE_DIR, 'code-api.json'), api_index)
585
586
 
586
587
  print(f'\n Total: {total_files} files, {len(keyword_index)} keywords, {len(api_index)} APIs')
587
588
  return total_files
@@ -630,9 +631,9 @@ def build_prd_code_mapping():
630
631
  """Build bidirectional PRD to Code mapping with business term awareness"""
631
632
  print('\n=== Building PRD to Code Mapping ===\n')
632
633
 
633
- prd_index = load_json(os.path.join(INDEX_DIR, 'prd-index.json'))
634
- keyword_index = load_json(os.path.join(INDEX_DIR, 'keyword-index.json'))
635
- module_map = load_json(os.path.join(INDEX_DIR, 'module-map.json'))
634
+ prd_index = load_json(os.path.join(BUILD_CACHE_DIR, 'prd-index.json'))
635
+ keyword_index = load_json(os.path.join(BUILD_CACHE_DIR, 'code-keyword.json'))
636
+ module_map = load_json(os.path.join(BUILD_CACHE_DIR, 'code-stats.json'))
636
637
 
637
638
  mapping = {'prd_to_code': {}, 'code_to_prd': {}}
638
639
 
@@ -691,7 +692,7 @@ def build_prd_code_mapping():
691
692
  if prd_file not in mapping['code_to_prd'][f]:
692
693
  mapping['code_to_prd'][f].append(prd_file)
693
694
 
694
- save_json(os.path.join(INDEX_DIR, 'prd-code-map.json'), mapping)
695
+ save_json(os.path.join(BUILD_CACHE_DIR, 'prd-code-map.json'), mapping)
695
696
 
696
697
  print(f' PRD -> Code: {len(mapping["prd_to_code"])} PRDs mapped')
697
698
  print(f' Code -> PRD: {len(mapping["code_to_prd"])} files with PRD links')
@@ -769,6 +770,17 @@ def main():
769
770
  if _n:
770
771
  print(' cache cleanup: %d stale files removed' % _n)
771
772
 
773
+ # Step 7: Generate DESIGN.md (AI prototype design system reference)
774
+ # 索引变了 → DESIGN.md 必须重新生成, 保证 AI 读到的风格规范是最新的
775
+ try:
776
+ import subprocess as _sp
777
+ _dd = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gen_design_doc.py')
778
+ if os.path.isfile(_dd):
779
+ _sp.run([sys.executable, _dd], cwd=str(BASE), capture_output=True, timeout=30)
780
+ print(' DESIGN.md regenerated (AI prototype reference)')
781
+ except Exception:
782
+ pass # 生成失败不阻塞索引更新
783
+
772
784
  print('\n' + '=' * 50)
773
785
  print('Update complete!')
774
786
  print('=' * 50)