@hupan56/wlkj 2.6.0 → 2.7.1
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 +269 -11
- 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-reference.md +8 -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 -311
- 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 +51 -15
- package/templates/qoder/hooks/inject-workflow-state.py +2 -2
- package/templates/qoder/hooks/session-start.py +82 -51
- 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/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/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 -231
- 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 +292 -40
- 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 -641
- 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 +2 -2
- 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 +184 -60
- 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/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 -57
- package/templates/qoder/agents/prd-research.md +0 -34
- package/templates/qoder/commands/wl-insight.md +0 -51
- package/templates/qoder/scripts/__pycache__/init_doctor.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/setup.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/common/__pycache__/developer.cpython-39.pyc +0 -0
- package/templates/qoder/skills/wl-prd/SKILL.md +0 -92
|
@@ -27,7 +27,7 @@ except (AttributeError, TypeError, OSError, IOError):
|
|
|
27
27
|
pass
|
|
28
28
|
|
|
29
29
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
30
|
-
from common.terms import CN_MAP, BUSINESS_PATH_MAP, CN_TO_EN, get_platform_map
|
|
30
|
+
from common.terms import CN_MAP, BUSINESS_PATH_MAP, CN_TO_EN, get_platform_map, expand_chinese_query
|
|
31
31
|
PLATFORM_MAP = get_platform_map()
|
|
32
32
|
|
|
33
33
|
BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
@@ -38,7 +38,20 @@ MIN_FUZZY_LEN = 4
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def load_index(filename, hint=''):
|
|
41
|
+
"""Load an index. Prefers DuckDB (kg.duckdb), falls back to JSON file.
|
|
42
|
+
|
|
43
|
+
Returns the same dict structure as the JSON file would have, so all
|
|
44
|
+
callers work unchanged regardless of the backend.
|
|
45
|
+
"""
|
|
46
|
+
# Try DuckDB first
|
|
47
|
+
result = _duckdb_load(filename)
|
|
48
|
+
if result is not None:
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
# Fallback: read JSON file
|
|
41
52
|
path = os.path.join(INDEX_DIR, filename)
|
|
53
|
+
if not os.path.isfile(path):
|
|
54
|
+
path = os.path.join(INDEX_DIR, '_build_cache', filename)
|
|
42
55
|
if not os.path.isfile(path):
|
|
43
56
|
print('{} not found.{}'.format(filename, ' ' + hint if hint else ''))
|
|
44
57
|
return None
|
|
@@ -50,6 +63,150 @@ def load_index(filename, hint=''):
|
|
|
50
63
|
return None
|
|
51
64
|
|
|
52
65
|
|
|
66
|
+
# DuckDB path (lazy-connect)
|
|
67
|
+
_duckdb_con = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _get_duckdb():
|
|
71
|
+
global _duckdb_con
|
|
72
|
+
if _duckdb_con is not None:
|
|
73
|
+
return _duckdb_con
|
|
74
|
+
db_path = os.path.join(INDEX_DIR, 'kg.duckdb')
|
|
75
|
+
if not os.path.isfile(db_path):
|
|
76
|
+
return None
|
|
77
|
+
try:
|
|
78
|
+
import duckdb
|
|
79
|
+
_duckdb_con = duckdb.connect(db_path, read_only=True)
|
|
80
|
+
return _duckdb_con
|
|
81
|
+
except Exception:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _load_keyword_with_cache(con):
|
|
86
|
+
"""加载 code-keyword (11836 行), 带 pickle 缓存层。
|
|
87
|
+
|
|
88
|
+
性能: DuckDB SELECT + Python 循环重建 = ~180ms;
|
|
89
|
+
pickle.load = ~37ms (提速 5x, 实测数据一致)。
|
|
90
|
+
缓存失效: kg.duckdb 的 mtime 变了就重建 (和 context_pack 缓存逻辑一致)。
|
|
91
|
+
降级: pickle 损坏/版本不兼容 → 静默回退 DuckDB 重建, 不报错。
|
|
92
|
+
"""
|
|
93
|
+
import pickle
|
|
94
|
+
duckdb_path = os.path.join(INDEX_DIR, 'kg.duckdb')
|
|
95
|
+
cache_path = os.path.join(BASE, '.qoder', '.runtime', 'code-keyword.pkl')
|
|
96
|
+
db_mtime = os.path.getmtime(duckdb_path) if os.path.isfile(duckdb_path) else 0
|
|
97
|
+
|
|
98
|
+
# ① 缓存有效 (存在 + mtime 匹配) → 直接 load, 跳过 DuckDB
|
|
99
|
+
if os.path.isfile(cache_path):
|
|
100
|
+
try:
|
|
101
|
+
if os.path.getmtime(cache_path) >= db_mtime:
|
|
102
|
+
with open(cache_path, 'rb') as f:
|
|
103
|
+
return pickle.load(f)
|
|
104
|
+
except (pickle.PickleError, EOFError, OSError):
|
|
105
|
+
pass # 缓存损坏 → 回退重建
|
|
106
|
+
|
|
107
|
+
# ② 缓存无效/缺失 → 从 DuckDB 重建
|
|
108
|
+
rows = con.execute("SELECT keyword, file_path FROM keywords").fetchall()
|
|
109
|
+
result = {}
|
|
110
|
+
for kw, fp in rows:
|
|
111
|
+
result.setdefault(kw, []).append(fp)
|
|
112
|
+
if not result:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
# ③ 写缓存 (best-effort, 失败不影响返回结果)
|
|
116
|
+
try:
|
|
117
|
+
os.makedirs(os.path.dirname(cache_path), exist_ok=True)
|
|
118
|
+
with open(cache_path, 'wb') as f:
|
|
119
|
+
pickle.dump(result, f, protocol=pickle.HIGHEST_PROTOCOL)
|
|
120
|
+
except OSError:
|
|
121
|
+
pass
|
|
122
|
+
return result
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _duckdb_load(filename):
|
|
126
|
+
"""Load an index from DuckDB, returning the same structure as the JSON.
|
|
127
|
+
|
|
128
|
+
Returns None if DuckDB unavailable or table doesn't have data,
|
|
129
|
+
triggering the JSON fallback.
|
|
130
|
+
"""
|
|
131
|
+
con = _get_duckdb()
|
|
132
|
+
if con is None:
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
if filename == 'code-keyword.json':
|
|
137
|
+
# {keyword: [files]} — 11836 行重建耗时 180ms, 用 pickle 缓存提速到 37ms
|
|
138
|
+
return _load_keyword_with_cache(con)
|
|
139
|
+
|
|
140
|
+
elif filename == 'code-api.json':
|
|
141
|
+
# {endpoint: controller_file}
|
|
142
|
+
rows = con.execute("SELECT endpoint, controller_file FROM endpoints").fetchall()
|
|
143
|
+
return dict(rows) if rows else None
|
|
144
|
+
|
|
145
|
+
elif filename == 'ui-style.json':
|
|
146
|
+
# Reconstruct the full ui-style structure from pages/fields/style_meta
|
|
147
|
+
result = {'version': '1.0', 'projects': {}, 'common_components': {}, 'form_components': {}, 'field_map': {}}
|
|
148
|
+
# Pages
|
|
149
|
+
page_rows = con.execute("SELECT file, project, page_type, components, table_columns, form_fields, layout FROM pages").fetchall()
|
|
150
|
+
for f, proj, ptype, comps, cols, fields, layout in page_rows:
|
|
151
|
+
proj_data = result['projects'].setdefault(proj, {'page_types': {}, 'page_examples': {}})
|
|
152
|
+
proj_data['page_types'][ptype] = proj_data['page_types'].get(ptype, 0) + 1
|
|
153
|
+
examples = proj_data['page_examples'].setdefault(ptype, [])
|
|
154
|
+
examples.append({'file': f, 'components': json.loads(comps) if comps else [],
|
|
155
|
+
'table_columns': json.loads(cols) if cols else [],
|
|
156
|
+
'form_fields': json.loads(fields) if fields else [],
|
|
157
|
+
'layout': json.loads(layout) if layout else {}})
|
|
158
|
+
# Fields
|
|
159
|
+
field_rows = con.execute("SELECT field, title, count, sample_files FROM fields").fetchall()
|
|
160
|
+
for field, title, count, samples in field_rows:
|
|
161
|
+
titles = title.split('/') if title else []
|
|
162
|
+
result['field_map'][field] = {'field': field, 'titles': titles, 'count': count,
|
|
163
|
+
'sample_files': json.loads(samples) if samples else []}
|
|
164
|
+
# Style meta (layout_fingerprint per project)
|
|
165
|
+
meta_rows = con.execute("SELECT project, stack, layout, access_mode, sidebar_width, color_primary, color_success, color_warning, color_destructive, radius, vue_files, common_components, form_components FROM style_meta").fetchall()
|
|
166
|
+
for row in meta_rows:
|
|
167
|
+
proj = row[0]
|
|
168
|
+
proj_data = result['projects'].setdefault(proj, {'page_types': {}, 'page_examples': {}})
|
|
169
|
+
proj_data['layout_fingerprint'] = {
|
|
170
|
+
'stack': row[1], 'layout': row[2], 'access_mode': row[3],
|
|
171
|
+
'sidebar_width': row[4], 'color_primary': row[5], 'color_success': row[6],
|
|
172
|
+
'color_warning': row[7], 'color_destructive': row[8], 'radius': row[9],
|
|
173
|
+
}
|
|
174
|
+
proj_data['vue_files'] = row[10]
|
|
175
|
+
if row[11]:
|
|
176
|
+
result['common_components'] = json.loads(row[11])
|
|
177
|
+
if row[12]:
|
|
178
|
+
result['form_components'] = json.loads(row[12])
|
|
179
|
+
return result if result['projects'] or result['field_map'] else None
|
|
180
|
+
|
|
181
|
+
elif filename == 'prd-index.json':
|
|
182
|
+
rows = con.execute("SELECT file, title, keywords, features, cn_terms FROM prds").fetchall()
|
|
183
|
+
result = {}
|
|
184
|
+
for f, title, kws, feats, cn in rows:
|
|
185
|
+
result[f] = {'title': title,
|
|
186
|
+
'keywords': json.loads(kws) if kws else [],
|
|
187
|
+
'features': json.loads(feats) if feats else [],
|
|
188
|
+
'cn_terms': json.loads(cn) if cn else []}
|
|
189
|
+
return result if result else None
|
|
190
|
+
|
|
191
|
+
elif filename == 'ui-modules.json':
|
|
192
|
+
rows = con.execute("SELECT dir, cn, project, path FROM modules").fetchall()
|
|
193
|
+
result = {'projects': {}}
|
|
194
|
+
for d, cn, proj, path in rows:
|
|
195
|
+
result['projects'].setdefault(proj, []).append({'dir': d, 'cn': cn, 'path': path})
|
|
196
|
+
return result if result['projects'] else None
|
|
197
|
+
|
|
198
|
+
elif filename == 'code-stats.json':
|
|
199
|
+
rows = con.execute("SELECT project, vue_files FROM style_meta").fetchall()
|
|
200
|
+
result = {}
|
|
201
|
+
for proj, vf in rows:
|
|
202
|
+
result[proj] = {'files': vf or 0, 'classes': 0, 'apis': 0}
|
|
203
|
+
return result if result else None
|
|
204
|
+
|
|
205
|
+
except Exception:
|
|
206
|
+
return None
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
|
|
53
210
|
def filter_by_platform(files, platform=None):
|
|
54
211
|
if not platform:
|
|
55
212
|
return files
|
|
@@ -72,7 +229,7 @@ def _match_keyword(query_word, kw):
|
|
|
72
229
|
def _search_cache_key(query, platform):
|
|
73
230
|
"""缓存 key: query|platform|keyword-index mtime。"""
|
|
74
231
|
import hashlib
|
|
75
|
-
ki_path = os.path.join(BASE, 'data', 'index', 'keyword
|
|
232
|
+
ki_path = os.path.join(BASE, 'data', 'index', 'code-keyword.json') if os.path.isdir(os.path.join(BASE, 'data', 'index')) else None
|
|
76
233
|
mtime = '%d' % os.path.getmtime(ki_path) if ki_path and os.path.isfile(ki_path) else '0'
|
|
77
234
|
raw = '{}|{}|{}'.format(query.lower(), platform or '', mtime)
|
|
78
235
|
return hashlib.md5(raw.encode('utf-8')).hexdigest()[:16]
|
|
@@ -215,61 +372,18 @@ def _cleanup_cache_dir(cache_dir, prefix, max_files=50):
|
|
|
215
372
|
pass
|
|
216
373
|
|
|
217
374
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
三级匹配:
|
|
222
|
-
1. 精确匹配: "异常" 在 CN_MAP → 直接扩展
|
|
223
|
-
2. 分词匹配: "异常记录" → 拆成 "异常" + "记录", 各自扩展
|
|
224
|
-
(贪心最长匹配: 从长到短扫 CN_MAP 的 key)
|
|
225
|
-
3. 兜底: 拆不出来的部分, 原样小写 (让英文子串索引做匹配)
|
|
226
|
-
|
|
227
|
-
同时合并 BUSINESS_PATH_MAP 的路径模式。
|
|
228
|
-
"""
|
|
229
|
-
words = []
|
|
230
|
-
# 合并 CN_MAP + PRD 自动扩展
|
|
231
|
-
try:
|
|
232
|
-
from common.terms import get_cn_map_with_auto
|
|
233
|
-
cn_map = get_cn_map_with_auto()
|
|
234
|
-
except ImportError:
|
|
235
|
-
cn_map = CN_MAP
|
|
236
|
-
|
|
237
|
-
# 1. 精确匹配
|
|
238
|
-
if query in cn_map:
|
|
239
|
-
words.extend(cn_map[query].split())
|
|
240
|
-
if query in BUSINESS_PATH_MAP:
|
|
241
|
-
words.extend(p.strip('/-').lower() for p in BUSINESS_PATH_MAP[query])
|
|
242
|
-
|
|
243
|
-
# 2. 分词匹配 (查询比单个 CN_MAP key 长 → 需要拆词)
|
|
244
|
-
if not words and len(query) >= 3:
|
|
245
|
-
# 贪心最长匹配: 把 CN_MAP keys 按长度降序排, 逐个从 query 里找
|
|
246
|
-
remaining = query
|
|
247
|
-
matched_segments = []
|
|
248
|
-
for cn in sorted(cn_map.keys(), key=len, reverse=True):
|
|
249
|
-
if cn in remaining:
|
|
250
|
-
matched_segments.append(cn)
|
|
251
|
-
remaining = remaining.replace(cn, ' ')
|
|
252
|
-
if matched_segments:
|
|
253
|
-
for seg in matched_segments:
|
|
254
|
-
words.extend(cn_map[seg].split())
|
|
255
|
-
if seg in BUSINESS_PATH_MAP:
|
|
256
|
-
words.extend(p.strip('/-').lower() for p in BUSINESS_PATH_MAP[seg])
|
|
257
|
-
# 剩余的英文/数字片段也加进去 (兜底)
|
|
258
|
-
for frag in remaining.split():
|
|
259
|
-
if frag and frag.isascii():
|
|
260
|
-
words.append(frag.lower())
|
|
261
|
-
|
|
262
|
-
return words
|
|
375
|
+
# _expand_chinese_query 已移至 common/terms.py (单一数据源, context_pack 共用)
|
|
376
|
+
# import 见文件头部: from common.terms import expand_chinese_query
|
|
263
377
|
|
|
264
378
|
|
|
265
379
|
def _search_keywords_impl(query, platform=None):
|
|
266
380
|
"""实际的搜索逻辑 (原 search_keywords 内容)。"""
|
|
267
|
-
ki = load_index('keyword
|
|
381
|
+
ki = load_index('code-keyword.json', hint='Run: python git_sync.py')
|
|
268
382
|
if ki is None:
|
|
269
383
|
return
|
|
270
384
|
|
|
271
385
|
# Expand Chinese query via the shared term maps (same maps used at build time)
|
|
272
|
-
words =
|
|
386
|
+
words = expand_chinese_query(query)
|
|
273
387
|
if words:
|
|
274
388
|
words = sorted(set(w.lower() for w in words))
|
|
275
389
|
print('Chinese: {} -> English: {}'.format(query, ' '.join(words)))
|
|
@@ -370,7 +484,7 @@ def search_prds(query):
|
|
|
370
484
|
|
|
371
485
|
def search_style(style_type, platform=None):
|
|
372
486
|
"""Find pages of a given UI type (table/form/detail/modal/dashboard)."""
|
|
373
|
-
si = load_index('style
|
|
487
|
+
si = load_index('ui-style.json', hint='Run: python build_style_index.py')
|
|
374
488
|
if si is None:
|
|
375
489
|
return
|
|
376
490
|
q = style_type.lower()
|
|
@@ -389,7 +503,9 @@ def search_style(style_type, platform=None):
|
|
|
389
503
|
found_any = True
|
|
390
504
|
for pt in hit_types:
|
|
391
505
|
print('[{}] {}: {} pages'.format(proj, pt, page_types[pt]))
|
|
392
|
-
for
|
|
506
|
+
for item in examples.get(pt, [])[:8]:
|
|
507
|
+
# page_examples 元素可能是 dict (新格式, 含结构) 或 str (旧格式)
|
|
508
|
+
f = item['file'] if isinstance(item, dict) else item
|
|
393
509
|
print(' ' + f)
|
|
394
510
|
if not examples.get(pt):
|
|
395
511
|
print(' (no examples stored - run: python build_style_index.py)')
|
|
@@ -403,8 +519,8 @@ def search_style(style_type, platform=None):
|
|
|
403
519
|
|
|
404
520
|
|
|
405
521
|
def search_field(field_name):
|
|
406
|
-
"""Search field usage in style
|
|
407
|
-
si = load_index('style
|
|
522
|
+
"""Search field usage in ui-style.json field_map."""
|
|
523
|
+
si = load_index('ui-style.json', hint='Run: python build_style_index.py')
|
|
408
524
|
if si is None:
|
|
409
525
|
return
|
|
410
526
|
fm = si.get('field_map', {})
|
|
@@ -421,8 +537,8 @@ def search_field(field_name):
|
|
|
421
537
|
|
|
422
538
|
|
|
423
539
|
def search_api(query):
|
|
424
|
-
"""Search API endpoints in api
|
|
425
|
-
ai = load_index('api
|
|
540
|
+
"""Search API endpoints in code-api.json."""
|
|
541
|
+
ai = load_index('code-api.json', hint='Run: python git_sync.py --index-only')
|
|
426
542
|
if ai is None:
|
|
427
543
|
return
|
|
428
544
|
q = query.lower()
|
|
@@ -438,8 +554,8 @@ def search_api(query):
|
|
|
438
554
|
|
|
439
555
|
|
|
440
556
|
def show_modules():
|
|
441
|
-
"""Show project overview from
|
|
442
|
-
mm = load_index('
|
|
557
|
+
"""Show project overview from code-stats.json."""
|
|
558
|
+
mm = load_index('code-stats.json', hint='Run: python git_sync.py --index-only')
|
|
443
559
|
if mm is None:
|
|
444
560
|
return
|
|
445
561
|
print('=== Project Modules ===')
|
|
@@ -453,7 +569,7 @@ def show_modules():
|
|
|
453
569
|
|
|
454
570
|
def show_top_keywords():
|
|
455
571
|
"""Show top keywords by file count."""
|
|
456
|
-
ki = load_index('keyword
|
|
572
|
+
ki = load_index('code-keyword.json', hint='Run: python git_sync.py')
|
|
457
573
|
if ki is None:
|
|
458
574
|
return
|
|
459
575
|
top = sorted(ki.items(), key=lambda x: len(x[1]), reverse=True)[:50]
|
|
@@ -464,7 +580,7 @@ def show_top_keywords():
|
|
|
464
580
|
|
|
465
581
|
|
|
466
582
|
def show_components():
|
|
467
|
-
meta = load_index('style-meta.json', hint='Run: python build_style_index.py')
|
|
583
|
+
meta = load_index('ui-style-meta.json', hint='Run: python build_style_index.py')
|
|
468
584
|
if meta is None:
|
|
469
585
|
return
|
|
470
586
|
print('=== UI Components Usage ===')
|
|
@@ -479,7 +595,7 @@ def show_components():
|
|
|
479
595
|
|
|
480
596
|
|
|
481
597
|
def show_vben():
|
|
482
|
-
data = load_index('vben-style
|
|
598
|
+
data = load_index('ref-vben-style.json')
|
|
483
599
|
if data is None:
|
|
484
600
|
return
|
|
485
601
|
print('=== Vben Admin Design Tokens ===')
|
|
@@ -538,6 +654,30 @@ def search_wiki_cli(query):
|
|
|
538
654
|
print('\n 提示: 读 wiki 文档 (带行号引用) 比读源码更省 token')
|
|
539
655
|
|
|
540
656
|
|
|
657
|
+
|
|
658
|
+
def search_impact(query):
|
|
659
|
+
"""改这个接口/符号影响哪些页面/按钮/模块? (U1: 内存图遍历)"""
|
|
660
|
+
from common.graph_traverse import CodeGraph
|
|
661
|
+
g = CodeGraph(INDEX_DIR)
|
|
662
|
+
result = g.impact(query)
|
|
663
|
+
if result:
|
|
664
|
+
print(result)
|
|
665
|
+
else:
|
|
666
|
+
print('未找到匹配 "{}" 的端点。尝试用部分路径, 如 /veh 或 export'.format(query))
|
|
667
|
+
print('提示: trace-chain 当前覆盖率 31%, 未覆盖的调用不会被列出')
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def search_context360(query):
|
|
671
|
+
"""符号的360度视图: 端点/函数/处理器/PRD (U1: 内存图遍历)"""
|
|
672
|
+
from common.graph_traverse import CodeGraph
|
|
673
|
+
g = CodeGraph(INDEX_DIR)
|
|
674
|
+
result = g.context360(query)
|
|
675
|
+
if result:
|
|
676
|
+
print(result)
|
|
677
|
+
else:
|
|
678
|
+
print('未找到匹配 "{}" 的符号。'.format(query))
|
|
679
|
+
|
|
680
|
+
|
|
541
681
|
def print_usage():
|
|
542
682
|
print('Usage:')
|
|
543
683
|
print(' search_index.py <keyword> [--platform web|app] - Search code')
|
|
@@ -545,6 +685,12 @@ def print_usage():
|
|
|
545
685
|
print(' search_index.py --style <type> [--platform p] - UI patterns (table/form/detail/modal/dashboard)')
|
|
546
686
|
print(' search_index.py --field <name> - Field usage')
|
|
547
687
|
print(' search_index.py --api <keyword> - API endpoints')
|
|
688
|
+
print(' search_index.py --impact <endpoint> - 改这个接口影响哪些页面/按钮/模块')
|
|
689
|
+
print(' search_index.py --context360 <symbol> - 符号的360度视图(端点/函数/处理器/PRD)')
|
|
690
|
+
print(' search_index.py --coverage - 功能×测试覆盖矩阵(哪些功能没测试)')
|
|
691
|
+
print(' search_index.py --feature <名称> - 功能画像(端点+按钮+测试+页面)')
|
|
692
|
+
print(' search_index.py --workflow <名称> - 业务流程链(盘点有哪几步)')
|
|
693
|
+
print(' search_index.py --routes <关键词> - 前端页面路由(保险->/veh/vehicle/vehAffair/insurance)')
|
|
548
694
|
print(' search_index.py --components - UI components')
|
|
549
695
|
print(' search_index.py --wiki <keyword> - Repo Wiki 语义模块文档 (精准)')
|
|
550
696
|
print(' search_index.py --modules - Project overview')
|
|
@@ -554,6 +700,95 @@ def print_usage():
|
|
|
554
700
|
print('Platform: web/pc/管理端 -> fywl-ui, app/mobile/h5/移动端 -> Carmg-H5')
|
|
555
701
|
|
|
556
702
|
|
|
703
|
+
def _search_routes(keyword):
|
|
704
|
+
"""查前端页面路由: 优先用 menu-routes-tree.json(getRouters父子树, path准确),
|
|
705
|
+
回退 menu-routes.json(扁平, component猜的URL可能不准)。
|
|
706
|
+
|
|
707
|
+
例: _search_routes('保险') -> /veh/vehicle/vehAffair/insurance
|
|
708
|
+
⚠️ 若结果URL打开404, 说明 tree 缓存过期, 重跑: python .qoder/scripts/extract_routes_tree.py
|
|
709
|
+
"""
|
|
710
|
+
import os, json
|
|
711
|
+
kw = keyword.lower()
|
|
712
|
+
# ① 优先: 准确路由树 (path 直接来自后端父子拼接)
|
|
713
|
+
tree_file = os.path.join(INDEX_DIR, 'menu-routes-tree.json')
|
|
714
|
+
if os.path.isfile(tree_file):
|
|
715
|
+
data = json.load(open(tree_file, encoding='utf-8'))
|
|
716
|
+
routes = data.get('routes', [])
|
|
717
|
+
hits = [r for r in routes if kw in (r.get('path','') + r.get('name','') + r.get('component','')).lower()]
|
|
718
|
+
if hits:
|
|
719
|
+
print('=== 页面路由: %s (%d 条, 来源: 准确路由树) ===' % (keyword, len(hits)))
|
|
720
|
+
for r in hits:
|
|
721
|
+
print(' %s -> %s' % (r.get('name', '?'), r.get('path', '')))
|
|
722
|
+
return
|
|
723
|
+
# tree 里没有, 不再回退到不准的旧数据, 直接提示 (避免误导成404)
|
|
724
|
+
print('未找到 "%s" 的路由(已查准确路由树 %d 条)。' % (keyword, len(routes)))
|
|
725
|
+
print('提示: 试试英文关键词, 或重跑刷新: python .qoder/scripts/extract_routes_tree.py')
|
|
726
|
+
return
|
|
727
|
+
# ② 回退: 旧扁平数据 (component猜URL, 可能不准)
|
|
728
|
+
routes_file = os.path.join(INDEX_DIR, 'menu-routes.json')
|
|
729
|
+
if not os.path.isfile(routes_file):
|
|
730
|
+
print('无路由数据。先运行: python .qoder/scripts/extract_routes_tree.py')
|
|
731
|
+
return
|
|
732
|
+
data = json.load(open(routes_file, encoding='utf-8'))
|
|
733
|
+
routes = data.get('routes', [])
|
|
734
|
+
hits = []
|
|
735
|
+
for r in routes:
|
|
736
|
+
searchable = ' '.join([r.get('path',''), r.get('component',''), r.get('name','')]).lower()
|
|
737
|
+
if kw in searchable:
|
|
738
|
+
hits.append(r)
|
|
739
|
+
if not hits:
|
|
740
|
+
print('未找到 "%s" 的路由。' % keyword)
|
|
741
|
+
print('提示: 试试英文关键词, 或刷新路由树: python .qoder/scripts/extract_routes_tree.py')
|
|
742
|
+
return
|
|
743
|
+
print('=== 页面路由: %s (%d 条, 来源: 旧扁平数据, URL可能不准) ===' % (keyword, len(hits)))
|
|
744
|
+
for r in hits:
|
|
745
|
+
print(' %s -> path=%s component=%s' % (r.get('name', '?'), r.get('path', ''), r.get('component', '')))
|
|
746
|
+
print('⚠️ 以上URL可能不准(component猜的)。建议运行 extract_routes_tree.py 用准确路由树。')
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def _graph_cli(method, *args):
|
|
750
|
+
"""CLI 入口: 调图谱方法(让纯 Qoder IDE/CLI 也能用, 不只 MCP)。
|
|
751
|
+
|
|
752
|
+
CodeGraph 方法(coverage_matrix/feature_overview/impact)走 JSON 索引;
|
|
753
|
+
get_workflow 走 DuckDB(业务操作链)。
|
|
754
|
+
"""
|
|
755
|
+
try:
|
|
756
|
+
if method == 'get_workflow':
|
|
757
|
+
# DuckDB 路径
|
|
758
|
+
import kg_duckdb as K
|
|
759
|
+
q = (args[0] if args else '').strip()
|
|
760
|
+
if not q:
|
|
761
|
+
print('请提供模块名, 如: assets / cost / hr / maintenance')
|
|
762
|
+
return
|
|
763
|
+
con = K.get_db()
|
|
764
|
+
rows = con.execute(
|
|
765
|
+
"SELECT seq, state, endpoint, button FROM workflows WHERE module=? ORDER BY seq",
|
|
766
|
+
[q.lower()]
|
|
767
|
+
).fetchall()
|
|
768
|
+
if not rows:
|
|
769
|
+
mods = [r[0] for r in con.execute("SELECT DISTINCT module FROM workflows ORDER BY module").fetchall()]
|
|
770
|
+
print("未找到模块 '%s'。现有: %s" % (q, ', '.join(mods)))
|
|
771
|
+
return
|
|
772
|
+
print('=== %s 业务操作链 (%d 步) ===' % (q, len(rows)))
|
|
773
|
+
for r in rows:
|
|
774
|
+
print(' %d. [%s] %s — %s' % (r[0], r[1] or '?', r[2] or '', r[3] or ''))
|
|
775
|
+
return
|
|
776
|
+
# JSON 索引路径 (CodeGraph)
|
|
777
|
+
from common.graph_traverse import CodeGraph
|
|
778
|
+
from common.paths import DATA_INDEX_DIR
|
|
779
|
+
g = CodeGraph(str(DATA_INDEX_DIR))
|
|
780
|
+
fn = getattr(g, method, None)
|
|
781
|
+
if not fn:
|
|
782
|
+
print('未知图谱方法: %s' % method)
|
|
783
|
+
return
|
|
784
|
+
result = fn(*args) if args else fn()
|
|
785
|
+
print(result or '(无结果)')
|
|
786
|
+
except ImportError as e:
|
|
787
|
+
print('依赖缺失: %s' % e)
|
|
788
|
+
except Exception as e:
|
|
789
|
+
print('图谱查询失败: %s' % e)
|
|
790
|
+
|
|
791
|
+
|
|
557
792
|
if __name__ == '__main__':
|
|
558
793
|
platform = None
|
|
559
794
|
args = sys.argv[1:]
|
|
@@ -575,6 +810,18 @@ if __name__ == '__main__':
|
|
|
575
810
|
search_field(args[1])
|
|
576
811
|
elif args[0] == '--api' and len(args) >= 2:
|
|
577
812
|
search_api(args[1])
|
|
813
|
+
elif args[0] == '--impact' and len(args) >= 2:
|
|
814
|
+
search_impact(args[1])
|
|
815
|
+
elif args[0] == '--context360' and len(args) >= 2:
|
|
816
|
+
search_context360(args[1])
|
|
817
|
+
elif args[0] == '--coverage':
|
|
818
|
+
_graph_cli('coverage_matrix')
|
|
819
|
+
elif args[0] == '--feature' and len(args) >= 2:
|
|
820
|
+
_graph_cli('feature_overview', args[1])
|
|
821
|
+
elif args[0] == '--workflow' and len(args) >= 2:
|
|
822
|
+
_graph_cli('get_workflow', args[1])
|
|
823
|
+
elif args[0] == '--routes' and len(args) >= 2:
|
|
824
|
+
_search_routes(args[1])
|
|
578
825
|
elif args[0] == '--wiki' and len(args) >= 2:
|
|
579
826
|
search_wiki_cli(args[1])
|
|
580
827
|
elif args[0] == '--components':
|