@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
@@ -19,6 +19,7 @@ Usage:
19
19
  """
20
20
 
21
21
  import os
22
+ from common.paths import BUILD_CACHE_DIR
22
23
  import sys
23
24
  import json
24
25
  import time
@@ -42,6 +43,14 @@ MIN_FUZZY_LEN = 4
42
43
 
43
44
 
44
45
  def load(filename):
46
+ """Load index from DuckDB (preferred) or JSON fallback."""
47
+ try:
48
+ from search_index import _duckdb_load
49
+ result = _duckdb_load(filename)
50
+ if result is not None:
51
+ return result
52
+ except Exception:
53
+ pass
45
54
  path = os.path.join(INDEX_DIR, filename)
46
55
  if not os.path.isfile(path):
47
56
  return None
@@ -73,17 +82,23 @@ def plat_filter(files, target):
73
82
 
74
83
 
75
84
  def _cache_key(query, platform, page_type):
76
- """生成缓存 key: query|platform|type|索引mtime 指纹。
85
+ """生成缓存 key: query|platform|type|数据源mtime 指纹。
77
86
 
78
- 索引文件没变就命中缓存, 变了自动失效。
87
+ 数据源变了就失效。注意: 数据真正存在 DuckDB(kg.duckdb) 里, JSON 文件
88
+ 大多缺失——所以指纹必须包含 kg.duckdb 的 mtime, 否则 DuckDB 重建/增量
89
+ 更新后, 旧的"空结果"缓存会一直命中(实测 bug: 保险词永久返回 0 命中)。
79
90
  """
80
91
  import hashlib
81
- # 取 3 个主要索引的 mtime 作为指纹
82
92
  mtime_str = ''
83
- for idx_file in ['keyword-index.json', 'api-index.json', 'prd-index.json']:
93
+ # DuckDB (数据真正来源, 必须算)
94
+ duckdb_path = os.path.join(INDEX_DIR, 'kg.duckdb')
95
+ if os.path.isfile(duckdb_path):
96
+ mtime_str += 'duck:%d_' % os.path.getmtime(duckdb_path)
97
+ # ② JSON 索引 (部分数据仍在 JSON, 一起算)
98
+ for idx_file in ['code-keyword.json', 'code-api.json', 'prd-index.json', 'ui-style.json']:
84
99
  p = os.path.join(INDEX_DIR, idx_file)
85
100
  if os.path.isfile(p):
86
- mtime_str += '%d_' % os.path.getmtime(p)
101
+ mtime_str += '%s:%d_' % (idx_file, os.path.getmtime(p))
87
102
  raw = '{}|{}|{}|{}'.format(query, platform or '', page_type or '', mtime_str)
88
103
  return hashlib.md5(raw.encode('utf-8')).hexdigest()[:16]
89
104
 
@@ -142,6 +157,10 @@ def main():
142
157
  return 1
143
158
  query = args[0]
144
159
 
160
+ # 注意: context_pack.py 不加平台硬守卫 —— 它是通用上下文工具,
161
+ # wl-insight(探索)/wl-search 也调用, 那些场景 platform 可选。
162
+ # 平台违规的真正信号点在 fill_prototype.py (出原型必分平台, PRD 链路必经)。
163
+
145
164
  # 结果缓存: 同 query+platform+索引未变 → 直接返回 (470ms → <10ms)
146
165
  cache_key = _cache_key(query, platform, page_type)
147
166
  cached = _try_cache(cache_key)
@@ -177,7 +196,7 @@ def _compute_and_print(query, platform, page_type):
177
196
  print('搜索词扩展: ' + ' '.join(words))
178
197
 
179
198
  # ---- 1. 相关代码文件 ----
180
- ki = load('keyword-index.json') or {}
199
+ ki = load('code-keyword.json') or {}
181
200
  hits = {}
182
201
  for word in words:
183
202
  for kw, files in ki.items():
@@ -195,7 +214,7 @@ def _compute_and_print(query, platform, page_type):
195
214
  print('- (无命中 — 换业务词重试, 或该功能为全新)')
196
215
 
197
216
  # ---- 2. 同类型页面真实示例 ----
198
- si = load('style-index.json') or {}
217
+ si = load('ui-style.json') or {}
199
218
  print('\n## 2. 同类页面示例 (风格锚点, 读 1-2 个)')
200
219
  for proj, pdata in sorted((si.get('projects') or {}).items()):
201
220
  if targets and proj not in targets:
@@ -206,7 +225,8 @@ def _compute_and_print(query, platform, page_type):
206
225
  continue
207
226
  if not page_type and pt == 'unknown':
208
227
  continue
209
- print('- [{}] {}: {}'.format(proj, pt, ' | '.join(fs[:3])))
228
+ fs_files = [item['file'] if isinstance(item, dict) else item for item in fs[:3]]
229
+ print('- [{}] {}: {}'.format(proj, pt, ' | '.join(fs_files)))
210
230
 
211
231
  # ---- 3. 相关字段规格 ----
212
232
  fm = si.get('field_map') or {}
@@ -238,7 +258,7 @@ def _compute_and_print(query, platform, page_type):
238
258
  print('- (无相关 PRD)')
239
259
 
240
260
  # ---- 5. 相关 API ----
241
- apis = load('api-index.json') or {}
261
+ apis = load('code-api.json') or {}
242
262
  ahits = [(ep, f) for ep, f in apis.items()
243
263
  if any(w in ep.lower() or w in f.lower() for w in words if len(w) >= MIN_FUZZY_LEN)]
244
264
  print('\n## 5. 相关 API (取前 8 / 共 {})'.format(len(ahits)))
@@ -249,9 +269,9 @@ def _compute_and_print(query, platform, page_type):
249
269
 
250
270
  # ---- 6. 风格/图标/模板速查 ----
251
271
  print('\n## 6. 风格真源速查')
252
- print('- Web Token: data/index/vben-style-reference.json (primary=hsl(212 100% 45%))')
253
- print('- 看板/大屏: data/index/chart-style-reference.json (大屏深蓝风 monitor / 浅色卡片风 workbench)')
254
- print('- 图标 (禁 emoji): data/index/icon-reference.json (Web=antd 内联SVG, APP=vant 字体)')
272
+ print('- Web Token: data/index/ref-vben-style.json (primary=hsl(214.58 86.34% 59.8%))')
273
+ print('- 看板/大屏: data/index/ref-chart-style.json (大屏深蓝风 monitor / 浅色卡片风 workbench)')
274
+ print('- 图标 (禁 emoji): data/index/ref-icon.json (Web=antd 内联SVG, APP=vant 字体)')
255
275
  print('- 模板: .qoder/templates/prototype-web.html / prototype-app.html')
256
276
  print('- PRD 模板: .qoder/templates/prd-full-template.md / prd-quick-template.md')
257
277
 
@@ -261,7 +281,7 @@ def _compute_and_print(query, platform, page_type):
261
281
  try:
262
282
  from common.repowiki import search_wiki, build_wiki_index
263
283
  from common.terms import get_cn_map_with_auto
264
- wiki_idx_path = os.path.join(INDEX_DIR, 'wiki-index.json')
284
+ wiki_idx_path = os.path.join(BUILD_CACHE_DIR, 'wiki-index.json')
265
285
  if not os.path.isfile(wiki_idx_path):
266
286
  build_wiki_index(code_dir, wiki_idx_path)
267
287
  cn_map = get_cn_map_with_auto()
@@ -293,6 +313,46 @@ def _compute_and_print(query, platform, page_type):
293
313
  for w in wiki_dirs:
294
314
  print(' - ' + w)
295
315
 
316
+ # ── 8. 数据库表结构 (从 kg_db.duckdb, 合并了原来要单独调 MCP 的查询) ──
317
+ try:
318
+ import duckdb as _ddb2
319
+ db_path = os.path.join(INDEX_DIR, 'kg_db.duckdb')
320
+ if os.path.isfile(db_path):
321
+ con = _ddb2.connect(db_path, read_only=True)
322
+ # 关键词匹配表名 (英文词或snake_case, camelCase自动转)
323
+ kw_lower = query.lower()
324
+ # camelCase → snake_case (qualityCase → quality_case)
325
+ import re as _re
326
+ snake = _re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', query).lower()
327
+ tbl_hits = con.execute(
328
+ "SELECT DISTINCT table_name, db FROM db_tables "
329
+ "WHERE lower(table_name) LIKE ? OR lower(table_name) LIKE ? LIMIT 5",
330
+ ['%' + kw_lower + '%', '%' + snake + '%']
331
+ ).fetchall()
332
+ if tbl_hits:
333
+ print('\n## 8. 数据库表结构 (kg_db.duckdb, QAS测试环境)')
334
+ for tn, dbn in tbl_hits[:3]:
335
+ cols = con.execute(
336
+ "SELECT column_name, data_type, column_comment "
337
+ "FROM db_columns WHERE table_name = ? LIMIT 8",
338
+ [tn]
339
+ ).fetchall()
340
+ print(' [{}.{}] {}:'.format(dbn, tn, len(cols)))
341
+ for cn, dt, cc in cols:
342
+ print(' {} ({}) {}'.format(cn, dt, (cc or '')[:25]))
343
+ # entity_table_map: 表对应哪个Entity
344
+ for tn, _ in tbl_hits[:2]:
345
+ ents = con.execute(
346
+ "SELECT DISTINCT entity_class FROM entity_table_map "
347
+ "WHERE table_name = ? LIMIT 3", [tn]
348
+ ).fetchall()
349
+ if ents:
350
+ print(' {} → Entity: {}'.format(
351
+ tn, ', '.join(e[0] for e in ents)))
352
+ con.close()
353
+ except Exception:
354
+ pass # kg_db不存在/被占用 → 跳过, 不影响其它段
355
+
296
356
 
297
357
  if __name__ == '__main__':
298
358
  sys.exit(main())
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """enrich_prompt.py - 极速提示词润色 (用户提问后第 0 步)
4
+
5
+ 目标: 在用户提问后, <10ms 内判断这句话命中哪些业务关键词,
6
+ 输出一个「润色后的提示词」给用户确认, 再走原命令。
7
+
8
+ 设计:
9
+ - 纯内存查表 (不跑重检索 context_pack, 那个留给确认后)
10
+ - 中文分词复用 common/terms.py 的 expand_chinese_query
11
+ - 命中 keyword-index / api-index / prd-index 的键
12
+ - 命中 0 个 = 非业务消息 (谢谢/继续), 直接放行不润色
13
+
14
+ 用法:
15
+ python .qoder/scripts/enrich_prompt.py "保险批改那个异常统计页加个审批按钮"
16
+ python .qoder/scripts/enrich_prompt.py "保险批改..." --json # 机器可读
17
+
18
+ 输出 (默认人类可读):
19
+ ── 极速润色 ──
20
+ 业务词: 保险→insurance, 批改→amend, 异常→abnormal...
21
+ 相关代码符号: 108 处 (abnormalovertime, approval-cell...)
22
+ 相关API: 5 个
23
+ 历史 PRD: 2 篇
24
+ ──────────────
25
+ 将以上下文继续。回复"改:xxx"调整, 任意其它内容=接受。
26
+ """
27
+ import os, sys, json, time
28
+
29
+ if sys.platform == 'win32':
30
+ try:
31
+ sys.stdout.reconfigure(encoding='utf-8')
32
+ except Exception:
33
+ pass
34
+
35
+ BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
36
+ sys.path.insert(0, os.path.join(BASE, '.qoder', 'scripts'))
37
+
38
+ IDX_DIR = os.path.join(BASE, 'data', 'index')
39
+ # 缓存: 同一进程内只加载一次索引
40
+ _CACHE = {}
41
+
42
+
43
+ def _load(name):
44
+ """加载索引 (优先 DuckDB, 回退 JSON)。复用 search_index.load_index 的 fallback 逻辑。"""
45
+ if name in _CACHE:
46
+ return _CACHE[name]
47
+ try:
48
+ from search_index import load_index
49
+ d = load_index(name) or {}
50
+ except ImportError:
51
+ # search_index 不可用时, 直接读 JSON (老逻辑)
52
+ d = {}
53
+ path = os.path.join(IDX_DIR, name)
54
+ if os.path.isfile(path):
55
+ try:
56
+ for enc in ('utf-8', 'gbk'):
57
+ try:
58
+ with open(path, encoding=enc) as f:
59
+ d = json.load(f)
60
+ break
61
+ except (OSError, UnicodeDecodeError):
62
+ continue
63
+ except Exception:
64
+ pass
65
+ _CACHE[name] = d
66
+ return d
67
+
68
+
69
+ # 短而无意义的英文 token 黑名单 (避免子串误命中 'cs'/'fl'/'in' 这类噪声)
70
+ # 提到模块级: _match_keys 和 code_total 共用一份
71
+ NOISE_KEYS = {
72
+ 'app', 'api', 'all', 'add', 'age', 'and', 'are', 'ars', 'ase', 'ask',
73
+ 'ate', 'aud', 'bar', 'big', 'btn', 'can', 'car', 'cas', 'cat', 'code',
74
+ 'con', 'cor', 'cs', 'cur', 'dae', 'dat', 'day', 'del', 'dep', 'des',
75
+ 'det', 'dim', 'div', 'doc', 'dos', 'ear', 'ecs', 'ema', 'end', 'ent',
76
+ 'err', 'eva', 'exe', 'exp', 'ext', 'fil', 'fix', 'fl', 'for', 'ge',
77
+ 'gen', 'get', 'has', 'her', 'his', 'ics', 'idx', 'imp', 'int', 'inv',
78
+ 'io', 'is', 'iso', 'ite', 'its', 'key', 'lab', 'lan', 'lat', 'let',
79
+ 'lin', 'lis', 'log', 'ma', 'map', 'max', 'min', 'mod', 'msg', 'new',
80
+ 'nil', 'not', 'now', 'num', 'obj', 'off', 'one', 'opt', 'ord', 'out',
81
+ 'ove', 'own', 'pag', 'par', 'pas', 'pay', 'pen', 'per', 'pos', 'pre',
82
+ 'pro', 'put', 'que', 'rec', 'red', 'ref', 'reg', 'rep', 'res', 'ret',
83
+ 'rev', 'row', 'run', 'sa', 'sel', 'set', 'sev', 'sie', 'sit', 'sor',
84
+ 'src', 'sta', 'sub', 'sum', 'sys', 'tab', 'tar', 'tes', 'tex', 'tim',
85
+ 'tmp', 'tok', 'tom', 'top', 'tot', 'tru', 'try', 'typ', 'udp', 'uni',
86
+ 'upd', 'url', 'use', 'usr', 'val', 'ver', 'vid', 'vio', 'vis', 'voi',
87
+ 'was', 'way', 'who', 'why', 'win', 'wri', 'xml', 'you', 'your', 'zone',
88
+ }
89
+
90
+
91
+ def _match_keys(query_lower, keys, max_hits=15, min_len=3):
92
+ """在 keys 里找被 query 包含的 (子串匹配), 返回命中列表 + 总数。
93
+ min_len: 短于这个长度的键忽略 (避免 'cs'/'fl'/'in' 这类噪声子串)。"""
94
+ hits = []
95
+ for k in keys:
96
+ kl = k.lower()
97
+ if len(kl) < min_len:
98
+ continue
99
+ if kl in NOISE_KEYS:
100
+ continue
101
+ if kl in query_lower:
102
+ hits.append(k)
103
+ if len(hits) >= max_hits:
104
+ break
105
+ return hits
106
+ for k in keys:
107
+ kl = k.lower()
108
+ if len(kl) < min_len:
109
+ continue
110
+ if kl in NOISE:
111
+ continue
112
+ if kl in query_lower:
113
+ hits.append(k)
114
+ if len(hits) >= max_hits:
115
+ break
116
+ return hits
117
+
118
+
119
+ def enrich(query):
120
+ """润色主函数。返回 dict:
121
+ {
122
+ 'is_business': bool, # 是否业务消息 (有命中才算)
123
+ 'cn_words': [...], # 中文分词出的英文搜索词
124
+ 'code_hits': [...], # 命中的代码符号 (样例)
125
+ 'code_total': int, # 代码命中总数
126
+ 'api_hits': [...], # 命中的 API
127
+ 'prd_hits': [...], # 命中的历史 PRD
128
+ 'elapsed_ms': float, # 耗时
129
+ 'polished': str, # 润色后的提示词 (人类可读)
130
+ }
131
+ """
132
+ t0 = time.perf_counter()
133
+ from common.terms import expand_chinese_query
134
+
135
+ # 1. 中文分词 → 英文搜索词
136
+ cn_words = list(dict.fromkeys(expand_chinese_query(query))) # 去重保序
137
+
138
+ # 2. 构造匹配串: 原句 + 扩展出的英文词, 一起拿去命中索引
139
+ match_str = query.lower()
140
+ for w in cn_words:
141
+ match_str += ' ' + w.lower()
142
+
143
+ # 3. 命中三类索引
144
+ code_idx = _load('code-keyword.json')
145
+ code_keys = list(code_idx.keys()) if isinstance(code_idx, dict) else []
146
+ code_hits = _match_keys(match_str, code_keys)
147
+
148
+ api_idx = _load('code-api.json')
149
+ api_keys = []
150
+ if isinstance(api_idx, dict):
151
+ api_keys = list(api_idx.keys())
152
+ api_hits = _match_keys(match_str, api_keys)
153
+
154
+ prd_idx = _load('prd-index.json')
155
+ prd_keys = []
156
+ if isinstance(prd_idx, dict):
157
+ prd_keys = list(prd_idx.keys())
158
+ prd_hits = _match_keys(match_str, prd_keys, max_hits=5)
159
+
160
+ # 4. 代码命中总数 (不限 max, 用于展示规模; 用与 _match_keys 一致的过滤)
161
+ code_total = 0
162
+ if code_keys:
163
+ code_total = sum(1 for k in code_keys
164
+ if len(k) >= 3 and k.lower() not in NOISE_KEYS
165
+ and k.lower() in match_str)
166
+
167
+ elapsed_ms = (time.perf_counter() - t0) * 1000
168
+ is_business = bool(code_hits or api_hits or prd_hits or cn_words)
169
+
170
+ # 5. 组装润色后提示词 (人类可读)
171
+ lines = []
172
+ if not is_business:
173
+ lines.append('(非业务消息,无需润色,直接放行)')
174
+ else:
175
+ if cn_words:
176
+ shown = ', '.join(cn_words[:8])
177
+ lines.append('业务词: ' + shown)
178
+ if code_total:
179
+ sample = ', '.join(code_hits[:8])
180
+ more = '' if code_total <= len(code_hits) else ' ...'
181
+ lines.append('相关代码符号: %d 处 (%s%s)' % (code_total, sample, more))
182
+ if api_hits:
183
+ lines.append('相关 API: %d 个 (%s)' % (len(api_hits), ', '.join(api_hits[:5])))
184
+ if prd_hits:
185
+ lines.append('历史 PRD: %s' % ', '.join(prd_hits[:5]))
186
+
187
+ polished = '\n'.join(lines)
188
+
189
+ return {
190
+ 'is_business': is_business,
191
+ 'cn_words': cn_words,
192
+ 'code_hits': code_hits,
193
+ 'code_total': code_total,
194
+ 'api_hits': api_hits,
195
+ 'prd_hits': prd_hits,
196
+ 'elapsed_ms': round(elapsed_ms, 2),
197
+ 'polished': polished,
198
+ }
199
+
200
+
201
+ def main():
202
+ args = sys.argv[1:]
203
+ if not args:
204
+ print(__doc__)
205
+ print('错误: 缺少查询参数。用法: enrich_prompt.py "你的问题"')
206
+ sys.exit(2)
207
+ query = ' '.join(args[:1]) if args[0] in ('--json',) else ' '.join(args)
208
+ # 处理 --json 标志
209
+ as_json = '--json' in args
210
+ query = query.replace('--json', '').strip()
211
+
212
+ result = enrich(query)
213
+ if as_json:
214
+ print(json.dumps(result, ensure_ascii=False))
215
+ return
216
+
217
+ # 人类可读输出
218
+ print('── 极速润色 (%.1fms) ──' % result['elapsed_ms'])
219
+ print(result['polished'])
220
+ if result['is_business']:
221
+ print('──────────────')
222
+ print('将以上下文继续。回复"改:xxx"调整,任意其它内容=接受。')
223
+
224
+
225
+ if __name__ == '__main__':
226
+ main()