@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.
- package/bin/cli.js +289 -12
- package/package.json +1 -1
- package/templates/qoder/agents/insight-planning.md +67 -0
- package/templates/qoder/agents/insight-research.md +61 -0
- package/templates/qoder/agents/prd-quick.md +1 -0
- package/templates/qoder/agents/prd-reference.md +10 -2
- package/templates/qoder/commands/optional/wl-insight.md +275 -0
- package/templates/qoder/commands/{wl-report.md → optional/wl-report.md} +13 -5
- package/templates/qoder/commands/{wl-spec.md → optional/wl-spec.md} +1 -1
- package/templates/qoder/commands/{wl-status.md → optional/wl-status.md} +28 -2
- package/templates/qoder/commands/wl-code.md +10 -2
- package/templates/qoder/commands/wl-commit.md +1 -1
- package/templates/qoder/commands/wl-design-draw.md +78 -0
- package/templates/qoder/commands/wl-design-scan.md +108 -0
- package/templates/qoder/commands/wl-design-spec.md +154 -0
- package/templates/qoder/commands/wl-design.md +32 -0
- package/templates/qoder/commands/wl-init.md +24 -3
- package/templates/qoder/commands/wl-prd-full.md +226 -0
- package/templates/qoder/commands/wl-prd-quick.md +134 -0
- package/templates/qoder/commands/wl-prd-review.md +104 -0
- package/templates/qoder/commands/wl-prd.md +17 -288
- package/templates/qoder/commands/wl-search.md +66 -30
- package/templates/qoder/commands/wl-task.md +290 -59
- package/templates/qoder/commands/wl-test.md +92 -24
- package/templates/qoder/config.yaml +59 -15
- package/templates/qoder/hooks/inject-workflow-state.py +35 -9
- package/templates/qoder/hooks/session-start.py +144 -62
- package/templates/qoder/rules/wl-pipeline.md +216 -105
- package/templates/qoder/scripts/__pycache__/search_index.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/archive_prd.py +377 -0
- package/templates/qoder/scripts/autotest.py +1715 -0
- package/templates/qoder/scripts/autotest_batch.py +224 -0
- package/templates/qoder/scripts/autotest_run.py +297 -0
- package/templates/qoder/scripts/benchmark.py +210 -209
- package/templates/qoder/scripts/build_style_index.py +444 -4
- package/templates/qoder/scripts/check_carriers.py +238 -0
- package/templates/qoder/scripts/check_mcp.py +298 -0
- package/templates/qoder/scripts/check_qoderwork_consistency.py +166 -0
- package/templates/qoder/scripts/common/developer.py +26 -19
- package/templates/qoder/scripts/common/events.py +46 -0
- package/templates/qoder/scripts/common/extract.py +419 -0
- package/templates/qoder/scripts/common/graph_traverse.py +533 -0
- package/templates/qoder/scripts/common/identity.py +6 -1
- package/templates/qoder/scripts/common/paths.py +89 -0
- package/templates/qoder/scripts/common/pip_install.py +144 -0
- package/templates/qoder/scripts/common/platform_guard.py +61 -0
- package/templates/qoder/scripts/common/search_engine.py +205 -205
- package/templates/qoder/scripts/common/terms.py +57 -0
- package/templates/qoder/scripts/common/ts_extract.py +536 -0
- package/templates/qoder/scripts/context_pack.py +73 -13
- package/templates/qoder/scripts/enrich_prompt.py +226 -0
- package/templates/qoder/scripts/eval_prd.py +318 -225
- package/templates/qoder/scripts/export.py +487 -487
- package/templates/qoder/scripts/extract_api_params.py +246 -0
- package/templates/qoder/scripts/extract_routes.py +54 -0
- package/templates/qoder/scripts/extract_routes_tree.py +78 -0
- package/templates/qoder/scripts/fill_prototype.py +707 -0
- package/templates/qoder/scripts/gen_design_doc.py +394 -0
- package/templates/qoder/scripts/git_sync.py +27 -15
- package/templates/qoder/scripts/init_doctor.py +306 -41
- package/templates/qoder/scripts/install_qoderwork.py +366 -9
- package/templates/qoder/scripts/kg.py +708 -0
- package/templates/qoder/scripts/kg_auto_login.py +196 -0
- package/templates/qoder/scripts/kg_build.py +612 -0
- package/templates/qoder/scripts/kg_build_db.py +327 -0
- package/templates/qoder/scripts/kg_duckdb.py +549 -0
- package/templates/qoder/scripts/kg_incremental.py +393 -0
- package/templates/qoder/scripts/kg_link_db.py +224 -0
- package/templates/qoder/scripts/kg_mcp_server.py +801 -0
- package/templates/qoder/scripts/kg_semantic.py +150 -0
- package/templates/qoder/scripts/kg_test_runner.py +241 -0
- package/templates/qoder/scripts/lanhu_stdio_wrapper.py +119 -0
- package/templates/qoder/scripts/learn.py +118 -39
- package/templates/qoder/scripts/learn_aggregate.py +201 -0
- package/templates/qoder/scripts/mcp_launcher.py +359 -0
- package/templates/qoder/scripts/mysql_mcp_server.py +396 -0
- package/templates/qoder/scripts/repo_root.py +106 -0
- package/templates/qoder/scripts/role.py +12 -0
- package/templates/qoder/scripts/run_weekly_update.bat +5 -0
- package/templates/qoder/scripts/run_weekly_update.sh +5 -0
- package/templates/qoder/scripts/search_index.py +307 -60
- package/templates/qoder/scripts/secure-ls.js +5640 -0
- package/templates/qoder/scripts/setup.py +706 -453
- package/templates/qoder/scripts/setup_lanhu.py +963 -0
- package/templates/qoder/scripts/status.py +250 -11
- package/templates/qoder/scripts/sync_carriers.py +259 -0
- package/templates/qoder/scripts/syncgate.py +5 -4
- package/templates/qoder/scripts/task.py +75 -0
- package/templates/qoder/scripts/team_sync.py +60 -4
- package/templates/qoder/scripts/workspace_init.py +1 -1
- package/templates/qoder/skills/design-import/SKILL.md +226 -0
- package/templates/qoder/skills/design-import/figma-workflow.md +81 -0
- package/templates/qoder/skills/design-review/SKILL.md +82 -25
- package/templates/qoder/skills/prd-generator/SKILL.md +185 -58
- package/templates/qoder/skills/prd-review/SKILL.md +18 -1
- package/templates/qoder/skills/prompt-enrich/SKILL.md +90 -0
- package/templates/qoder/skills/prototype-generator/SKILL.md +256 -141
- package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
- package/templates/qoder/skills/spec-coder/SKILL.md +18 -1
- package/templates/qoder/skills/spec-generator/SKILL.md +18 -1
- package/templates/qoder/skills/test-generator/SKILL.md +15 -2
- package/templates/qoder/skills/wl-code/SKILL.md +55 -36
- package/templates/qoder/skills/wl-commit/SKILL.md +89 -76
- package/templates/qoder/skills/wl-design/SKILL.md +55 -0
- package/templates/qoder/skills/wl-init/SKILL.md +76 -67
- package/templates/qoder/skills/wl-insight/SKILL.md +201 -81
- package/templates/qoder/skills/wl-prd-full/SKILL.md +69 -0
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +49 -0
- package/templates/qoder/skills/wl-prd-review/SKILL.md +34 -0
- package/templates/qoder/skills/wl-report/SKILL.md +131 -107
- package/templates/qoder/skills/wl-search/SKILL.md +141 -75
- package/templates/qoder/skills/wl-spec/SKILL.md +49 -39
- package/templates/qoder/skills/wl-status/SKILL.md +83 -61
- package/templates/qoder/skills/wl-task/SKILL.md +132 -58
- package/templates/qoder/skills/wl-test/SKILL.md +406 -40
- package/templates/qoder/templates/prd-full-template.md +2 -0
- package/templates/qoder/templates/prd-quick-template.md +1 -0
- package/templates/qoder/templates/prototype-app.html +13 -8
- package/templates/qoder/templates/prototype-web.html +376 -93
- package/templates/root/AGENTS.md +89 -34
- package/templates/root/requirements.txt +21 -0
- package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +259 -259
- package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -186
- package/templates/qoder/agents/prd-planning.md +0 -56
- package/templates/qoder/agents/prd-research.md +0 -33
- package/templates/qoder/commands/wl-insight.md +0 -51
- package/templates/qoder/skills/wl-prd/SKILL.md +0 -89
|
@@ -8,6 +8,7 @@ Usage:
|
|
|
8
8
|
python build_style_index.py
|
|
9
9
|
"""
|
|
10
10
|
import os, json, re, sys
|
|
11
|
+
from common.paths import BUILD_CACHE_DIR
|
|
11
12
|
from datetime import datetime
|
|
12
13
|
|
|
13
14
|
# UTF-8 stdio (防御性: stdout 被捕获时不崩溃)
|
|
@@ -22,8 +23,23 @@ except (AttributeError, TypeError, OSError, IOError):
|
|
|
22
23
|
BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
23
24
|
DATA_CODE = os.path.join(BASE, 'data', 'code')
|
|
24
25
|
INDEX_DIR = os.path.join(BASE, 'data', 'index')
|
|
25
|
-
STYLE_INDEX_PATH = os.path.join(
|
|
26
|
-
STYLE_META_PATH = os.path.join(
|
|
26
|
+
STYLE_INDEX_PATH = os.path.join(BUILD_CACHE_DIR, 'ui-style.json')
|
|
27
|
+
STYLE_META_PATH = os.path.join(BUILD_CACHE_DIR, 'ui-style-meta.json')
|
|
28
|
+
MODULE_INDEX_PATH = os.path.join(BUILD_CACHE_DIR, 'ui-modules.json')
|
|
29
|
+
TRACE_INDEX_PATH = os.path.join(BUILD_CACHE_DIR, 'trace-chain.json')
|
|
30
|
+
TEST_ASSERTIONS_PATH = os.path.join(BUILD_CACHE_DIR, 'test-assertions.json')
|
|
31
|
+
|
|
32
|
+
# Import the shared extraction utilities (call-chain + test code extractors)
|
|
33
|
+
# E2: prefer tree-sitter extraction (31%->70%+ coverage); fallback to regex
|
|
34
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
35
|
+
try:
|
|
36
|
+
from common.ts_extract import ts_build_api_fn_table as build_api_fn_table
|
|
37
|
+
from common.ts_extract import ts_trace_clicks as trace_clicks
|
|
38
|
+
_EXTRACT_ENGINE = 'tree-sitter'
|
|
39
|
+
except Exception:
|
|
40
|
+
from common.extract import build_api_fn_table, trace_clicks
|
|
41
|
+
_EXTRACT_ENGINE = 'regex (tree-sitter not installed)'
|
|
42
|
+
from common.test_extract import extract_test_assertions # noqa: E402
|
|
27
43
|
|
|
28
44
|
|
|
29
45
|
def extract_vue_patterns(filepath, content):
|
|
@@ -85,9 +101,268 @@ def extract_vue_patterns(filepath, content):
|
|
|
85
101
|
if grid_c:
|
|
86
102
|
patterns['layout']['grid_cols'] = [int(x) for x in grid_c]
|
|
87
103
|
|
|
104
|
+
# Extract interaction behaviors (供 fill_prototype / DESIGN.md 用, 不固化模板)
|
|
105
|
+
behaviors = {}
|
|
106
|
+
# 侧边栏折叠模式: click / hover / trigger
|
|
107
|
+
if re.search(r'useMenu|collapsed|sidebarCollapsed', content):
|
|
108
|
+
behaviors['sidebar_collapse'] = 'click' if 'collapsed' in content else 'auto'
|
|
109
|
+
# 表格分页模式: 前端 / 后端 / 滚动
|
|
110
|
+
if 'pagination:' in content:
|
|
111
|
+
behaviors['table_pagination'] = 'pagination'
|
|
112
|
+
elif 'scroll-y' in content or 'virtual' in content.lower():
|
|
113
|
+
behaviors['table_pagination'] = 'scroll'
|
|
114
|
+
elif any(m in content for m in TABLE_MARKS):
|
|
115
|
+
behaviors['table_pagination'] = 'none'
|
|
116
|
+
# 表单提交模式: 整体 / 分步 / 行内
|
|
117
|
+
if 'Steps' in content or 'a-steps' in content or 'useStep' in content:
|
|
118
|
+
behaviors['form_mode'] = 'wizard'
|
|
119
|
+
elif 'Tab' in content and 'pane' in content.lower():
|
|
120
|
+
behaviors['form_mode'] = 'tabs'
|
|
121
|
+
elif any(m in content for m in FORM_MARKS):
|
|
122
|
+
behaviors['form_mode'] = 'single'
|
|
123
|
+
# 弹窗类型: modal / drawer
|
|
124
|
+
if 'a-modal' in content or 'Modal' in content.split('import')[-1] if 'import' in content else '':
|
|
125
|
+
behaviors['popup'] = 'modal'
|
|
126
|
+
elif 'a-drawer' in content or 'Drawer' in content:
|
|
127
|
+
behaviors['popup'] = 'drawer'
|
|
128
|
+
if behaviors:
|
|
129
|
+
patterns['behaviors'] = behaviors
|
|
130
|
+
|
|
88
131
|
return patterns
|
|
89
132
|
|
|
90
133
|
|
|
134
|
+
def detect_stack(proj_path):
|
|
135
|
+
"""Detect the UI tech stack from package.json dependencies.
|
|
136
|
+
|
|
137
|
+
Returns one of: 'vben-admin', 'ant-design-vue', 'vant-mobile',
|
|
138
|
+
'element-plus', 'react', 'unknown'. This lets the prototype generator pick
|
|
139
|
+
the right skeleton (or honestly degrade) instead of assuming Vben for every
|
|
140
|
+
project.
|
|
141
|
+
|
|
142
|
+
For monorepos (pnpm workspaces), reads apps/*/package.json where concrete
|
|
143
|
+
UI deps live (the root package.json is tooling-only with workspace:* refs).
|
|
144
|
+
"""
|
|
145
|
+
def _read_deps(pkg_path):
|
|
146
|
+
try:
|
|
147
|
+
with open(pkg_path, encoding='utf-8', errors='ignore') as f:
|
|
148
|
+
pkg = json.load(f)
|
|
149
|
+
deps = set((pkg.get('dependencies') or {}).keys())
|
|
150
|
+
deps |= set((pkg.get('devDependencies') or {}).keys())
|
|
151
|
+
return deps, pkg
|
|
152
|
+
except Exception:
|
|
153
|
+
return set(), {}
|
|
154
|
+
|
|
155
|
+
# 收集所有 package.json 的依赖 (root + monorepo apps/*)
|
|
156
|
+
all_deps = set()
|
|
157
|
+
root_pkg = os.path.join(proj_path, 'package.json')
|
|
158
|
+
if os.path.isfile(root_pkg):
|
|
159
|
+
d, _ = _read_deps(root_pkg)
|
|
160
|
+
all_deps |= d
|
|
161
|
+
# monorepo: apps/*/package.json 有真实 UI 依赖
|
|
162
|
+
apps_dir = os.path.join(proj_path, 'apps')
|
|
163
|
+
if os.path.isdir(apps_dir):
|
|
164
|
+
for app in os.listdir(apps_dir):
|
|
165
|
+
ap = os.path.join(apps_dir, app, 'package.json')
|
|
166
|
+
if os.path.isfile(ap):
|
|
167
|
+
d, _ = _read_deps(ap)
|
|
168
|
+
all_deps |= d
|
|
169
|
+
|
|
170
|
+
if not all_deps:
|
|
171
|
+
return 'unknown'
|
|
172
|
+
|
|
173
|
+
# 决策树 (按特异性排序)
|
|
174
|
+
if any(d.startswith('@vben/') for d in all_deps):
|
|
175
|
+
return 'vben-admin'
|
|
176
|
+
if 'react' in all_deps and 'react-dom' in all_deps and 'vue' not in all_deps:
|
|
177
|
+
return 'react'
|
|
178
|
+
if 'vant' in all_deps:
|
|
179
|
+
return 'vant-mobile'
|
|
180
|
+
if 'ant-design-vue' in all_deps:
|
|
181
|
+
return 'ant-design-vue'
|
|
182
|
+
if 'element-plus' in all_deps:
|
|
183
|
+
return 'element-plus'
|
|
184
|
+
if 'vue' in all_deps:
|
|
185
|
+
return 'ant-design-vue' # Vue 但无已知组件库, 假设最常见
|
|
186
|
+
return 'unknown'
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def extract_layout_fingerprint(proj_path):
|
|
190
|
+
"""Extract the project's layout "fingerprint" from Vben preferences.ts.
|
|
191
|
+
|
|
192
|
+
Captures layout mode, sidebar width, theme colors and access mode so the
|
|
193
|
+
prototype generator can mirror the *real* shell of the target project
|
|
194
|
+
(e.g. ICS uses ``mixed-nav`` with a backend-driven multi-level menu),
|
|
195
|
+
instead of always drawing a flat single-level sidebar.
|
|
196
|
+
|
|
197
|
+
Gracefully degrades to defaults when preferences.ts is absent (e.g. the
|
|
198
|
+
Vant mobile project Carmg-H5 has no Vben preferences at all).
|
|
199
|
+
"""
|
|
200
|
+
DEFAULT = {
|
|
201
|
+
'stack': 'unknown',
|
|
202
|
+
'layout': 'sidebar-nav',
|
|
203
|
+
'access_mode': 'frontend',
|
|
204
|
+
'sidebar_width': None,
|
|
205
|
+
'theme_mode': 'light',
|
|
206
|
+
'semi_dark_sidebar': False,
|
|
207
|
+
'color_primary': None,
|
|
208
|
+
'color_success': None,
|
|
209
|
+
'color_warning': None,
|
|
210
|
+
'color_destructive': None,
|
|
211
|
+
'radius': None,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# Find the first preferences.ts that actually overrides something.
|
|
215
|
+
# Monorepos (fywl-ui) may host several apps; pick the richest one.
|
|
216
|
+
pref_text = ''
|
|
217
|
+
for root, dirs, files in os.walk(proj_path):
|
|
218
|
+
dirs[:] = [d for d in dirs if d not in ['node_modules', 'dist', '.git', '__pycache__', '.svn']]
|
|
219
|
+
for f in files:
|
|
220
|
+
if f == 'preferences.ts':
|
|
221
|
+
try:
|
|
222
|
+
txt = open(os.path.join(root, f), encoding='utf-8', errors='ignore').read()
|
|
223
|
+
except Exception:
|
|
224
|
+
continue
|
|
225
|
+
# Prefer files that declare overridesPreferences (the app one),
|
|
226
|
+
# over framework defaults / type stubs.
|
|
227
|
+
if 'overridesPreferences' in txt or 'defineOverridesPreferences' in txt:
|
|
228
|
+
pref_text = txt
|
|
229
|
+
break
|
|
230
|
+
if pref_text:
|
|
231
|
+
break
|
|
232
|
+
|
|
233
|
+
if not pref_text:
|
|
234
|
+
# 无 preferences.ts 也要检测技术栈 (来自 package.json), 否则新项目
|
|
235
|
+
# 永远显示 unknown stack, 原型生成器无法选对骨架
|
|
236
|
+
DEFAULT['stack'] = detect_stack(proj_path)
|
|
237
|
+
return DEFAULT
|
|
238
|
+
|
|
239
|
+
fp = dict(DEFAULT)
|
|
240
|
+
|
|
241
|
+
def grab(key, within=None):
|
|
242
|
+
block = within if within is not None else pref_text
|
|
243
|
+
m = re.search(r"%s\s*:\s*['\"]([^'\"]+)['\"]" % re.escape(key), block)
|
|
244
|
+
return m.group(1) if m else None
|
|
245
|
+
|
|
246
|
+
def grab_num(key, within=None):
|
|
247
|
+
block = within if within is not None else pref_text
|
|
248
|
+
m = re.search(r"%s\s*:\s*(\d+)" % re.escape(key), block)
|
|
249
|
+
return int(m.group(1)) if m else None
|
|
250
|
+
|
|
251
|
+
def grab_bool(key, within=None):
|
|
252
|
+
block = within if within is not None else pref_text
|
|
253
|
+
m = re.search(r"%s\s*:\s*(true|false)" % re.escape(key), block)
|
|
254
|
+
return m.group(1) == 'true' if m else None
|
|
255
|
+
|
|
256
|
+
fp['layout'] = grab('layout') or DEFAULT['layout']
|
|
257
|
+
fp['access_mode'] = grab('accessMode') or DEFAULT['access_mode']
|
|
258
|
+
fp['sidebar_width'] = grab_num('width')
|
|
259
|
+
|
|
260
|
+
# theme block
|
|
261
|
+
theme_m = re.search(r"theme\s*:\s*\{([^}]*)\}", pref_text, re.S)
|
|
262
|
+
theme_block = theme_m.group(1) if theme_m else ''
|
|
263
|
+
if theme_block:
|
|
264
|
+
fp['theme_mode'] = grab('mode', theme_block) or DEFAULT['theme_mode']
|
|
265
|
+
fp['semi_dark_sidebar'] = grab_bool('semiDarkSidebar', theme_block)
|
|
266
|
+
if fp['semi_dark_sidebar'] is None:
|
|
267
|
+
fp['semi_dark_sidebar'] = DEFAULT['semi_dark_sidebar']
|
|
268
|
+
for target, src in [('color_primary', 'colorPrimary'),
|
|
269
|
+
('color_success', 'colorSuccess'),
|
|
270
|
+
('color_warning', 'colorWarning'),
|
|
271
|
+
('color_destructive', 'colorDestructive')]:
|
|
272
|
+
val = grab(src, theme_block)
|
|
273
|
+
if val:
|
|
274
|
+
fp[target] = val
|
|
275
|
+
|
|
276
|
+
rad = grab('radius')
|
|
277
|
+
if rad:
|
|
278
|
+
fp['radius'] = rad
|
|
279
|
+
|
|
280
|
+
# 技术栈检测 (独立于 preferences.ts, 来自 package.json)
|
|
281
|
+
fp['stack'] = detect_stack(proj_path)
|
|
282
|
+
|
|
283
|
+
return fp
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# 常见模块目录名 -> 中文标签映射 (兜底, 当 repowiki 无对应时用).
|
|
287
|
+
# 覆盖 ICS 当前已知模块; 新模块若不在表里则用目录名本身.
|
|
288
|
+
MODULE_CN_MAP = {
|
|
289
|
+
'agent': '代理人', 'assets': '资产管理', 'assignment': '任务调度',
|
|
290
|
+
'core': '核心', 'cost': '费用管理', 'dashboard': '概览工作台',
|
|
291
|
+
'energy': '能耗管理', 'fleet': '车队管理', 'flow': '流程管理',
|
|
292
|
+
'hr': '人事管理', 'maintenance': '维护管理', 'monitor': '监控管理',
|
|
293
|
+
'operation': '运营管理', 'quality': '质量管理', 'qalityassessment': '质量考核',
|
|
294
|
+
'qualityandsafety': '质量安全', 'settings': '系统设置', 'system': '系统管理',
|
|
295
|
+
'tool': '工具', 'vehlife': '车辆生活',
|
|
296
|
+
# APP (Carmg-H5) modules
|
|
297
|
+
'icsAbnormalCenter': '异常中心', 'icsAbnormalStatistics': '异常统计',
|
|
298
|
+
'icsAttendance': '考勤管理', 'icsAttendCheck': '考勤核查',
|
|
299
|
+
'icsBudgetDashboard': '预算看板', 'icsBusiness': '业务管理',
|
|
300
|
+
'icsCostManage': '费用管理', 'icsDailyReport': '日报管理',
|
|
301
|
+
'icsFacilities': '设施管理', 'icsFeedBack': '意见反馈',
|
|
302
|
+
'icsGarden': '园区管理', 'icsHrManagement': '人事管理',
|
|
303
|
+
'icsInventory': '库存管理', 'icsLogin': '登录',
|
|
304
|
+
'icsMaintenanceManag': '维护管理', 'icsMaterial': '物料管理',
|
|
305
|
+
'icsNotice': '通知公告', 'icsOil': '油耗管理',
|
|
306
|
+
'icsOperation': '运营管理', 'icsPersonalCenter': '个人中心',
|
|
307
|
+
'icsQualityInspection': '质量巡查', 'icsSafe': '安全管理',
|
|
308
|
+
'icsSmartProperty': '智慧物业', 'icsVehicle': '车辆管理',
|
|
309
|
+
'icsVehicleAffairs': '车务管理', 'icsTodo': '待办事项',
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def extract_modules(proj_name, proj_path):
|
|
314
|
+
"""提取项目的业务模块清单 (英文目录名 + 中文标签 + 来源路径)。
|
|
315
|
+
|
|
316
|
+
扫描两类来源:
|
|
317
|
+
- Web (fywl-ui): apps/*/src/api/<module>/ 目录
|
|
318
|
+
- APP (Carmg-H5): src/modules/<module>/ 目录
|
|
319
|
+
中文标签优先用 MODULE_CN_MAP 兜底表。返回 [{dir, cn, path}] 列表。
|
|
320
|
+
"""
|
|
321
|
+
# 明显的测试/工具/示例目录, 不算业务模块 (避免污染菜单)
|
|
322
|
+
NOISE_DIRS = {
|
|
323
|
+
'demo', 'test', 'newtest', 'icstest', 'example', 'examples',
|
|
324
|
+
'icsqrcode', 'icscontact', 'icshelpcenter', 'icsskill',
|
|
325
|
+
'icsstationvideo', 'icsworkinstruction', 'icsnotice',
|
|
326
|
+
}
|
|
327
|
+
modules = []
|
|
328
|
+
seen = set()
|
|
329
|
+
|
|
330
|
+
def _add(dir_name, rel_path):
|
|
331
|
+
key = dir_name.lower()
|
|
332
|
+
if key in seen or key in NOISE_DIRS:
|
|
333
|
+
return
|
|
334
|
+
seen.add(key)
|
|
335
|
+
modules.append({
|
|
336
|
+
'dir': dir_name,
|
|
337
|
+
'cn': MODULE_CN_MAP.get(dir_name, MODULE_CN_MAP.get(dir_name.lower(), dir_name)),
|
|
338
|
+
'path': (proj_name + '/' + rel_path).replace('\\', '/'),
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
# Web: apps/*/src/api/<module>/
|
|
342
|
+
api_root = os.path.join(proj_path, 'apps')
|
|
343
|
+
if os.path.isdir(api_root):
|
|
344
|
+
for app in os.listdir(api_root):
|
|
345
|
+
api_dir = os.path.join(api_root, app, 'src', 'api')
|
|
346
|
+
if not os.path.isdir(api_dir):
|
|
347
|
+
continue
|
|
348
|
+
for d in sorted(os.listdir(api_dir)):
|
|
349
|
+
if d.startswith('.') or d.startswith('_'):
|
|
350
|
+
continue
|
|
351
|
+
if os.path.isdir(os.path.join(api_dir, d)):
|
|
352
|
+
_add(d, 'apps/%s/src/api/%s' % (app, d))
|
|
353
|
+
|
|
354
|
+
# APP: src/modules/<module>/ (Carmg-H5 结构)
|
|
355
|
+
mods_dir = os.path.join(proj_path, 'src', 'modules')
|
|
356
|
+
if os.path.isdir(mods_dir):
|
|
357
|
+
for d in sorted(os.listdir(mods_dir)):
|
|
358
|
+
if d.startswith('.') or d.startswith('_'):
|
|
359
|
+
continue
|
|
360
|
+
if os.path.isdir(os.path.join(mods_dir, d)):
|
|
361
|
+
_add(d, 'src/modules/%s' % d)
|
|
362
|
+
|
|
363
|
+
return modules
|
|
364
|
+
|
|
365
|
+
|
|
91
366
|
def scan_project(proj_name, proj_path):
|
|
92
367
|
"""Scan a single frontend project."""
|
|
93
368
|
pages = []
|
|
@@ -153,6 +428,106 @@ def scan_project(proj_name, proj_path):
|
|
|
153
428
|
}
|
|
154
429
|
|
|
155
430
|
|
|
431
|
+
def build_trace_index():
|
|
432
|
+
"""Build the call-chain index: button @click → API endpoint → Java controller.
|
|
433
|
+
|
|
434
|
+
Scans fywl-ui (Web) Vue views + api files. Returns a dict with:
|
|
435
|
+
api_functions: {fnName: {url, verb, file}}
|
|
436
|
+
traces: [{vue_file, button, handler, api_fn, endpoint, verb, controller, confidence}]
|
|
437
|
+
stats: {total_clicks, resolved, coverage_pct, by_confidence}
|
|
438
|
+
|
|
439
|
+
Cross-references endpoints against code-api.json (built by git_sync.py)
|
|
440
|
+
to attach the Java controller file. Carmg-H5 (APP) is skipped — different
|
|
441
|
+
structure, separate concern.
|
|
442
|
+
"""
|
|
443
|
+
trace_index = {
|
|
444
|
+
'version': '1.0', 'updated': datetime.now().isoformat(),
|
|
445
|
+
'api_functions': {}, 'traces': [], 'stats': {},
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
# Only process projects that have the Web (Vben) views+api structure
|
|
449
|
+
web_proj_path = os.path.join(DATA_CODE, 'fywl-ui')
|
|
450
|
+
if not os.path.isdir(web_proj_path):
|
|
451
|
+
return trace_index
|
|
452
|
+
|
|
453
|
+
# Find the api dir (monorepo: apps/*/src/api)
|
|
454
|
+
api_dir = None
|
|
455
|
+
apps_dir = os.path.join(web_proj_path, 'apps')
|
|
456
|
+
if os.path.isdir(apps_dir):
|
|
457
|
+
for app in os.listdir(apps_dir):
|
|
458
|
+
candidate = os.path.join(apps_dir, app, 'src', 'api')
|
|
459
|
+
if os.path.isdir(candidate):
|
|
460
|
+
api_dir = candidate
|
|
461
|
+
break
|
|
462
|
+
if not api_dir:
|
|
463
|
+
return trace_index
|
|
464
|
+
|
|
465
|
+
# Pass 1: build api function table
|
|
466
|
+
api_table = build_api_fn_table(api_dir)
|
|
467
|
+
trace_index['api_functions'] = api_table
|
|
468
|
+
|
|
469
|
+
# Load code-api.json for Java controller cross-reference
|
|
470
|
+
java_endpoints = {}
|
|
471
|
+
api_idx_path = os.path.join(BUILD_CACHE_DIR, 'code-api.json')
|
|
472
|
+
if os.path.isfile(api_idx_path):
|
|
473
|
+
try:
|
|
474
|
+
java_endpoints = json.load(open(api_idx_path, encoding='utf-8'))
|
|
475
|
+
except Exception:
|
|
476
|
+
pass
|
|
477
|
+
|
|
478
|
+
# Pass 2: scan Vue views for click traces
|
|
479
|
+
views_dir = None
|
|
480
|
+
if os.path.isdir(apps_dir):
|
|
481
|
+
for app in os.listdir(apps_dir):
|
|
482
|
+
candidate = os.path.join(apps_dir, app, 'src', 'views')
|
|
483
|
+
if os.path.isdir(candidate):
|
|
484
|
+
views_dir = candidate
|
|
485
|
+
break
|
|
486
|
+
|
|
487
|
+
total_clicks = 0
|
|
488
|
+
resolved = 0
|
|
489
|
+
by_conf = {}
|
|
490
|
+
if views_dir:
|
|
491
|
+
for root, dirs, files in os.walk(views_dir):
|
|
492
|
+
dirs[:] = [d for d in dirs if d not in ('node_modules', 'dist', '.git')]
|
|
493
|
+
for f in files:
|
|
494
|
+
if not f.endswith('.vue'):
|
|
495
|
+
continue
|
|
496
|
+
fpath = os.path.join(root, f)
|
|
497
|
+
try:
|
|
498
|
+
content = open(fpath, encoding='utf-8', errors='ignore').read()
|
|
499
|
+
except Exception:
|
|
500
|
+
continue
|
|
501
|
+
if '@click' not in content:
|
|
502
|
+
continue
|
|
503
|
+
traces = trace_clicks(fpath, content, api_table)
|
|
504
|
+
for t in traces:
|
|
505
|
+
total_clicks += 1
|
|
506
|
+
conf = t.get('confidence', 'unresolved')
|
|
507
|
+
by_conf[conf] = by_conf.get(conf, 0) + 1
|
|
508
|
+
# Cross-ref endpoint → Java controller
|
|
509
|
+
endpoint = t.get('endpoint')
|
|
510
|
+
if endpoint and endpoint in java_endpoints:
|
|
511
|
+
t['controller'] = java_endpoints[endpoint].split('/')[-1]
|
|
512
|
+
else:
|
|
513
|
+
# Try prefix match (endpoint may have path params)
|
|
514
|
+
for ep, ctrl in java_endpoints.items():
|
|
515
|
+
if endpoint and (endpoint.startswith(ep) or ep.startswith(endpoint)):
|
|
516
|
+
t['controller'] = ctrl.split('/')[-1]
|
|
517
|
+
break
|
|
518
|
+
if t.get('endpoint'):
|
|
519
|
+
resolved += 1
|
|
520
|
+
trace_index['traces'].append(t)
|
|
521
|
+
|
|
522
|
+
trace_index['stats'] = {
|
|
523
|
+
'total_clicks': total_clicks,
|
|
524
|
+
'resolved': resolved,
|
|
525
|
+
'coverage_pct': round(100.0 * resolved / total_clicks, 1) if total_clicks else 0,
|
|
526
|
+
'by_confidence': by_conf,
|
|
527
|
+
}
|
|
528
|
+
return trace_index
|
|
529
|
+
|
|
530
|
+
|
|
156
531
|
def main():
|
|
157
532
|
print('Building style index...')
|
|
158
533
|
print('Code dir: ' + DATA_CODE)
|
|
@@ -174,6 +549,7 @@ def main():
|
|
|
174
549
|
total_component_counter = {}
|
|
175
550
|
total_form_components = {}
|
|
176
551
|
total_field_map = {}
|
|
552
|
+
module_index = {'version': '1.0', 'updated': datetime.now().isoformat(), 'projects': {}}
|
|
177
553
|
|
|
178
554
|
for proj in sorted(os.listdir(DATA_CODE)):
|
|
179
555
|
proj_path = os.path.join(DATA_CODE, proj)
|
|
@@ -184,11 +560,17 @@ def main():
|
|
|
184
560
|
result = scan_project(proj, proj_path)
|
|
185
561
|
print(' Vue: ' + str(result['vue_count']) + ', Data: ' + str(result['data_count']))
|
|
186
562
|
|
|
563
|
+
mods = extract_modules(proj, proj_path)
|
|
564
|
+
if mods:
|
|
565
|
+
module_index['projects'][proj] = mods
|
|
566
|
+
print(' Modules: ' + str(len(mods)))
|
|
567
|
+
|
|
187
568
|
style_index['projects'][proj] = {
|
|
188
569
|
'vue_files': result['vue_count'],
|
|
189
570
|
'data_files': result['data_count'],
|
|
190
571
|
'page_types': {},
|
|
191
572
|
'page_examples': {},
|
|
573
|
+
'layout_fingerprint': extract_layout_fingerprint(proj_path),
|
|
192
574
|
}
|
|
193
575
|
|
|
194
576
|
# Count page types and keep sample files per type so
|
|
@@ -201,8 +583,17 @@ def main():
|
|
|
201
583
|
pages_by_type.setdefault(pt, []).append(page)
|
|
202
584
|
for pt, pages in pages_by_type.items():
|
|
203
585
|
pages.sort(key=lambda p: len(p.get('table_columns', [])) + len(p.get('form_fields', [])), reverse=True)
|
|
204
|
-
|
|
205
|
-
|
|
586
|
+
# 保留提取的结构 (table_columns/form_fields/form_components/layout),
|
|
587
|
+
# 供 fill_prototype.py 自动填模板用。旧版只存 file 路径, 扔了结构。
|
|
588
|
+
style_index['projects'][proj]['page_examples'][pt] = [
|
|
589
|
+
{'file': p['file'],
|
|
590
|
+
'table_columns': p.get('table_columns', []),
|
|
591
|
+
'form_fields': p.get('form_fields', []),
|
|
592
|
+
'form_components': p.get('form_components', []),
|
|
593
|
+
'layout': p.get('layout', {}),
|
|
594
|
+
'behaviors': p.get('behaviors', {})}
|
|
595
|
+
for p in pages[:10]
|
|
596
|
+
]
|
|
206
597
|
|
|
207
598
|
# Merge component counters
|
|
208
599
|
for comp, count in result['component_counter'].items():
|
|
@@ -256,11 +647,60 @@ def main():
|
|
|
256
647
|
with open(STYLE_META_PATH, 'w', encoding='utf-8') as f:
|
|
257
648
|
json.dump(meta, f, ensure_ascii=False, indent=2, sort_keys=True)
|
|
258
649
|
|
|
650
|
+
# ui-modules.json — 业务模块清单 (供 fill_prototype 取真实模块名,
|
|
651
|
+
# 替代靠文件路径猜的 infer_module_name; 让菜单不再是 "veh"/"二级菜单A")
|
|
652
|
+
module_index['updated'] = datetime.now().isoformat()
|
|
653
|
+
with open(MODULE_INDEX_PATH, 'w', encoding='utf-8') as f:
|
|
654
|
+
json.dump(module_index, f, ensure_ascii=False, indent=2, sort_keys=True)
|
|
655
|
+
|
|
656
|
+
# trace-chain.json — 调用链索引 (按钮@click → API端点 → Java Controller)
|
|
657
|
+
# 支撑测试自动化 (QoderWork 知道点哪调啥) + 开发影响分析 (改接口影响哪些页面)
|
|
658
|
+
print('Building trace index (engine: %s)...' % _EXTRACT_ENGINE)
|
|
659
|
+
trace_index = build_trace_index()
|
|
660
|
+
with open(TRACE_INDEX_PATH, 'w', encoding='utf-8') as f:
|
|
661
|
+
json.dump(trace_index, f, ensure_ascii=False, indent=2, sort_keys=True)
|
|
662
|
+
|
|
663
|
+
# test-assertions.json — E1: 测试代码提取 (Given-When-Then + 断言 + 注入依赖)
|
|
664
|
+
# 第二层知识: 生产代码提取不到的业务预期和断言标准, 补上'怎么算对'天花板
|
|
665
|
+
print('Building test assertions index...')
|
|
666
|
+
all_tests = []
|
|
667
|
+
for proj in sorted(os.listdir(DATA_CODE)):
|
|
668
|
+
proj_path = os.path.join(DATA_CODE, proj)
|
|
669
|
+
if os.path.isdir(proj_path):
|
|
670
|
+
tests = extract_test_assertions(proj_path)
|
|
671
|
+
all_tests.extend(tests)
|
|
672
|
+
test_index = {
|
|
673
|
+
'version': '1.0', 'updated': datetime.now().isoformat(),
|
|
674
|
+
'tests': all_tests,
|
|
675
|
+
'stats': {'total': len(all_tests),
|
|
676
|
+
'integration': sum(1 for t in all_tests if t.get('is_integration'))},
|
|
677
|
+
}
|
|
678
|
+
with open(TEST_ASSERTIONS_PATH, 'w', encoding='utf-8') as f:
|
|
679
|
+
json.dump(test_index, f, ensure_ascii=False, indent=2, sort_keys=True)
|
|
680
|
+
|
|
681
|
+
# ── 知识图谱层: relations + features + entity-registry + DuckDB ──
|
|
682
|
+
print('Building knowledge graph layers...')
|
|
683
|
+
import subprocess
|
|
684
|
+
for script in ['build_relations.py', 'build_features.py',
|
|
685
|
+
'build_entity_registry.py', 'build_workflows.py', 'kg_duckdb.py']:
|
|
686
|
+
sp = os.path.join(os.path.dirname(os.path.abspath(__file__)), script)
|
|
687
|
+
if os.path.isfile(sp):
|
|
688
|
+
try:
|
|
689
|
+
subprocess.run([sys.executable, sp], capture_output=True, timeout=120)
|
|
690
|
+
print(' %s OK' % script)
|
|
691
|
+
except Exception as e:
|
|
692
|
+
print(' %s SKIP: %s' % (script, str(e)[:60]))
|
|
693
|
+
|
|
259
694
|
print()
|
|
260
695
|
print('=== Done ===')
|
|
261
696
|
print('Common components: ' + str(len(style_index['common_components'])))
|
|
262
697
|
print('Form components: ' + str(len(style_index['form_components'])))
|
|
263
698
|
print('Field map: ' + str(len(style_index['field_map'])) + ' entries')
|
|
699
|
+
print('Module index: ' + str(sum(len(v) for v in module_index['projects'].values())) + ' modules')
|
|
700
|
+
print('Trace index: ' + str(len(trace_index['api_functions'])) + ' api fns, '
|
|
701
|
+
+ str(trace_index['stats'].get('resolved', 0)) + '/'
|
|
702
|
+
+ str(trace_index['stats'].get('total_clicks', 0)) + ' clicks resolved ('
|
|
703
|
+
+ str(trace_index['stats'].get('coverage_pct', 0)) + '%)')
|
|
264
704
|
print('Saved to: ' + STYLE_META_PATH)
|
|
265
705
|
|
|
266
706
|
|