@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.
- package/bin/cli.js +1435 -1373
- package/package.json +1 -1
- package/templates/qoder/commands/optional/wl-insight.md +6 -4
- package/templates/qoder/commands/optional/wl-report.md +20 -39
- package/templates/qoder/commands/optional/wl-status.md +4 -4
- package/templates/qoder/commands/wl-code.md +2 -2
- package/templates/qoder/commands/wl-design.md +5 -5
- package/templates/qoder/commands/wl-init.md +3 -3
- package/templates/qoder/commands/wl-prd.md +26 -13
- package/templates/qoder/commands/wl-req.md +3 -3
- package/templates/qoder/commands/wl-search.md +78 -47
- package/templates/qoder/commands/wl-task.md +343 -40
- package/templates/qoder/commands/wl-test.md +10 -10
- package/templates/qoder/config.yaml +35 -13
- package/templates/qoder/hooks/post-tool-use.py +181 -0
- package/templates/qoder/hooks/session-start.py +82 -1
- package/templates/qoder/hooks/stop-eval.py +207 -0
- package/templates/qoder/hooks/user-prompt-submit.py +139 -0
- package/templates/qoder/rules/wl-pipeline.md +70 -8
- package/templates/qoder/scripts/README.md +78 -115
- package/templates/qoder/scripts/capability/__init__.py +26 -0
- package/templates/qoder/scripts/capability/__main__.py +72 -0
- package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
- package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
- package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
- package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
- package/templates/qoder/scripts/capability/caps/context.py +36 -0
- package/templates/qoder/scripts/capability/caps/cron.py +50 -0
- package/templates/qoder/scripts/capability/caps/identity.py +43 -0
- package/templates/qoder/scripts/capability/caps/memory.py +71 -0
- package/templates/qoder/scripts/capability/caps/notify.py +41 -0
- package/templates/qoder/scripts/capability/caps/present.py +48 -0
- package/templates/qoder/scripts/capability/caps/repo.py +31 -0
- package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
- package/templates/qoder/scripts/capability/chain.py +92 -0
- package/templates/qoder/scripts/capability/memory_chain.py +41 -0
- package/templates/qoder/scripts/capability/registry.py +246 -0
- package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
- package/templates/qoder/scripts/capability/smoke_test.py +211 -0
- package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
- package/templates/qoder/scripts/{report → deployment/setup}/__init__.py +11 -11
- package/templates/qoder/scripts/{setup → deployment/setup}/carriers.py +33 -26
- package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
- package/templates/qoder/scripts/{setup → deployment/setup}/init_doctor.py +40 -16
- package/templates/qoder/scripts/{setup → deployment/setup}/install_qoderwork.py +20 -12
- package/templates/qoder/scripts/{setup → deployment/setup}/platform_doctor.py +8 -2
- package/templates/qoder/scripts/{setup → deployment/setup}/repo_root.py +113 -112
- package/templates/qoder/scripts/{setup → deployment/setup}/setup.py +31 -11
- package/templates/qoder/scripts/{setup → deployment/setup}/setup_lanhu.py +13 -7
- package/templates/qoder/scripts/domain/__init__.py +0 -0
- package/templates/qoder/scripts/domain/design/__init__.py +0 -0
- package/templates/qoder/scripts/{report → domain/design}/fill_prototype.py +12 -6
- package/templates/qoder/scripts/{report → domain/design}/gen_design_doc.py +8 -2
- package/templates/qoder/scripts/{setup → domain/kg}/__init__.py +11 -11
- package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_entity_registry.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_relations.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_style_index.py +13 -7
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_workflows.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build.py +76 -9
- package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build_db.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/kg_incremental.py +11 -6
- package/templates/qoder/scripts/{report → domain/kg/build}/learn_aggregate.py +9 -4
- package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
- package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
- package/templates/qoder/scripts/{common → domain/kg/extract}/test_extract.py +115 -115
- package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
- package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/graph}/kg_link_db.py +7 -2
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
- package/templates/qoder/scripts/{kg → domain/kg}/kg.py +263 -30
- package/templates/qoder/scripts/{common → domain/kg}/kg_capabilities.py +182 -182
- package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/search}/context_pack.py +30 -9
- package/templates/qoder/scripts/{kg → domain/kg/search}/enrich_prompt.py +10 -4
- package/templates/qoder/scripts/{kg → domain/kg/search}/prefetch.py +33 -8
- package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
- package/templates/qoder/scripts/{kg → domain/kg/search}/search_index.py +143 -77
- package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
- package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
- package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
- package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/storage}/kg_duckdb.py +38 -14
- package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
- package/templates/qoder/scripts/{report → domain/learning}/learn.py +8 -3
- package/templates/qoder/scripts/{mcp → domain/report}/__init__.py +11 -11
- package/templates/qoder/scripts/{report → domain/report}/add_session.py +9 -3
- package/templates/qoder/scripts/{report → domain/report}/export.py +10 -5
- package/templates/qoder/scripts/{report → domain/report}/report.py +9 -4
- package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
- package/templates/qoder/scripts/{report → domain/report}/role.py +10 -4
- package/templates/qoder/scripts/{report → domain/report}/status.py +112 -9
- package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
- package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
- package/templates/qoder/scripts/{report → domain/requirement}/archive_prd.py +8 -2
- package/templates/qoder/scripts/{report → domain/requirement}/req.py +228 -222
- package/templates/qoder/scripts/{kg → domain/task}/__init__.py +11 -11
- package/templates/qoder/scripts/{task → domain/task}/git_sync.py +40 -21
- package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
- package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
- package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
- package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
- package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
- package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
- package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
- package/templates/qoder/scripts/foundation/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
- package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
- package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
- package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
- package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
- package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
- package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
- package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
- package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
- package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
- package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
- package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
- package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
- package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
- package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +2 -2
- package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
- package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
- package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
- package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
- package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
- package/templates/qoder/scripts/{common → foundation/protocol}/mcp_base.py +1 -1
- package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
- package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
- package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
- package/templates/qoder/scripts/orchestration/__init__.py +0 -0
- package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
- package/templates/qoder/scripts/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/protocol/browser/README.md +207 -0
- package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
- package/templates/qoder/scripts/protocol/browser/config.env +9 -0
- package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
- package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
- package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
- package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
- package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
- package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
- package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
- package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
- package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
- package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
- package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
- package/templates/qoder/scripts/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
- package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
- package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
- package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
- package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
- package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
- package/templates/qoder/scripts/protocol/transports/base.py +87 -0
- package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
- package/templates/qoder/scripts/protocol/transports/http.py +141 -0
- package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
- package/templates/qoder/scripts/run_weekly_update.bat +27 -18
- package/templates/qoder/scripts/run_weekly_update.sh +22 -13
- package/templates/qoder/scripts/validation/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
- package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
- package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
- package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
- package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
- package/templates/qoder/scripts/{report → validation/metrics}/eval_prd.py +17 -10
- package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
- package/templates/qoder/scripts/validation/test/__init__.py +11 -0
- package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
- package/templates/qoder/scripts/{test → validation/test}/autotest.py +64 -4
- package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
- package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
- package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
- package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
- package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
- package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
- package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
- package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
- package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
- package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
- package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
- package/templates/qoder/settings.json +30 -0
- package/templates/qoder/skills/design-import/SKILL.md +22 -13
- package/templates/qoder/skills/design-review/SKILL.md +19 -10
- package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
- package/templates/qoder/skills/prd-review/SKILL.md +6 -4
- package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
- package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
- package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
- package/templates/qoder/skills/test-generator/SKILL.md +7 -5
- package/templates/qoder/skills/wl-code/SKILL.md +23 -11
- package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
- package/templates/qoder/skills/wl-design/SKILL.md +5 -3
- package/templates/qoder/skills/wl-init/SKILL.md +6 -6
- package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
- package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
- package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
- package/templates/qoder/skills/wl-report/SKILL.md +25 -23
- package/templates/qoder/skills/wl-search/SKILL.md +149 -78
- package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
- package/templates/qoder/skills/wl-status/SKILL.md +48 -16
- package/templates/qoder/skills/wl-task/SKILL.md +92 -10
- package/templates/qoder/skills/wl-test/SKILL.md +75 -29
- package/templates/qoder/templates/prd-full-template.md +7 -0
- package/templates/root/AGENTS.md +275 -236
- package/templates/root/requirements.txt +3 -0
- package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
- package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
- package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
- package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
- package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
- package/templates/.qoder/learning/eval-history.jsonl +0 -28
- package/templates/data/index/wiki-index.json +0 -8
- package/templates/qoder/nul +0 -4
- package/templates/qoder/scripts/kg/extract.py +0 -398
- package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
- package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
- package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
- package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
- package/templates/qoder/scripts/task/__init__.py +0 -11
- package/templates/qoder/scripts/test/__init__.py +0 -11
- package/templates/qoder/scripts/wlkj.py +0 -116
- /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
- /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
- /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
|
-
|
|
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
|
-
# ── 配置 (
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
'
|
|
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
|
-
|
|
68
|
-
|
|
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((
|
|
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
|
-
'
|
|
76
|
-
'⚠️ 请确认你已连接公司内网 (VPN/办公网)
|
|
77
|
-
|
|
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
|
-
|
|
183
|
+
# ── MySQL 连接 (惰性, 多环境连接池) ────────────────────────────────────
|
|
184
|
+
# 每个环境一个连接, 按 env_key 缓存。运行时切库 = 取不同 env_key 的连接。
|
|
185
|
+
_conns = {} # env_key -> connection
|
|
124
186
|
|
|
125
187
|
|
|
126
|
-
def _get_conn():
|
|
127
|
-
"""惰性建连 (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
134
|
-
host=
|
|
135
|
-
user=
|
|
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
|
|
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
|
-
|
|
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'
|
|
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
|
|
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": "查枚举值:
|
|
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": "
|
|
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": "
|
|
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
|
-
|
|
228
|
-
|
|
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
|
|
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' % (
|
|
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 (
|
|
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) +
|
|
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) +
|
|
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 次
|
|
306
|
-
return '\n'.join(lines) +
|
|
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
|
|
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
|
-
#
|
|
334
|
-
|
|
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 只读校验拒绝, 单独友好提示
|