@hupan56/wlkj 2.7.11 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/bin/cli.js +375 -78
  2. package/package.json +29 -29
  3. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +23 -0
  4. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +23 -0
  5. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +1 -0
  6. package/templates/.qoder/learning/eval-history.jsonl +28 -0
  7. package/templates/data/index/wiki-index.json +8 -0
  8. package/templates/qoder/agents/insight-planning.md +1 -1
  9. package/templates/qoder/agents/insight-research.md +28 -15
  10. package/templates/qoder/commands/optional/wl-insight.md +159 -161
  11. package/templates/qoder/commands/optional/wl-report.md +4 -4
  12. package/templates/qoder/commands/optional/wl-status.md +2 -2
  13. package/templates/qoder/commands/wl-code.md +2 -2
  14. package/templates/qoder/commands/wl-design.md +2 -2
  15. package/templates/qoder/commands/wl-init.md +3 -3
  16. package/templates/qoder/commands/wl-prd.md +2 -2
  17. package/templates/qoder/commands/wl-req.md +43 -0
  18. package/templates/qoder/commands/wl-search.md +8 -8
  19. package/templates/qoder/commands/wl-task.md +17 -17
  20. package/templates/qoder/commands/wl-test.md +41 -15
  21. package/templates/qoder/config.yaml +17 -1
  22. package/templates/qoder/hooks/session-start.py +12 -22
  23. package/templates/qoder/nul +4 -0
  24. package/templates/qoder/rules/wl-pipeline.md +22 -48
  25. package/templates/qoder/scripts/README.md +139 -0
  26. package/templates/qoder/scripts/common/autotest_auth.py +109 -0
  27. package/templates/qoder/scripts/common/bootstrap.py +145 -0
  28. package/templates/qoder/scripts/common/check_publish.py +98 -0
  29. package/templates/qoder/scripts/common/cmd_registry.py +112 -0
  30. package/templates/qoder/scripts/common/config.py +187 -0
  31. package/templates/qoder/scripts/common/contract.py +317 -0
  32. package/templates/qoder/scripts/common/developer.py +2 -1
  33. package/templates/qoder/scripts/common/feishu.py +10 -9
  34. package/templates/qoder/scripts/common/guard.py +159 -0
  35. package/templates/qoder/scripts/common/identity.py +121 -2
  36. package/templates/qoder/scripts/common/kg_capabilities.py +182 -0
  37. package/templates/qoder/scripts/common/mcp_base.py +268 -0
  38. package/templates/qoder/scripts/common/paths.py +187 -1
  39. package/templates/qoder/scripts/common/result.py +223 -0
  40. package/templates/qoder/scripts/common/roles.py +60 -0
  41. package/templates/qoder/scripts/common/task_utils.py +21 -9
  42. package/templates/qoder/scripts/common/test_extract.py +115 -0
  43. package/templates/qoder/scripts/kg/__init__.py +11 -0
  44. package/templates/qoder/scripts/kg/build_entity_registry.py +196 -0
  45. package/templates/qoder/scripts/kg/build_relations.py +127 -0
  46. package/templates/qoder/scripts/{build_style_index.py → kg/build_style_index.py} +39 -10
  47. package/templates/qoder/scripts/kg/build_workflows.py +144 -0
  48. package/templates/qoder/scripts/{context_pack.py → kg/context_pack.py} +18 -11
  49. package/templates/qoder/scripts/{enrich_prompt.py → kg/enrich_prompt.py} +232 -226
  50. package/templates/qoder/scripts/{extract_api_params.py → kg/extract.py} +398 -246
  51. package/templates/qoder/scripts/{kg.py → kg/kg.py} +638 -708
  52. package/templates/qoder/scripts/{kg_build.py → kg/kg_build.py} +618 -612
  53. package/templates/qoder/scripts/{kg_build_db.py → kg/kg_build_db.py} +333 -327
  54. package/templates/qoder/scripts/{kg_duckdb.py → kg/kg_duckdb.py} +38 -37
  55. package/templates/qoder/scripts/{kg_incremental.py → kg/kg_incremental.py} +420 -393
  56. package/templates/qoder/scripts/{kg_link_db.py → kg/kg_link_db.py} +230 -224
  57. package/templates/qoder/scripts/{kg_semantic.py → kg/kg_semantic.py} +156 -150
  58. package/templates/qoder/scripts/kg/prefetch.py +359 -0
  59. package/templates/qoder/scripts/{search_index.py → kg/search_index.py} +70 -14
  60. package/templates/qoder/scripts/mcp/__init__.py +11 -0
  61. package/templates/qoder/scripts/{kg_mcp_server.py → mcp/kg_mcp_server.py} +77 -272
  62. package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → mcp/lanhu_stdio_wrapper.py} +125 -119
  63. package/templates/qoder/scripts/{check_mcp.py → mcp/mcp_doctor.py} +515 -298
  64. package/templates/qoder/scripts/{mcp_launcher.py → mcp/mcp_launcher.py} +442 -414
  65. package/templates/qoder/scripts/{mysql_mcp_server.py → mcp/mysql_mcp_server.py} +347 -396
  66. package/templates/qoder/scripts/{zentao_mcp_server.py → mcp/zentao_mcp_server.py} +384 -424
  67. package/templates/qoder/scripts/report/__init__.py +11 -0
  68. package/templates/qoder/scripts/{add_session.py → report/add_session.py} +250 -244
  69. package/templates/qoder/scripts/{archive_prd.py → report/archive_prd.py} +383 -377
  70. package/templates/qoder/scripts/{eval_prd.py → report/eval_prd.py} +73 -11
  71. package/templates/qoder/scripts/{export.py → report/export.py} +63 -0
  72. package/templates/qoder/scripts/{fill_prototype.py → report/fill_prototype.py} +6 -0
  73. package/templates/qoder/scripts/{gen_design_doc.py → report/gen_design_doc.py} +400 -394
  74. package/templates/qoder/scripts/{learn.py → report/learn.py} +152 -146
  75. package/templates/qoder/scripts/{learn_aggregate.py → report/learn_aggregate.py} +207 -201
  76. package/templates/qoder/scripts/{report.py → report/report.py} +287 -281
  77. package/templates/qoder/scripts/report/req.py +222 -0
  78. package/templates/qoder/scripts/report/role.py +33 -0
  79. package/templates/qoder/scripts/{status.py → report/status.py} +634 -628
  80. package/templates/qoder/scripts/setup/__init__.py +11 -0
  81. package/templates/qoder/scripts/setup/carriers.py +662 -0
  82. package/templates/qoder/scripts/{init_doctor.py → setup/init_doctor.py} +63 -26
  83. package/templates/qoder/scripts/{install_qoderwork.py → setup/install_qoderwork.py} +36 -26
  84. package/templates/qoder/scripts/{platform_doctor.py → setup/platform_doctor.py} +265 -259
  85. package/templates/qoder/scripts/{repo_root.py → setup/repo_root.py} +112 -106
  86. package/templates/qoder/scripts/{setup.py → setup/setup.py} +147 -0
  87. package/templates/qoder/scripts/{setup_lanhu.py → setup/setup_lanhu.py} +973 -963
  88. package/templates/qoder/scripts/task/__init__.py +11 -0
  89. package/templates/qoder/scripts/{git_sync.py → task/git_sync.py} +52 -31
  90. package/templates/qoder/scripts/{syncgate.py → task/syncgate.py} +6 -0
  91. package/templates/qoder/scripts/task/task.py +221 -0
  92. package/templates/qoder/scripts/task/task_lifecycle.py +596 -0
  93. package/templates/qoder/scripts/task/task_query.py +161 -0
  94. package/templates/qoder/scripts/task/task_relations.py +424 -0
  95. package/templates/qoder/scripts/{team_sync.py → task/team_sync.py} +93 -20
  96. package/templates/qoder/scripts/test/__init__.py +11 -0
  97. package/templates/qoder/scripts/{autotest.py → test/autotest.py} +1174 -1751
  98. package/templates/qoder/scripts/{autotest_batch.py → test/autotest_batch.py} +242 -224
  99. package/templates/qoder/scripts/test/autotest_data.py +675 -0
  100. package/templates/qoder/scripts/{autotest_run.py → test/autotest_run.py} +309 -297
  101. package/templates/qoder/scripts/{benchmark.py → test/benchmark.py} +6 -0
  102. package/templates/qoder/scripts/{kg_auto_login.py → test/kg_auto_login.py} +202 -196
  103. package/templates/qoder/scripts/{kg_test_runner.py → test/kg_test_runner.py} +7 -1
  104. package/templates/qoder/scripts/{page_probe.py → test/page_probe.py} +465 -459
  105. package/templates/qoder/scripts/wlkj.py +116 -0
  106. package/templates/qoder/settings.json +1 -10
  107. package/templates/qoder/skills/design-import/SKILL.md +226 -226
  108. package/templates/qoder/skills/design-review/SKILL.md +82 -82
  109. package/templates/qoder/skills/prd-generator/SKILL.md +26 -16
  110. package/templates/qoder/skills/prd-review/SKILL.md +5 -5
  111. package/templates/qoder/skills/prototype-generator/SKILL.md +256 -256
  112. package/templates/qoder/skills/spec-coder/SKILL.md +4 -4
  113. package/templates/qoder/skills/spec-generator/SKILL.md +4 -4
  114. package/templates/qoder/skills/test-generator/SKILL.md +5 -5
  115. package/templates/qoder/skills/wl-code/SKILL.md +4 -4
  116. package/templates/qoder/skills/wl-commit/SKILL.md +4 -4
  117. package/templates/qoder/skills/wl-design/SKILL.md +3 -3
  118. package/templates/qoder/skills/wl-init/SKILL.md +8 -8
  119. package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
  120. package/templates/qoder/skills/wl-prd-full/SKILL.md +6 -6
  121. package/templates/qoder/skills/wl-prd-quick/SKILL.md +6 -6
  122. package/templates/qoder/skills/wl-prd-review/SKILL.md +4 -4
  123. package/templates/qoder/skills/wl-report/SKILL.md +7 -7
  124. package/templates/qoder/skills/wl-search/SKILL.md +13 -13
  125. package/templates/qoder/skills/wl-spec/SKILL.md +5 -5
  126. package/templates/qoder/skills/wl-status/SKILL.md +5 -5
  127. package/templates/qoder/skills/wl-task/SKILL.md +6 -6
  128. package/templates/qoder/skills/wl-test/SKILL.md +102 -39
  129. package/templates/root/AGENTS.md +39 -40
  130. package/templates/qoder/hooks/inject-workflow-state.py +0 -169
  131. package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
  132. package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
  133. package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
  134. package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
  135. package/templates/qoder/scripts/check_carriers.py +0 -238
  136. package/templates/qoder/scripts/check_mcp_launch.py +0 -183
  137. package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
  138. package/templates/qoder/scripts/collect_prds.py +0 -31
  139. package/templates/qoder/scripts/common/mentions.py +0 -134
  140. package/templates/qoder/scripts/common/utf8.py +0 -38
  141. package/templates/qoder/scripts/extract_routes.py +0 -54
  142. package/templates/qoder/scripts/extract_routes_tree.py +0 -78
  143. package/templates/qoder/scripts/handoff.py +0 -22
  144. package/templates/qoder/scripts/init_developer.py +0 -76
  145. package/templates/qoder/scripts/parse_prds.py +0 -33
  146. package/templates/qoder/scripts/role.py +0 -51
  147. package/templates/qoder/scripts/sync_carriers.py +0 -259
  148. package/templates/qoder/scripts/task.py +0 -1261
  149. package/templates/qoder/scripts/workspace_init.py +0 -102
  150. package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
  151. package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
  152. /package/templates/qoder/scripts/{secure-ls.js → test/secure-ls.js} +0 -0
@@ -1,226 +1,232 @@
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()
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
4
+ import os as _o, sys as _s
5
+ _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
6
+ if _cp not in _s.path: _s.path.insert(0, _cp)
7
+ from bootstrap import setup; setup()
8
+
9
+ """enrich_prompt.py - 极速提示词润色 (用户提问后第 0 )
10
+
11
+ 目标: 在用户提问后, <10ms 内判断这句话命中哪些业务关键词,
12
+ 输出一个「润色后的提示词」给用户确认, 再走原命令。
13
+
14
+ 设计:
15
+ - 纯内存查表 (不跑重检索 context_pack, 那个留给确认后)
16
+ - 中文分词复用 common/terms.py expand_chinese_query
17
+ - 命中 keyword-index / api-index / prd-index 的键
18
+ - 命中 0 个 = 非业务消息 (谢谢/继续), 直接放行不润色
19
+
20
+ 用法:
21
+ python .qoder/scripts/enrich_prompt.py "保险批改那个异常统计页加个审批按钮"
22
+ python .qoder/scripts/enrich_prompt.py "保险批改..." --json # 机器可读
23
+
24
+ 输出 (默认人类可读):
25
+ ── 极速润色 ──
26
+ 业务词: 保险→insurance, 批改→amend, 异常→abnormal...
27
+ 相关代码符号: 108 (abnormalovertime, approval-cell...)
28
+ 相关API: 5 个
29
+ 历史 PRD: 2
30
+ ──────────────
31
+ 将以上下文继续。回复"改:xxx"调整, 任意其它内容=接受。
32
+ """
33
+ import os, sys, json, time
34
+
35
+ if sys.platform == 'win32':
36
+ try:
37
+ sys.stdout.reconfigure(encoding='utf-8')
38
+ except Exception:
39
+ pass
40
+
41
+ BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
42
+ sys.path.insert(0, os.path.join(BASE, '.qoder', 'scripts'))
43
+
44
+ IDX_DIR = os.path.join(BASE, 'data', 'index')
45
+ # 缓存: 同一进程内只加载一次索引
46
+ _CACHE = {}
47
+
48
+
49
+ def _load(name):
50
+ """加载索引 (优先 DuckDB, 回退 JSON)。复用 search_index.load_index 的 fallback 逻辑。"""
51
+ if name in _CACHE:
52
+ return _CACHE[name]
53
+ try:
54
+ from search_index import load_index
55
+ d = load_index(name) or {}
56
+ except ImportError:
57
+ # search_index 不可用时, 直接读 JSON (老逻辑)
58
+ d = {}
59
+ path = os.path.join(IDX_DIR, name)
60
+ if os.path.isfile(path):
61
+ try:
62
+ for enc in ('utf-8', 'gbk'):
63
+ try:
64
+ with open(path, encoding=enc) as f:
65
+ d = json.load(f)
66
+ break
67
+ except (OSError, UnicodeDecodeError):
68
+ continue
69
+ except Exception:
70
+ pass
71
+ _CACHE[name] = d
72
+ return d
73
+
74
+
75
+ # 短而无意义的英文 token 黑名单 (避免子串误命中 'cs'/'fl'/'in' 这类噪声)
76
+ # 提到模块级: _match_keys code_total 共用一份
77
+ NOISE_KEYS = {
78
+ 'app', 'api', 'all', 'add', 'age', 'and', 'are', 'ars', 'ase', 'ask',
79
+ 'ate', 'aud', 'bar', 'big', 'btn', 'can', 'car', 'cas', 'cat', 'code',
80
+ 'con', 'cor', 'cs', 'cur', 'dae', 'dat', 'day', 'del', 'dep', 'des',
81
+ 'det', 'dim', 'div', 'doc', 'dos', 'ear', 'ecs', 'ema', 'end', 'ent',
82
+ 'err', 'eva', 'exe', 'exp', 'ext', 'fil', 'fix', 'fl', 'for', 'ge',
83
+ 'gen', 'get', 'has', 'her', 'his', 'ics', 'idx', 'imp', 'int', 'inv',
84
+ 'io', 'is', 'iso', 'ite', 'its', 'key', 'lab', 'lan', 'lat', 'let',
85
+ 'lin', 'lis', 'log', 'ma', 'map', 'max', 'min', 'mod', 'msg', 'new',
86
+ 'nil', 'not', 'now', 'num', 'obj', 'off', 'one', 'opt', 'ord', 'out',
87
+ 'ove', 'own', 'pag', 'par', 'pas', 'pay', 'pen', 'per', 'pos', 'pre',
88
+ 'pro', 'put', 'que', 'rec', 'red', 'ref', 'reg', 'rep', 'res', 'ret',
89
+ 'rev', 'row', 'run', 'sa', 'sel', 'set', 'sev', 'sie', 'sit', 'sor',
90
+ 'src', 'sta', 'sub', 'sum', 'sys', 'tab', 'tar', 'tes', 'tex', 'tim',
91
+ 'tmp', 'tok', 'tom', 'top', 'tot', 'tru', 'try', 'typ', 'udp', 'uni',
92
+ 'upd', 'url', 'use', 'usr', 'val', 'ver', 'vid', 'vio', 'vis', 'voi',
93
+ 'was', 'way', 'who', 'why', 'win', 'wri', 'xml', 'you', 'your', 'zone',
94
+ }
95
+
96
+
97
+ def _match_keys(query_lower, keys, max_hits=15, min_len=3):
98
+ """在 keys 里找被 query 包含的 (子串匹配), 返回命中列表 + 总数。
99
+ min_len: 短于这个长度的键忽略 (避免 'cs'/'fl'/'in' 这类噪声子串)。"""
100
+ hits = []
101
+ for k in keys:
102
+ kl = k.lower()
103
+ if len(kl) < min_len:
104
+ continue
105
+ if kl in NOISE_KEYS:
106
+ continue
107
+ if kl in query_lower:
108
+ hits.append(k)
109
+ if len(hits) >= max_hits:
110
+ break
111
+ return hits
112
+ for k in keys:
113
+ kl = k.lower()
114
+ if len(kl) < min_len:
115
+ continue
116
+ if kl in NOISE:
117
+ continue
118
+ if kl in query_lower:
119
+ hits.append(k)
120
+ if len(hits) >= max_hits:
121
+ break
122
+ return hits
123
+
124
+
125
+ def enrich(query):
126
+ """润色主函数。返回 dict:
127
+ {
128
+ 'is_business': bool, # 是否业务消息 (有命中才算)
129
+ 'cn_words': [...], # 中文分词出的英文搜索词
130
+ 'code_hits': [...], # 命中的代码符号 (样例)
131
+ 'code_total': int, # 代码命中总数
132
+ 'api_hits': [...], # 命中的 API
133
+ 'prd_hits': [...], # 命中的历史 PRD
134
+ 'elapsed_ms': float, # 耗时
135
+ 'polished': str, # 润色后的提示词 (人类可读)
136
+ }
137
+ """
138
+ t0 = time.perf_counter()
139
+ from common.terms import expand_chinese_query
140
+
141
+ # 1. 中文分词 英文搜索词
142
+ cn_words = list(dict.fromkeys(expand_chinese_query(query))) # 去重保序
143
+
144
+ # 2. 构造匹配串: 原句 + 扩展出的英文词, 一起拿去命中索引
145
+ match_str = query.lower()
146
+ for w in cn_words:
147
+ match_str += ' ' + w.lower()
148
+
149
+ # 3. 命中三类索引
150
+ code_idx = _load('code-keyword.json')
151
+ code_keys = list(code_idx.keys()) if isinstance(code_idx, dict) else []
152
+ code_hits = _match_keys(match_str, code_keys)
153
+
154
+ api_idx = _load('code-api.json')
155
+ api_keys = []
156
+ if isinstance(api_idx, dict):
157
+ api_keys = list(api_idx.keys())
158
+ api_hits = _match_keys(match_str, api_keys)
159
+
160
+ prd_idx = _load('prd-index.json')
161
+ prd_keys = []
162
+ if isinstance(prd_idx, dict):
163
+ prd_keys = list(prd_idx.keys())
164
+ prd_hits = _match_keys(match_str, prd_keys, max_hits=5)
165
+
166
+ # 4. 代码命中总数 (不限 max, 用于展示规模; 用与 _match_keys 一致的过滤)
167
+ code_total = 0
168
+ if code_keys:
169
+ code_total = sum(1 for k in code_keys
170
+ if len(k) >= 3 and k.lower() not in NOISE_KEYS
171
+ and k.lower() in match_str)
172
+
173
+ elapsed_ms = (time.perf_counter() - t0) * 1000
174
+ is_business = bool(code_hits or api_hits or prd_hits or cn_words)
175
+
176
+ # 5. 组装润色后提示词 (人类可读)
177
+ lines = []
178
+ if not is_business:
179
+ lines.append('(非业务消息,无需润色,直接放行)')
180
+ else:
181
+ if cn_words:
182
+ shown = ', '.join(cn_words[:8])
183
+ lines.append('业务词: ' + shown)
184
+ if code_total:
185
+ sample = ', '.join(code_hits[:8])
186
+ more = '' if code_total <= len(code_hits) else ' ...'
187
+ lines.append('相关代码符号: %d 处 (%s%s)' % (code_total, sample, more))
188
+ if api_hits:
189
+ lines.append('相关 API: %d 个 (%s)' % (len(api_hits), ', '.join(api_hits[:5])))
190
+ if prd_hits:
191
+ lines.append('历史 PRD: %s' % ', '.join(prd_hits[:5]))
192
+
193
+ polished = '\n'.join(lines)
194
+
195
+ return {
196
+ 'is_business': is_business,
197
+ 'cn_words': cn_words,
198
+ 'code_hits': code_hits,
199
+ 'code_total': code_total,
200
+ 'api_hits': api_hits,
201
+ 'prd_hits': prd_hits,
202
+ 'elapsed_ms': round(elapsed_ms, 2),
203
+ 'polished': polished,
204
+ }
205
+
206
+
207
+ def main():
208
+ args = sys.argv[1:]
209
+ if not args:
210
+ print(__doc__)
211
+ print('错误: 缺少查询参数。用法: enrich_prompt.py "你的问题"')
212
+ sys.exit(2)
213
+ query = ' '.join(args[:1]) if args[0] in ('--json',) else ' '.join(args)
214
+ # 处理 --json 标志
215
+ as_json = '--json' in args
216
+ query = query.replace('--json', '').strip()
217
+
218
+ result = enrich(query)
219
+ if as_json:
220
+ print(json.dumps(result, ensure_ascii=False))
221
+ return
222
+
223
+ # 人类可读输出
224
+ print('── 极速润色 (%.1fms) ──' % result['elapsed_ms'])
225
+ print(result['polished'])
226
+ if result['is_business']:
227
+ print('──────────────')
228
+ print('将以上下文继续。回复"改:xxx"调整,任意其它内容=接受。')
229
+
230
+
231
+ if __name__ == '__main__':
232
+ main()