@hupan56/wlkj 3.0.0 → 3.1.2

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 (237) hide show
  1. package/bin/cli.js +1435 -1373
  2. package/package.json +1 -1
  3. package/templates/qoder/commands/optional/wl-insight.md +6 -4
  4. package/templates/qoder/commands/optional/wl-report.md +20 -39
  5. package/templates/qoder/commands/optional/wl-status.md +4 -4
  6. package/templates/qoder/commands/wl-code.md +2 -2
  7. package/templates/qoder/commands/wl-design.md +5 -5
  8. package/templates/qoder/commands/wl-init.md +3 -3
  9. package/templates/qoder/commands/wl-prd.md +26 -13
  10. package/templates/qoder/commands/wl-req.md +3 -3
  11. package/templates/qoder/commands/wl-search.md +78 -47
  12. package/templates/qoder/commands/wl-task.md +343 -40
  13. package/templates/qoder/commands/wl-test.md +10 -10
  14. package/templates/qoder/config.yaml +35 -13
  15. package/templates/qoder/hooks/post-tool-use.py +181 -0
  16. package/templates/qoder/hooks/session-start.py +82 -1
  17. package/templates/qoder/hooks/stop-eval.py +207 -0
  18. package/templates/qoder/hooks/user-prompt-submit.py +139 -0
  19. package/templates/qoder/rules/wl-pipeline.md +70 -8
  20. package/templates/qoder/scripts/README.md +78 -115
  21. package/templates/qoder/scripts/capability/__init__.py +26 -0
  22. package/templates/qoder/scripts/capability/__main__.py +72 -0
  23. package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
  24. package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
  25. package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
  26. package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
  27. package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
  28. package/templates/qoder/scripts/capability/caps/context.py +36 -0
  29. package/templates/qoder/scripts/capability/caps/cron.py +50 -0
  30. package/templates/qoder/scripts/capability/caps/identity.py +43 -0
  31. package/templates/qoder/scripts/capability/caps/memory.py +71 -0
  32. package/templates/qoder/scripts/capability/caps/notify.py +41 -0
  33. package/templates/qoder/scripts/capability/caps/present.py +48 -0
  34. package/templates/qoder/scripts/capability/caps/repo.py +31 -0
  35. package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
  36. package/templates/qoder/scripts/capability/chain.py +92 -0
  37. package/templates/qoder/scripts/capability/memory_chain.py +41 -0
  38. package/templates/qoder/scripts/capability/registry.py +246 -0
  39. package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
  40. package/templates/qoder/scripts/capability/smoke_test.py +211 -0
  41. package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
  42. package/templates/qoder/scripts/{report → deployment/setup}/__init__.py +11 -11
  43. package/templates/qoder/scripts/{setup → deployment/setup}/carriers.py +33 -26
  44. package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
  45. package/templates/qoder/scripts/{setup → deployment/setup}/init_doctor.py +40 -16
  46. package/templates/qoder/scripts/{setup → deployment/setup}/install_qoderwork.py +20 -12
  47. package/templates/qoder/scripts/{setup → deployment/setup}/platform_doctor.py +8 -2
  48. package/templates/qoder/scripts/{setup → deployment/setup}/repo_root.py +113 -112
  49. package/templates/qoder/scripts/{setup → deployment/setup}/setup.py +31 -11
  50. package/templates/qoder/scripts/{setup → deployment/setup}/setup_lanhu.py +13 -7
  51. package/templates/qoder/scripts/domain/__init__.py +0 -0
  52. package/templates/qoder/scripts/domain/design/__init__.py +0 -0
  53. package/templates/qoder/scripts/{report → domain/design}/fill_prototype.py +12 -6
  54. package/templates/qoder/scripts/{report → domain/design}/gen_design_doc.py +8 -2
  55. package/templates/qoder/scripts/{setup → domain/kg}/__init__.py +11 -11
  56. package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
  57. package/templates/qoder/scripts/{kg → domain/kg/build}/build_entity_registry.py +7 -2
  58. package/templates/qoder/scripts/{kg → domain/kg/build}/build_relations.py +7 -2
  59. package/templates/qoder/scripts/{kg → domain/kg/build}/build_style_index.py +13 -7
  60. package/templates/qoder/scripts/{kg → domain/kg/build}/build_workflows.py +7 -2
  61. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build.py +76 -9
  62. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build_db.py +7 -2
  63. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_incremental.py +11 -6
  64. package/templates/qoder/scripts/{report → domain/kg/build}/learn_aggregate.py +9 -4
  65. package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
  66. package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
  67. package/templates/qoder/scripts/{common → domain/kg/extract}/test_extract.py +115 -115
  68. package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
  69. package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
  70. package/templates/qoder/scripts/{kg → domain/kg/graph}/kg_link_db.py +7 -2
  71. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
  72. package/templates/qoder/scripts/{kg → domain/kg}/kg.py +263 -30
  73. package/templates/qoder/scripts/{common → domain/kg}/kg_capabilities.py +182 -182
  74. package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
  75. package/templates/qoder/scripts/{kg → domain/kg/search}/context_pack.py +30 -9
  76. package/templates/qoder/scripts/{kg → domain/kg/search}/enrich_prompt.py +10 -4
  77. package/templates/qoder/scripts/{kg → domain/kg/search}/prefetch.py +33 -8
  78. package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
  79. package/templates/qoder/scripts/{kg → domain/kg/search}/search_index.py +143 -77
  80. package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
  81. package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
  82. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  83. package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
  84. package/templates/qoder/scripts/{kg → domain/kg/storage}/kg_duckdb.py +38 -14
  85. package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
  86. package/templates/qoder/scripts/{report → domain/learning}/learn.py +8 -3
  87. package/templates/qoder/scripts/{mcp → domain/report}/__init__.py +11 -11
  88. package/templates/qoder/scripts/{report → domain/report}/add_session.py +9 -3
  89. package/templates/qoder/scripts/{report → domain/report}/export.py +10 -5
  90. package/templates/qoder/scripts/{report → domain/report}/report.py +9 -4
  91. package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
  92. package/templates/qoder/scripts/{report → domain/report}/role.py +10 -4
  93. package/templates/qoder/scripts/{report → domain/report}/status.py +112 -9
  94. package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
  95. package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
  96. package/templates/qoder/scripts/{report → domain/requirement}/archive_prd.py +8 -2
  97. package/templates/qoder/scripts/{report → domain/requirement}/req.py +228 -222
  98. package/templates/qoder/scripts/{kg → domain/task}/__init__.py +11 -11
  99. package/templates/qoder/scripts/{task → domain/task}/git_sync.py +40 -21
  100. package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
  101. package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
  102. package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
  103. package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
  104. package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
  105. package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
  106. package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
  107. package/templates/qoder/scripts/foundation/__init__.py +0 -0
  108. package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
  109. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  110. package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
  111. package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
  112. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
  113. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  114. package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
  115. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  116. package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
  117. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
  118. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  119. package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
  120. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
  121. package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
  122. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
  123. package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
  124. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  125. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  126. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +2 -2
  127. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  128. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  129. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  130. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  131. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  132. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  133. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  134. package/templates/qoder/scripts/{common → foundation/protocol}/mcp_base.py +1 -1
  135. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  136. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  137. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  138. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  139. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  140. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  141. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  142. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  143. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  144. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  145. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  146. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  147. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  148. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  149. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  150. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  151. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  152. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  153. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  154. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  155. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  156. package/templates/qoder/scripts/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
  157. package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
  158. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  159. package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
  160. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  161. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  162. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  163. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  164. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  165. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  166. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  167. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  168. package/templates/qoder/scripts/validation/__init__.py +0 -0
  169. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  170. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  171. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  172. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  173. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  174. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  175. package/templates/qoder/scripts/{report → validation/metrics}/eval_prd.py +17 -10
  176. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  177. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  178. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  179. package/templates/qoder/scripts/{test → validation/test}/autotest.py +64 -4
  180. package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
  181. package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
  182. package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
  183. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  184. package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
  185. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  186. package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
  187. package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
  188. package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
  189. package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
  190. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  191. package/templates/qoder/settings.json +30 -0
  192. package/templates/qoder/skills/design-import/SKILL.md +22 -13
  193. package/templates/qoder/skills/design-review/SKILL.md +19 -10
  194. package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
  195. package/templates/qoder/skills/prd-review/SKILL.md +6 -4
  196. package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
  197. package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
  198. package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
  199. package/templates/qoder/skills/test-generator/SKILL.md +7 -5
  200. package/templates/qoder/skills/wl-code/SKILL.md +23 -11
  201. package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
  202. package/templates/qoder/skills/wl-design/SKILL.md +5 -3
  203. package/templates/qoder/skills/wl-init/SKILL.md +6 -6
  204. package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
  205. package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
  206. package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
  207. package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
  208. package/templates/qoder/skills/wl-report/SKILL.md +25 -23
  209. package/templates/qoder/skills/wl-search/SKILL.md +149 -78
  210. package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
  211. package/templates/qoder/skills/wl-status/SKILL.md +48 -16
  212. package/templates/qoder/skills/wl-task/SKILL.md +92 -10
  213. package/templates/qoder/skills/wl-test/SKILL.md +75 -29
  214. package/templates/qoder/templates/prd-full-template.md +7 -0
  215. package/templates/root/AGENTS.md +275 -236
  216. package/templates/root/requirements.txt +3 -0
  217. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  218. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  219. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
  220. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
  221. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
  222. package/templates/.qoder/learning/eval-history.jsonl +0 -28
  223. package/templates/data/index/wiki-index.json +0 -8
  224. package/templates/qoder/nul +0 -4
  225. package/templates/qoder/scripts/kg/extract.py +0 -398
  226. package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
  227. package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
  228. package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
  229. package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
  230. package/templates/qoder/scripts/task/__init__.py +0 -11
  231. package/templates/qoder/scripts/test/__init__.py +0 -11
  232. package/templates/qoder/scripts/wlkj.py +0 -116
  233. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  234. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  235. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  236. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  237. /package/templates/qoder/scripts/{test → validation/test}/secure-ls.js +0 -0
@@ -2,7 +2,12 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
4
4
  import os as _o, sys as _s
5
- _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
5
+ _f = _o.path.abspath(__file__)
6
+ for _ in range(10):
7
+ _f = _o.path.dirname(_f)
8
+ _cp = _o.path.join(_f, 'foundation')
9
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
10
+ break
6
11
  if _cp not in _s.path: _s.path.insert(0, _cp)
7
12
  from bootstrap import setup; setup()
8
13
 
@@ -38,44 +43,100 @@ try:
38
43
  except Exception:
39
44
  pass
40
45
 
41
- # ── 配置 (从环境变量, mcp.json env 注入) ──────────────────────────────
42
- MYSQL_HOST = os.environ.get('MYSQL_HOST', '10.54.6.9')
43
- MYSQL_PORT = int(os.environ.get('MYSQL_PORT', '3306'))
44
- MYSQL_USER = os.environ.get('MYSQL_USER', 'fywl_ics_test')
45
- MYSQL_PASSWORD = os.environ.get('MYSQL_PASSWORD', 'Fywl_ics_test56476')
46
- PING_HOST = MYSQL_HOST # 连通性校验目标
46
+ # ── 配置 (v3.x 多环境: 全部环境从 MYSQL_ENVIRONMENTS JSON 读, 运行时按 env ) ──
47
+ # launcher 把 config.yaml 的 mysql.environments 整体序列化成 JSON 注入这里。
48
+ # server 启动后不再锁定单一连接, 而是按工具调用的 env 参数取对应环境建连。
47
49
  PING_TIMEOUT = 3 # 秒
48
50
 
49
- # ── 环境可信度声明 (随每次工具返回, AI 不可能"忘记"边界) ──────────────
50
- # QAS 测试环境: 表结构/字段/枚举取值范围 = 生产一致 (可信);
51
- # 数据行/计数分布 = 测试数据, 不代表线上 (不可信)
52
- ENV_NAME = os.environ.get('MYSQL_ENV_NAME', 'QAS 测试环境')
53
- ENV_NOTICE_SCHEMA = (
54
- '\n[环境: %s] 表结构/字段/枚举定义与生产一致,可作为字段规格的真源。' % ENV_NAME
55
- )
56
- ENV_NOTICE_DATA = (
57
- '\n⚠️ [环境: %s] 以下为测试数据样本,不代表线上真实分布。'
58
- '字段取值范围可信,但具体数值/计数仅测试环境参考。' % ENV_NAME
59
- )
51
+
52
+ def _load_environments():
53
+ """解析全部环境配置。返回 dict[env_key -> {host,port,user,password,env_name}]
54
+ 数据源优先级: MYSQL_ENVIRONMENTS (JSON) > 旧扁平 MYSQL_HOST/... (包成单环境)
55
+ 解析结果缓存到模块级 _envs。"""
56
+ global _envs
57
+ if _envs is not None:
58
+ return _envs
59
+ _envs = {}
60
+ raw = os.environ.get('MYSQL_ENVIRONMENTS', '')
61
+ if raw:
62
+ try:
63
+ data = json.loads(raw)
64
+ if isinstance(data, dict):
65
+ for k, v in data.items():
66
+ if isinstance(v, dict) and v.get('host'):
67
+ _envs[k] = v
68
+ except (json.JSONDecodeError, TypeError):
69
+ pass
70
+ # 兼容旧扁平 env (launcher 没注入 JSON 时, 直接从 MYSQL_HOST 等读)
71
+ if not _envs:
72
+ h = os.environ.get('MYSQL_HOST', '')
73
+ if h:
74
+ _envs = {'test': {
75
+ 'host': h,
76
+ 'port': int(os.environ.get('MYSQL_PORT', '3306')),
77
+ 'user': os.environ.get('MYSQL_USER', ''),
78
+ 'password': os.environ.get('MYSQL_PASSWORD', ''),
79
+ 'env_name': os.environ.get('MYSQL_ENV_NAME', h),
80
+ }}
81
+ return _envs
82
+
83
+
84
+ def _default_env():
85
+ """默认环境 key。优先 MYSQL_DEFAULT_ENV, 否则取第一个环境。"""
86
+ d = os.environ.get('MYSQL_DEFAULT_ENV', '')
87
+ if d and d in _load_environments():
88
+ return d
89
+ envs = _load_environments()
90
+ return next(iter(envs), 'test') if envs else 'test'
91
+
92
+
93
+ def _env_config(env_key):
94
+ """取某环境的配置 dict。不存在返回 None。"""
95
+ return _load_environments().get(env_key)
96
+
97
+
98
+ def _env_notice(env_key, kind='data'):
99
+ """生成当前环境可信度声明 (随查询结果返回, AI 不可能"忘记"边界)。
100
+ kind='schema' = 表结构 (可信); kind='data' = 数据行 (测试/生产语义不同)。"""
101
+ cfg = _env_config(env_key) or {}
102
+ name = cfg.get('env_name', env_key)
103
+ is_prod = 'prod' in str(env_key).lower() or '生产' in name
104
+ if kind == 'schema':
105
+ if is_prod:
106
+ return '\n🔴 [环境: %s] 表结构/字段/枚举定义 (生产环境, 仅核对用)。' % name
107
+ return '\n[环境: %s] 表结构/字段/枚举定义与生产一致,可作为字段规格的真源。' % name
108
+ # data
109
+ if is_prod:
110
+ return ('\n🔴⚠️ [环境: %s] 以下为生产真实数据! 仅核对字段含义/格式, '
111
+ '禁止作为业务结论外传。' % name)
112
+ return ('\n⚠️ [环境: %s] 以下为测试数据样本,不代表线上真实分布。'
113
+ '字段取值范围可信,但具体数值/计数仅测试环境参考。' % name)
114
+
115
+
116
+ _envs = None # 缓存: 解析一次
117
+
60
118
 
61
119
  # stdout 重定向 + 协议层由 BaseMCPServer 接管 (v3.0 重构)
62
120
 
63
121
 
64
122
  # ── 安全: 内网连通性校验 ──────────────────────────────────────────────
65
- def check_intranet():
123
+ def check_intranet(env_key=None):
66
124
  """TCP 连通性校验 (比 ICMP ping 更可靠, 不依赖管理员权限)。
67
- 3306 端口, 通=内网可达, 不通=提示连内网。
68
- 返回 (ok: bool, msg: str)。
69
- """
125
+ 连目标环境 3306 端口, 通=内网可达, 不通=提示连内网。
126
+ env_key=None 时用默认环境。返回 (ok: bool, msg: str)。"""
127
+ env_key = env_key or _default_env()
128
+ cfg = _env_config(env_key)
129
+ if not cfg:
130
+ return False, '未知环境 "%s",可选: %s' % (env_key, list(_load_environments()))
131
+ host, port = cfg.get('host', ''), int(cfg.get('port', 3306))
70
132
  try:
71
- with socket.create_connection((MYSQL_HOST, MYSQL_PORT), timeout=PING_TIMEOUT):
133
+ with socket.create_connection((host, port), timeout=PING_TIMEOUT):
72
134
  return True, ''
73
135
  except (socket.timeout, OSError) as e:
74
136
  return False, (
75
- '无法连接内网 MySQL (%s:%s): %s\n'
76
- '⚠️ 请确认你已连接公司内网 (VPN/办公网), 然后重试。'
77
- 'MySQL MCP 仅在内网可用。'
78
- ) % (MYSQL_HOST, MYSQL_PORT, str(e)[:80])
137
+ '无法连接 MySQL [%s] (%s:%s): %s\n'
138
+ '⚠️ 请确认你已连接公司内网 (VPN/办公网) 且该环境地址可达, 然后重试。'
139
+ ) % (env_key, host, port, str(e)[:80])
79
140
 
80
141
 
81
142
  # ── 安全: SQL 只读白名单 ──────────────────────────────────────────────
@@ -119,33 +180,38 @@ def inject_limit(sql, max_rows=200):
119
180
  return s + ' LIMIT %d' % max_rows
120
181
 
121
182
 
122
- # ── MySQL 连接 (惰性) ─────────────────────────────────────────────────
123
- _conn = None
183
+ # ── MySQL 连接 (惰性, 多环境连接池) ────────────────────────────────────
184
+ # 每个环境一个连接, 按 env_key 缓存。运行时切库 = 取不同 env_key 的连接。
185
+ _conns = {} # env_key -> connection
124
186
 
125
187
 
126
- def _get_conn():
127
- """惰性建连 (首次查询时才连)"""
128
- global _conn
129
- if _conn is not None:
130
- return _conn
188
+ def _get_conn(env_key):
189
+ """惰性建连 (首次查某环境时才连)。连接按 env_key 缓存复用。"""
190
+ if env_key in _conns:
191
+ return _conns[env_key]
192
+ cfg = _env_config(env_key)
193
+ if not cfg:
194
+ raise ValueError('未知环境 "%s",可选: %s' % (env_key, list(_load_environments())))
131
195
  # 用最普及的 pymysql, 纯 Python, 无编译依赖
132
196
  import pymysql
133
- _conn = pymysql.connect(
134
- host=MYSQL_HOST, port=MYSQL_PORT,
135
- user=MYSQL_USER, password=MYSQL_PASSWORD,
197
+ _conns[env_key] = pymysql.connect(
198
+ host=cfg.get('host', ''), port=int(cfg.get('port', 3306)),
199
+ user=cfg.get('user', ''), password=cfg.get('password', ''),
136
200
  charset='utf8mb4',
137
201
  cursorclass=pymysql.cursors.DictCursor,
138
202
  connect_timeout=5, read_timeout=10,
139
203
  )
140
- return _conn
204
+ return _conns[env_key]
141
205
 
142
206
 
143
- def _execute_query(sql, db=None, max_rows=200):
144
- """执行只读查询, 返回 (columns, rows)。失败抛异常。"""
207
+ def _execute_query(sql, db=None, max_rows=200, env_key=None):
208
+ """执行只读查询, 返回 (columns, rows)。失败抛异常。
209
+ env_key 指定走哪个环境 (默认 default_env)。"""
145
210
  ok, reason = validate_readonly(sql)
146
211
  if not ok:
147
212
  raise ValueError('SQL 被拒: ' + reason)
148
- conn = _get_conn()
213
+ env_key = env_key or _default_env()
214
+ conn = _get_conn(env_key)
149
215
  if db:
150
216
  conn.select_db(db)
151
217
  cur = conn.cursor()
@@ -159,22 +225,34 @@ def _execute_query(sql, db=None, max_rows=200):
159
225
 
160
226
 
161
227
  # ── MCP 工具定义 ──────────────────────────────────────────────────────
228
+ # 所有查询工具都带可选 env 参数, 取 config.yaml mysql.environments 的 key
229
+ # (如 test/pre/prod)。不填用 default_env。调用方 (AI) 应先问用户查哪个环境。
230
+ _ENV_PROP = {"type": "string", "description":
231
+ "环境标签 (config.yaml mysql.environments 的 key, 如 test/pre/prod)。"
232
+ "不填用默认环境。先问用户查哪个环境再传。"}
233
+
162
234
  TOOLS = [
235
+ {
236
+ "name": "list_envs",
237
+ "description": "列出所有可用的数据库环境 (config.yaml mysql.environments)。AI 查库前先调用此工具, 把可选环境列给用户选。例: list_envs()",
238
+ "inputSchema": {"type": "object", "properties": {}},
239
+ },
163
240
  {
164
241
  "name": "query_schema",
165
- "description": "查表结构: 列名/类型/注释/是否可空/主键。用于写 PRD/spec 时核对真实字段。例: query_schema(table='t_quality_case') 或 query_schema(table='t_quality_case', db='fywl_ics')",
242
+ "description": "查表结构: 列名/类型/注释/是否可空/主键。用于写 PRD/spec 时核对真实字段。例: query_schema(table='t_quality_case', env='test')",
166
243
  "inputSchema": {
167
244
  "type": "object",
168
245
  "properties": {
169
246
  "table": {"type": "string", "description": "表名 (如 t_quality_case)"},
170
247
  "db": {"type": "string", "description": "库名 (可选, 不填用默认库)"},
248
+ "env": _ENV_PROP,
171
249
  },
172
250
  "required": ["table"],
173
251
  },
174
252
  },
175
253
  {
176
254
  "name": "query_data",
177
- "description": "查数据: 按条件查表数据, 自动 LIMIT 200 行。[QAS测试环境] 数据行是测试数据, 不代表线上真实分布, 仅参考字段含义/格式。例: query_data(table='t_quality_case', columns='id,case_status', where='create_time > \"2026-06-01\"', limit=10)",
255
+ "description": "查数据: 按条件查表数据, 自动 LIMIT 200 行。数据行语义随环境不同 (测试=样本, 生产=真实数据)。例: query_data(table='t_quality_case', columns='id,case_status', env='test', limit=10)",
178
256
  "inputSchema": {
179
257
  "type": "object",
180
258
  "properties": {
@@ -183,13 +261,14 @@ TOOLS = [
183
261
  "where": {"type": "string", "description": "WHERE 条件 (不带 WHERE 关键字)"},
184
262
  "db": {"type": "string", "description": "库名 (可选)"},
185
263
  "limit": {"type": "integer", "description": "行数上限 (默认200, 最大200)"},
264
+ "env": _ENV_PROP,
186
265
  },
187
266
  "required": ["table"],
188
267
  },
189
268
  },
190
269
  {
191
270
  "name": "query_distinct",
192
- "description": "查枚举值: 某字段的去重值。[QAS测试环境] 取值范围可信(与生产一致), 计数不可信(测试分布)。用于确认状态/类型字段有哪些取值。例: query_distinct(table='t_quality_case', column='case_status')",
271
+ "description": "查枚举值: 某字段的去重值。取值范围可信(各环境一致), 计数语义随环境不同。例: query_distinct(table='t_quality_case', column='case_status', env='test')",
193
272
  "inputSchema": {
194
273
  "type": "object",
195
274
  "properties": {
@@ -197,23 +276,25 @@ TOOLS = [
197
276
  "column": {"type": "string", "description": "要查枚举的列名"},
198
277
  "db": {"type": "string", "description": "库名 (可选)"},
199
278
  "limit": {"type": "integer", "description": "返回枚举值上限 (默认50)"},
279
+ "env": _ENV_PROP,
200
280
  },
201
281
  "required": ["table", "column"],
202
282
  },
203
283
  },
204
284
  {
205
285
  "name": "list_databases",
206
- "description": "列出所有可见的数据库。用于发现有哪些库可查。例: list_databases()",
207
- "inputSchema": {"type": "object", "properties": {}},
286
+ "description": "列出某环境所有可见的数据库。用于发现有哪些库可查。例: list_databases(env='test')",
287
+ "inputSchema": {"type": "object", "properties": {"env": _ENV_PROP}},
208
288
  },
209
289
  {
210
290
  "name": "list_tables",
211
- "description": "列出某库的表 (支持模糊匹配)。用于发现有哪些表。例: list_tables(db='fywl_ics', pattern='quality%')",
291
+ "description": "列出某环境某库的表 (支持模糊匹配)。用于发现有哪些表。例: list_tables(db='fywl_ics', pattern='quality%', env='test')",
212
292
  "inputSchema": {
213
293
  "type": "object",
214
294
  "properties": {
215
295
  "db": {"type": "string", "description": "库名"},
216
296
  "pattern": {"type": "string", "description": "表名 LIKE 模式 (可选, 如 quality%)"},
297
+ "env": _ENV_PROP,
217
298
  },
218
299
  "required": ["db"],
219
300
  },
@@ -224,15 +305,37 @@ TOOLS = [
224
305
  # ── 工具执行 ──────────────────────────────────────────────────────────
225
306
  def _execute_tool(name, args):
226
307
  args = args or {}
227
- # 内网校验 (每次工具调用都校验, 防 VPN 中途断开)
228
- ok, msg = check_intranet()
308
+
309
+ # list_envs: 不需要连库, 直接返回可选环境 (供 AI 列给用户选)
310
+ if name == 'list_envs':
311
+ envs = _load_environments()
312
+ if not envs:
313
+ return ('未配置任何数据库环境。请在 .qoder/config.yaml 的 mysql.environments '
314
+ '里配置 (参考 mysql.default_env + environments 结构)。')
315
+ default = _default_env()
316
+ lines = ['可选数据库环境 (%d 个, 标 * 为默认):' % len(envs)]
317
+ for k, v in envs.items():
318
+ mark = ' *' if k == default else ''
319
+ lines.append(' %s%s - %s (%s)' % (
320
+ k, mark, v.get('env_name', k), v.get('host', '?')))
321
+ return '\n'.join(lines)
322
+
323
+ # 其它工具: 解析 env 参数, 校验环境存在
324
+ env_key = args.get('env') or _default_env()
325
+ if not _env_config(env_key):
326
+ return '❌ 未知环境 "%s",可选: %s\n(在 config.yaml mysql.environments 里配置)' % (
327
+ env_key, list(_load_environments()))
328
+
329
+ # 内网/连通性校验 (按目标环境, 每次调用都校验, 防 VPN 中途断开)
330
+ ok, msg = check_intranet(env_key)
229
331
  if not ok:
230
332
  return msg
231
333
 
232
334
  if name == 'list_databases':
233
- cols, rows = _execute_query('SHOW DATABASES')
335
+ cols, rows = _execute_query('SHOW DATABASES', env_key=env_key)
234
336
  dbs = [list(r.values())[0] for r in rows]
235
- return '数据库 (%d ):\n %s' % (len(dbs), '\n '.join(dbs))
337
+ return '数据库 (%d 个, 环境=%s):\n %s%s' % (
338
+ len(dbs), env_key, '\n '.join(dbs), _env_notice(env_key, 'schema'))
236
339
 
237
340
  if name == 'list_tables':
238
341
  db = args.get('db', '')
@@ -240,9 +343,11 @@ def _execute_tool(name, args):
240
343
  sql = 'SHOW TABLES'
241
344
  if pattern:
242
345
  sql += ' LIKE "%s"' % pattern.replace('"', '')
243
- cols, rows = _execute_query(sql, db=db)
346
+ cols, rows = _execute_query(sql, db=db, env_key=env_key)
244
347
  tables = [list(r.values())[0] for r in rows]
245
- return '表 (%d 个, 库=%s):\n %s' % (len(tables), db, '\n '.join(tables[:100]))
348
+ return '表 (%d 个, 库=%s, 环境=%s):\n %s%s' % (
349
+ len(tables), db, env_key, '\n '.join(tables[:100]),
350
+ _env_notice(env_key, 'schema'))
246
351
 
247
352
  if name == 'query_schema':
248
353
  table = args.get('table', '').replace('"', '').replace("'", '')
@@ -256,9 +361,9 @@ def _execute_tool(name, args):
256
361
  if db:
257
362
  sql += 'AND table_schema = "%s" ' % db.replace('"', '')
258
363
  sql += 'ORDER BY ordinal_position'
259
- cols, rows = _execute_query(sql)
364
+ cols, rows = _execute_query(sql, env_key=env_key)
260
365
  if not rows:
261
- return '未找到表: %s (检查表名或库名)' % table
366
+ return '未找到表: %s (检查表名或库名, 环境=%s)' % (table, env_key)
262
367
  lines = ['%-20s %-12s %-6s %-6s %s' % (
263
368
  '列名', '类型', '可空', '键', '注释')]
264
369
  for r in rows:
@@ -266,7 +371,7 @@ def _execute_tool(name, args):
266
371
  r.get('column_name', ''), r.get('column_type', '')[:12],
267
372
  r.get('is_nullable', '')[:3], r.get('column_key', ''),
268
373
  (r.get('column_comment', '') or '')[:30]))
269
- return '\n'.join(lines) + ENV_NOTICE_SCHEMA
374
+ return '\n'.join(lines) + _env_notice(env_key, 'schema')
270
375
 
271
376
  if name == 'query_data':
272
377
  table = args.get('table', '').replace('"', '').replace("'", '')
@@ -278,14 +383,14 @@ def _execute_tool(name, args):
278
383
  if where:
279
384
  sql += ' WHERE %s' % where
280
385
  sql = inject_limit(sql, limit)
281
- cols, rows = _execute_query(sql, db=db, max_rows=limit)
386
+ cols, rows = _execute_query(sql, db=db, max_rows=limit, env_key=env_key)
282
387
  if not rows:
283
- return '无数据 (表=%s)' % table
388
+ return '无数据 (表=%s, 环境=%s)' % (table, env_key)
284
389
  lines = [' | '.join(cols)]
285
390
  lines.append('-' * 40)
286
391
  for r in rows[:limit]:
287
392
  lines.append(' | '.join(str(r.get(c, ''))[:30] for c in cols))
288
- return '\n'.join(lines) + ENV_NOTICE_DATA
393
+ return '\n'.join(lines) + _env_notice(env_key, 'data')
289
394
 
290
395
  if name == 'query_distinct':
291
396
  table = args.get('table', '').replace('"', '').replace("'", '')
@@ -296,14 +401,14 @@ def _execute_tool(name, args):
296
401
  'SELECT `%s` AS val, COUNT(*) AS cnt FROM `%s` '
297
402
  'GROUP BY `%s` ORDER BY cnt DESC LIMIT %d'
298
403
  ) % (column, table, column, limit)
299
- cols, rows = _execute_query(sql, db=db, max_rows=limit)
404
+ cols, rows = _execute_query(sql, db=db, max_rows=limit, env_key=env_key)
300
405
  if not rows:
301
- return '无数据 (表=%s 列=%s)' % (table, column)
302
- # 取值范围可信(和生产一致), 但计数是测试环境分布(不可信)
406
+ return '无数据 (表=%s 列=%s, 环境=%s)' % (table, column, env_key)
407
+ # 取值范围可信(各环境一致), 计数语义随环境 (测试=样本/生产=真实)
303
408
  lines = ['枚举值 (表=%s 列=%s, 取值范围可信):' % (table, column)]
304
409
  for r in rows:
305
- lines.append(' %s: %s 次 (计数仅测试环境)' % (r.get('val', ''), r.get('cnt', '')))
306
- return '\n'.join(lines) + ENV_NOTICE_DATA
410
+ lines.append(' %s: %s 次' % (r.get('val', ''), r.get('cnt', '')))
411
+ return '\n'.join(lines) + _env_notice(env_key, 'data')
307
412
 
308
413
  return '未知工具: %s' % name
309
414
 
@@ -315,7 +420,7 @@ def _execute_tool(name, args):
315
420
  def _bootstrap_base():
316
421
  """延迟 import BaseMCPServer (scripts/ 在 sys.path)。"""
317
422
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
318
- from common.mcp_base import BaseMCPServer
423
+ from foundation.protocol.mcp_base import BaseMCPServer
319
424
  return BaseMCPServer
320
425
 
321
426
 
@@ -330,10 +435,19 @@ class MysqlMCPServer(_bootstrap_base()):
330
435
  return _execute_tool(name, args)
331
436
 
332
437
  def on_initialize_hook(self, info):
333
- # 启动时内网校验, 不通塞 warning (与旧版行为一致)
334
- ok, msg = check_intranet()
438
+ # 启动时校验默认环境连通性, 不通塞 warning
439
+ # 附带把可用环境列出来, 方便调用方知道有哪些环境可查。
440
+ envs = _load_environments()
441
+ if not envs:
442
+ info['warning'] = ('未配置任何 mysql 环境。请在 config.yaml 的 '
443
+ 'mysql.environments 里配置。')
444
+ return
445
+ default = _default_env()
446
+ ok, msg = check_intranet(default)
335
447
  if not ok:
336
448
  info['warning'] = msg
449
+ info['mysqlEnvs'] = list(envs.keys())
450
+ info['mysqlDefaultEnv'] = default
337
451
 
338
452
  def on_tool_error(self, e):
339
453
  # mysql 特有: ValueError = SQL 只读校验拒绝, 单独友好提示