@hupan56/wlkj 2.7.12 → 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 (253) hide show
  1. package/bin/cli.js +1435 -1107
  2. package/package.json +29 -29
  3. package/templates/qoder/agents/insight-planning.md +1 -1
  4. package/templates/qoder/agents/insight-research.md +28 -15
  5. package/templates/qoder/commands/optional/wl-insight.md +161 -161
  6. package/templates/qoder/commands/optional/wl-report.md +20 -39
  7. package/templates/qoder/commands/optional/wl-status.md +4 -4
  8. package/templates/qoder/commands/wl-code.md +2 -2
  9. package/templates/qoder/commands/wl-design.md +6 -6
  10. package/templates/qoder/commands/wl-init.md +3 -3
  11. package/templates/qoder/commands/wl-prd.md +26 -13
  12. package/templates/qoder/commands/wl-req.md +43 -0
  13. package/templates/qoder/commands/wl-search.md +78 -47
  14. package/templates/qoder/commands/wl-task.md +343 -40
  15. package/templates/qoder/commands/wl-test.md +43 -17
  16. package/templates/qoder/config.yaml +52 -14
  17. package/templates/qoder/hooks/post-tool-use.py +181 -0
  18. package/templates/qoder/hooks/session-start.py +94 -23
  19. package/templates/qoder/hooks/stop-eval.py +207 -0
  20. package/templates/qoder/hooks/user-prompt-submit.py +139 -0
  21. package/templates/qoder/rules/wl-pipeline.md +85 -49
  22. package/templates/qoder/scripts/README.md +102 -0
  23. package/templates/qoder/scripts/capability/__init__.py +26 -0
  24. package/templates/qoder/scripts/capability/__main__.py +72 -0
  25. package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
  26. package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
  27. package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
  28. package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
  29. package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
  30. package/templates/qoder/scripts/capability/caps/context.py +36 -0
  31. package/templates/qoder/scripts/capability/caps/cron.py +50 -0
  32. package/templates/qoder/scripts/capability/caps/identity.py +43 -0
  33. package/templates/qoder/scripts/capability/caps/memory.py +71 -0
  34. package/templates/qoder/scripts/capability/caps/notify.py +41 -0
  35. package/templates/qoder/scripts/capability/caps/present.py +48 -0
  36. package/templates/qoder/scripts/capability/caps/repo.py +31 -0
  37. package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
  38. package/templates/qoder/scripts/capability/chain.py +92 -0
  39. package/templates/qoder/scripts/capability/memory_chain.py +41 -0
  40. package/templates/qoder/scripts/capability/registry.py +246 -0
  41. package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
  42. package/templates/qoder/scripts/capability/smoke_test.py +211 -0
  43. package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
  44. package/templates/qoder/scripts/deployment/setup/__init__.py +11 -0
  45. package/templates/qoder/scripts/deployment/setup/carriers.py +669 -0
  46. package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
  47. package/templates/qoder/scripts/{init_doctor.py → deployment/setup/init_doctor.py} +100 -39
  48. package/templates/qoder/scripts/{install_qoderwork.py → deployment/setup/install_qoderwork.py} +48 -30
  49. package/templates/qoder/scripts/{platform_doctor.py → deployment/setup/platform_doctor.py} +271 -259
  50. package/templates/qoder/scripts/{repo_root.py → deployment/setup/repo_root.py} +9 -2
  51. package/templates/qoder/scripts/{setup.py → deployment/setup/setup.py} +143 -14
  52. package/templates/qoder/scripts/{setup_lanhu.py → deployment/setup/setup_lanhu.py} +979 -963
  53. package/templates/qoder/scripts/domain/design/__init__.py +0 -0
  54. package/templates/qoder/scripts/{fill_prototype.py → domain/design/fill_prototype.py} +17 -5
  55. package/templates/qoder/scripts/{gen_design_doc.py → domain/design/gen_design_doc.py} +406 -394
  56. package/templates/qoder/scripts/domain/kg/__init__.py +11 -0
  57. package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
  58. package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +201 -0
  59. package/templates/qoder/scripts/domain/kg/build/build_relations.py +132 -0
  60. package/templates/qoder/scripts/{build_style_index.py → domain/kg/build/build_style_index.py} +50 -15
  61. package/templates/qoder/scripts/domain/kg/build/build_workflows.py +149 -0
  62. package/templates/qoder/scripts/{kg_build.py → domain/kg/build/kg_build.py} +685 -612
  63. package/templates/qoder/scripts/{kg_build_db.py → domain/kg/build/kg_build_db.py} +338 -327
  64. package/templates/qoder/scripts/{kg_incremental.py → domain/kg/build/kg_incremental.py} +425 -393
  65. package/templates/qoder/scripts/{learn_aggregate.py → domain/kg/build/learn_aggregate.py} +212 -201
  66. package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
  67. package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
  68. package/templates/qoder/scripts/domain/kg/extract/test_extract.py +115 -0
  69. package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
  70. package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
  71. package/templates/qoder/scripts/{kg_link_db.py → domain/kg/graph/kg_link_db.py} +235 -224
  72. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
  73. package/templates/qoder/scripts/{kg.py → domain/kg/kg.py} +871 -708
  74. package/templates/qoder/scripts/domain/kg/kg_capabilities.py +182 -0
  75. package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
  76. package/templates/qoder/scripts/{context_pack.py → domain/kg/search/context_pack.py} +45 -17
  77. package/templates/qoder/scripts/{enrich_prompt.py → domain/kg/search/enrich_prompt.py} +238 -226
  78. package/templates/qoder/scripts/domain/kg/search/prefetch.py +384 -0
  79. package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
  80. package/templates/qoder/scripts/{search_index.py → domain/kg/search/search_index.py} +210 -88
  81. package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
  82. package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
  83. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  84. package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
  85. package/templates/qoder/scripts/{kg_duckdb.py → domain/kg/storage/kg_duckdb.py} +70 -45
  86. package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
  87. package/templates/qoder/scripts/{learn.py → domain/learning/learn.py} +157 -146
  88. package/templates/qoder/scripts/domain/report/__init__.py +11 -0
  89. package/templates/qoder/scripts/{add_session.py → domain/report/add_session.py} +256 -244
  90. package/templates/qoder/scripts/{export.py → domain/report/export.py} +72 -4
  91. package/templates/qoder/scripts/{report.py → domain/report/report.py} +292 -281
  92. package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
  93. package/templates/qoder/scripts/domain/report/role.py +39 -0
  94. package/templates/qoder/scripts/{status.py → domain/report/status.py} +737 -628
  95. package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
  96. package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
  97. package/templates/qoder/scripts/{archive_prd.py → domain/requirement/archive_prd.py} +389 -377
  98. package/templates/qoder/scripts/domain/requirement/req.py +228 -0
  99. package/templates/qoder/scripts/domain/task/__init__.py +11 -0
  100. package/templates/qoder/scripts/{git_sync.py → domain/task/git_sync.py} +90 -50
  101. package/templates/qoder/scripts/{syncgate.py → domain/task/syncgate.py} +18 -7
  102. package/templates/qoder/scripts/domain/task/task.py +229 -0
  103. package/templates/qoder/scripts/domain/task/task_lifecycle.py +606 -0
  104. package/templates/qoder/scripts/domain/task/task_query.py +162 -0
  105. package/templates/qoder/scripts/domain/task/task_relations.py +425 -0
  106. package/templates/qoder/scripts/{team_sync.py → domain/task/team_sync.py} +101 -23
  107. package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
  108. package/templates/qoder/scripts/foundation/__init__.py +0 -0
  109. package/templates/qoder/scripts/foundation/bootstrap.py +145 -0
  110. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  111. package/templates/qoder/scripts/foundation/core/cmd_registry.py +112 -0
  112. package/templates/qoder/scripts/foundation/core/config.py +187 -0
  113. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -400
  114. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  115. package/templates/qoder/scripts/foundation/data/contract.py +317 -0
  116. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  117. package/templates/qoder/scripts/foundation/data/result.py +223 -0
  118. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -392
  119. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  120. package/templates/qoder/scripts/foundation/identity/check_publish.py +103 -0
  121. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -238
  122. package/templates/qoder/scripts/foundation/identity/guard.py +159 -0
  123. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +132 -9
  124. package/templates/qoder/scripts/foundation/identity/roles.py +60 -0
  125. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  126. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  127. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +11 -10
  128. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  129. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  130. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  131. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  132. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  133. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  134. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  135. package/templates/qoder/scripts/foundation/protocol/mcp_base.py +268 -0
  136. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  137. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  138. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  139. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  140. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  141. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  142. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  143. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  144. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  145. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  146. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  147. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  148. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  149. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  150. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  151. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  152. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  153. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  154. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  155. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  156. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  157. package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → protocol/mcp/lanhu_stdio_wrapper.py} +131 -119
  158. package/templates/qoder/scripts/protocol/mcp/mcp_doctor.py +548 -0
  159. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  160. package/templates/qoder/scripts/protocol/mcp/mysql_mcp_server.py +461 -0
  161. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  162. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  163. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  164. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  165. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  166. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  167. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  168. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  169. package/templates/qoder/scripts/validation/__init__.py +0 -0
  170. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  171. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  172. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  173. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  174. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  175. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  176. package/templates/qoder/scripts/{eval_prd.py → validation/metrics/eval_prd.py} +84 -15
  177. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  178. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  179. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  180. package/templates/qoder/scripts/{autotest.py → validation/test/autotest.py} +1234 -1751
  181. package/templates/qoder/scripts/validation/test/autotest_auth.py +109 -0
  182. package/templates/qoder/scripts/{autotest_batch.py → validation/test/autotest_batch.py} +255 -224
  183. package/templates/qoder/scripts/validation/test/autotest_data.py +680 -0
  184. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  185. package/templates/qoder/scripts/{autotest_run.py → validation/test/autotest_run.py} +323 -297
  186. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  187. package/templates/qoder/scripts/{benchmark.py → validation/test/benchmark.py} +17 -5
  188. package/templates/qoder/scripts/{kg_auto_login.py → validation/test/kg_auto_login.py} +208 -196
  189. package/templates/qoder/scripts/{kg_test_runner.py → validation/test/kg_test_runner.py} +13 -2
  190. package/templates/qoder/scripts/{page_probe.py → validation/test/page_probe.py} +470 -459
  191. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  192. package/templates/qoder/settings.json +23 -2
  193. package/templates/qoder/skills/design-import/SKILL.md +235 -226
  194. package/templates/qoder/skills/design-review/SKILL.md +91 -82
  195. package/templates/qoder/skills/prd-generator/SKILL.md +38 -24
  196. package/templates/qoder/skills/prd-review/SKILL.md +8 -6
  197. package/templates/qoder/skills/prototype-generator/SKILL.md +296 -247
  198. package/templates/qoder/skills/spec-coder/SKILL.md +24 -15
  199. package/templates/qoder/skills/spec-generator/SKILL.md +24 -12
  200. package/templates/qoder/skills/test-generator/SKILL.md +9 -7
  201. package/templates/qoder/skills/wl-code/SKILL.md +25 -13
  202. package/templates/qoder/skills/wl-commit/SKILL.md +10 -9
  203. package/templates/qoder/skills/wl-design/SKILL.md +7 -5
  204. package/templates/qoder/skills/wl-init/SKILL.md +8 -8
  205. package/templates/qoder/skills/wl-insight/SKILL.md +25 -12
  206. package/templates/qoder/skills/wl-prd-full/SKILL.md +59 -8
  207. package/templates/qoder/skills/wl-prd-quick/SKILL.md +7 -7
  208. package/templates/qoder/skills/wl-prd-review/SKILL.md +18 -6
  209. package/templates/qoder/skills/wl-report/SKILL.md +27 -25
  210. package/templates/qoder/skills/wl-search/SKILL.md +151 -80
  211. package/templates/qoder/skills/wl-spec/SKILL.md +9 -7
  212. package/templates/qoder/skills/wl-status/SKILL.md +50 -18
  213. package/templates/qoder/skills/wl-task/SKILL.md +94 -12
  214. package/templates/qoder/skills/wl-test/SKILL.md +154 -45
  215. package/templates/qoder/templates/prd-full-template.md +7 -0
  216. package/templates/root/AGENTS.md +275 -237
  217. package/templates/root/requirements.txt +3 -0
  218. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  219. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  220. package/templates/qoder/hooks/inject-workflow-state.py +0 -169
  221. package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
  222. package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
  223. package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
  224. package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
  225. package/templates/qoder/scripts/check_carriers.py +0 -238
  226. package/templates/qoder/scripts/check_mcp.py +0 -298
  227. package/templates/qoder/scripts/check_mcp_launch.py +0 -183
  228. package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
  229. package/templates/qoder/scripts/collect_prds.py +0 -31
  230. package/templates/qoder/scripts/common/mentions.py +0 -134
  231. package/templates/qoder/scripts/common/utf8.py +0 -38
  232. package/templates/qoder/scripts/extract_api_params.py +0 -246
  233. package/templates/qoder/scripts/extract_routes.py +0 -54
  234. package/templates/qoder/scripts/extract_routes_tree.py +0 -78
  235. package/templates/qoder/scripts/handoff.py +0 -22
  236. package/templates/qoder/scripts/init_developer.py +0 -76
  237. package/templates/qoder/scripts/kg_mcp_server.py +0 -801
  238. package/templates/qoder/scripts/kg_semantic.py +0 -150
  239. package/templates/qoder/scripts/mcp_launcher.py +0 -414
  240. package/templates/qoder/scripts/mysql_mcp_server.py +0 -396
  241. package/templates/qoder/scripts/parse_prds.py +0 -33
  242. package/templates/qoder/scripts/role.py +0 -51
  243. package/templates/qoder/scripts/sync_carriers.py +0 -259
  244. package/templates/qoder/scripts/task.py +0 -1261
  245. package/templates/qoder/scripts/workspace_init.py +0 -102
  246. package/templates/qoder/scripts/zentao_mcp_server.py +0 -424
  247. package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
  248. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  249. /package/templates/qoder/{skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp → scripts/domain/__init__.py} +0 -0
  250. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  251. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  252. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  253. /package/templates/qoder/scripts/{secure-ls.js → validation/test/secure-ls.js} +0 -0
@@ -1,396 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
- """MySQL Read-Only MCP Server (pure Python stdio, no external SDK).
4
-
5
- 提供 3 个语义化只读工具查询 MySQL 数据库 (真实数据层真相源):
6
- - query_schema: 查表结构 (列名/类型/注释/约束)
7
- - query_data: 查数据 (自动 LIMIT, 防 SELECT * 拉爆)
8
- - query_distinct:查枚举值 (DISTINCT 某字段的去重值 + 计数)
9
-
10
- 安全设计 (硬约束):
11
- 1. 启动前 ping 内网, 不通 → 输出"请连接内网"并拒绝启动
12
- 2. SQL 只读白名单: 只允许 SELECT/SHOW/DESCRIBE/EXPLAIN/WITH
13
- 任何写操作 (INSERT/UPDATE/DELETE/DROP/ALTER/...) 直接拒绝
14
- 3. query_data 自动注入 LIMIT, 上限 200 行
15
- 4. 账号密码从环境变量读 (mcp.json env 配置, 不落代码)
16
-
17
- 注册: ~/.qoderwork/mcp.json 的 "qoder-mysql"。
18
- 工具名: mcp__qoder-mysql__query_schema / query_data / query_distinct
19
- """
20
- import io
21
- import os
22
- import re
23
- import sys
24
- import json
25
- import socket
26
- import asyncio
27
-
28
- # UTF-8
29
- try:
30
- sys.stdout.reconfigure(encoding='utf-8', errors='replace')
31
- sys.stderr.reconfigure(encoding='utf-8', errors='replace')
32
- except Exception:
33
- pass
34
-
35
- # ── 配置 (从环境变量, mcp.json env 注入) ──────────────────────────────
36
- MYSQL_HOST = os.environ.get('MYSQL_HOST', '10.54.6.9')
37
- MYSQL_PORT = int(os.environ.get('MYSQL_PORT', '3306'))
38
- MYSQL_USER = os.environ.get('MYSQL_USER', 'fywl_ics_test')
39
- MYSQL_PASSWORD = os.environ.get('MYSQL_PASSWORD', 'Fywl_ics_test56476')
40
- PING_HOST = MYSQL_HOST # 连通性校验目标
41
- PING_TIMEOUT = 3 # 秒
42
-
43
- # ── 环境可信度声明 (随每次工具返回, AI 不可能"忘记"边界) ──────────────
44
- # QAS 测试环境: 表结构/字段/枚举取值范围 = 生产一致 (可信);
45
- # 数据行/计数分布 = 测试数据, 不代表线上 (不可信)。
46
- ENV_NAME = os.environ.get('MYSQL_ENV_NAME', 'QAS 测试环境')
47
- ENV_NOTICE_SCHEMA = (
48
- '\n[环境: %s] 表结构/字段/枚举定义与生产一致,可作为字段规格的真源。' % ENV_NAME
49
- )
50
- ENV_NOTICE_DATA = (
51
- '\n⚠️ [环境: %s] 以下为测试数据样本,不代表线上真实分布。'
52
- '字段取值范围可信,但具体数值/计数仅测试环境参考。' % ENV_NAME
53
- )
54
-
55
- # Suppress stdout non-JSON (MCP 用 stdout 传 JSON-RPC)
56
- _real_stdout = sys.stdout
57
- sys.stdout = sys.stderr
58
-
59
-
60
- # ── 安全: 内网连通性校验 ──────────────────────────────────────────────
61
- def check_intranet():
62
- """TCP 连通性校验 (比 ICMP ping 更可靠, 不依赖管理员权限)。
63
- 连 3306 端口, 通=内网可达, 不通=提示连内网。
64
- 返回 (ok: bool, msg: str)。
65
- """
66
- try:
67
- with socket.create_connection((MYSQL_HOST, MYSQL_PORT), timeout=PING_TIMEOUT):
68
- return True, ''
69
- except (socket.timeout, OSError) as e:
70
- return False, (
71
- '无法连接内网 MySQL (%s:%s): %s\n'
72
- '⚠️ 请确认你已连接公司内网 (VPN/办公网), 然后重试。'
73
- 'MySQL MCP 仅在内网可用。'
74
- ) % (MYSQL_HOST, MYSQL_PORT, str(e)[:80])
75
-
76
-
77
- # ── 安全: SQL 只读白名单 ──────────────────────────────────────────────
78
- # 允许的 SQL 首关键字 (只读操作)
79
- READONLY_PREFIXES = {
80
- 'select', 'show', 'describe', 'desc', 'explain', 'with',
81
- }
82
- # 明确禁止的关键字 (即使出现在子查询也拦截, 防注入写操作)
83
- FORBIDDEN_KEYWORDS = {
84
- 'insert', 'update', 'delete', 'drop', 'alter', 'create', 'truncate',
85
- 'replace', 'rename', 'grant', 'revoke', 'lock', 'unlock', 'call',
86
- 'handler', 'load', 'outfile', 'dumpfile',
87
- }
88
-
89
-
90
- def validate_readonly(sql):
91
- """校验 SQL 是否只读。返回 (ok: bool, reason: str)。"""
92
- if not sql or not sql.strip():
93
- return False, '空 SQL'
94
- s = sql.strip().lower().lstrip('(').strip()
95
- # 去掉开头的 WITH ... AS ( ... ) 后看主语句
96
- # 简化: 取第一个词
97
- first_word = re.split(r'[\s(]', s, 1)[0]
98
- if first_word not in READONLY_PREFIXES:
99
- return False, '非只读操作: 首关键字 "%s" 不在白名单 %s' % (
100
- first_word, sorted(READONLY_PREFIXES))
101
- # 扫描是否含危险关键字 (作为独立词, 防 delete 嵌在字段名里)
102
- tokens = set(re.findall(r'\b\w+\b', s))
103
- hit = tokens & FORBIDDEN_KEYWORDS
104
- if hit:
105
- return False, 'SQL 含禁止的关键字: %s (只读模式拒绝写操作)' % sorted(hit)
106
- return True, ''
107
-
108
-
109
- def inject_limit(sql, max_rows=200):
110
- """给 SELECT 自动加 LIMIT (若用户没写)。防拉爆。"""
111
- s = sql.strip().rstrip(';')
112
- # 已有 limit → 不动 (但截断到 max_rows 由 fetch 控制)
113
- if re.search(r'\blimit\s+\d+', s, re.IGNORECASE):
114
- return s
115
- return s + ' LIMIT %d' % max_rows
116
-
117
-
118
- # ── MySQL 连接 (惰性) ─────────────────────────────────────────────────
119
- _conn = None
120
-
121
-
122
- def _get_conn():
123
- """惰性建连 (首次查询时才连)。"""
124
- global _conn
125
- if _conn is not None:
126
- return _conn
127
- # 用最普及的 pymysql, 纯 Python, 无编译依赖
128
- import pymysql
129
- _conn = pymysql.connect(
130
- host=MYSQL_HOST, port=MYSQL_PORT,
131
- user=MYSQL_USER, password=MYSQL_PASSWORD,
132
- charset='utf8mb4',
133
- cursorclass=pymysql.cursors.DictCursor,
134
- connect_timeout=5, read_timeout=10,
135
- )
136
- return _conn
137
-
138
-
139
- def _execute_query(sql, db=None, max_rows=200):
140
- """执行只读查询, 返回 (columns, rows)。失败抛异常。"""
141
- ok, reason = validate_readonly(sql)
142
- if not ok:
143
- raise ValueError('SQL 被拒: ' + reason)
144
- conn = _get_conn()
145
- if db:
146
- conn.select_db(db)
147
- cur = conn.cursor()
148
- try:
149
- cur.execute(sql)
150
- rows = cur.fetchmany(max_rows) # 硬上限, 不全拉
151
- cols = [d[0] for d in cur.description] if cur.description else []
152
- return cols, rows
153
- finally:
154
- cur.close()
155
-
156
-
157
- # ── MCP 工具定义 ──────────────────────────────────────────────────────
158
- TOOLS = [
159
- {
160
- "name": "query_schema",
161
- "description": "查表结构: 列名/类型/注释/是否可空/主键。用于写 PRD/spec 时核对真实字段。例: query_schema(table='t_quality_case') 或 query_schema(table='t_quality_case', db='fywl_ics')",
162
- "inputSchema": {
163
- "type": "object",
164
- "properties": {
165
- "table": {"type": "string", "description": "表名 (如 t_quality_case)"},
166
- "db": {"type": "string", "description": "库名 (可选, 不填用默认库)"},
167
- },
168
- "required": ["table"],
169
- },
170
- },
171
- {
172
- "name": "query_data",
173
- "description": "查数据: 按条件查表数据, 自动 LIMIT 200 行。[QAS测试环境] 数据行是测试数据, 不代表线上真实分布, 仅参考字段含义/格式。例: query_data(table='t_quality_case', columns='id,case_status', where='create_time > \"2026-06-01\"', limit=10)",
174
- "inputSchema": {
175
- "type": "object",
176
- "properties": {
177
- "table": {"type": "string", "description": "表名"},
178
- "columns": {"type": "string", "description": "列名 (逗号分隔, 默认 *)"},
179
- "where": {"type": "string", "description": "WHERE 条件 (不带 WHERE 关键字)"},
180
- "db": {"type": "string", "description": "库名 (可选)"},
181
- "limit": {"type": "integer", "description": "行数上限 (默认200, 最大200)"},
182
- },
183
- "required": ["table"],
184
- },
185
- },
186
- {
187
- "name": "query_distinct",
188
- "description": "查枚举值: 某字段的去重值。[QAS测试环境] 取值范围可信(与生产一致), 计数不可信(测试分布)。用于确认状态/类型字段有哪些取值。例: query_distinct(table='t_quality_case', column='case_status')",
189
- "inputSchema": {
190
- "type": "object",
191
- "properties": {
192
- "table": {"type": "string", "description": "表名"},
193
- "column": {"type": "string", "description": "要查枚举的列名"},
194
- "db": {"type": "string", "description": "库名 (可选)"},
195
- "limit": {"type": "integer", "description": "返回枚举值上限 (默认50)"},
196
- },
197
- "required": ["table", "column"],
198
- },
199
- },
200
- {
201
- "name": "list_databases",
202
- "description": "列出所有可见的数据库。用于发现有哪些库可查。例: list_databases()",
203
- "inputSchema": {"type": "object", "properties": {}},
204
- },
205
- {
206
- "name": "list_tables",
207
- "description": "列出某库的表 (支持模糊匹配)。用于发现有哪些表。例: list_tables(db='fywl_ics', pattern='quality%')",
208
- "inputSchema": {
209
- "type": "object",
210
- "properties": {
211
- "db": {"type": "string", "description": "库名"},
212
- "pattern": {"type": "string", "description": "表名 LIKE 模式 (可选, 如 quality%)"},
213
- },
214
- "required": ["db"],
215
- },
216
- },
217
- ]
218
-
219
-
220
- # ── 工具执行 ──────────────────────────────────────────────────────────
221
- def _execute_tool(name, args):
222
- args = args or {}
223
- # 内网校验 (每次工具调用都校验, 防 VPN 中途断开)
224
- ok, msg = check_intranet()
225
- if not ok:
226
- return msg
227
-
228
- if name == 'list_databases':
229
- cols, rows = _execute_query('SHOW DATABASES')
230
- dbs = [list(r.values())[0] for r in rows]
231
- return '数据库 (%d 个):\n %s' % (len(dbs), '\n '.join(dbs))
232
-
233
- if name == 'list_tables':
234
- db = args.get('db', '')
235
- pattern = args.get('pattern', '')
236
- sql = 'SHOW TABLES'
237
- if pattern:
238
- sql += ' LIKE "%s"' % pattern.replace('"', '')
239
- cols, rows = _execute_query(sql, db=db)
240
- tables = [list(r.values())[0] for r in rows]
241
- return '表 (%d 个, 库=%s):\n %s' % (len(tables), db, '\n '.join(tables[:100]))
242
-
243
- if name == 'query_schema':
244
- table = args.get('table', '').replace('"', '').replace("'", '')
245
- db = args.get('db')
246
- sql = (
247
- 'SELECT column_name, data_type, column_type, is_nullable, '
248
- 'column_key, column_default, column_comment '
249
- 'FROM information_schema.columns '
250
- 'WHERE table_name = "%s" ' % table
251
- )
252
- if db:
253
- sql += 'AND table_schema = "%s" ' % db.replace('"', '')
254
- sql += 'ORDER BY ordinal_position'
255
- cols, rows = _execute_query(sql)
256
- if not rows:
257
- return '未找到表: %s (检查表名或库名)' % table
258
- lines = ['%-20s %-12s %-6s %-6s %s' % (
259
- '列名', '类型', '可空', '键', '注释')]
260
- for r in rows:
261
- lines.append('%-20s %-12s %-6s %-6s %s' % (
262
- r.get('column_name', ''), r.get('column_type', '')[:12],
263
- r.get('is_nullable', '')[:3], r.get('column_key', ''),
264
- (r.get('column_comment', '') or '')[:30]))
265
- return '\n'.join(lines) + ENV_NOTICE_SCHEMA
266
-
267
- if name == 'query_data':
268
- table = args.get('table', '').replace('"', '').replace("'", '')
269
- columns = args.get('columns') or '*'
270
- where = args.get('where', '').strip()
271
- db = args.get('db')
272
- limit = min(int(args.get('limit', 200)), 200)
273
- sql = 'SELECT %s FROM `%s`' % (columns, table)
274
- if where:
275
- sql += ' WHERE %s' % where
276
- sql = inject_limit(sql, limit)
277
- cols, rows = _execute_query(sql, db=db, max_rows=limit)
278
- if not rows:
279
- return '无数据 (表=%s)' % table
280
- lines = [' | '.join(cols)]
281
- lines.append('-' * 40)
282
- for r in rows[:limit]:
283
- lines.append(' | '.join(str(r.get(c, ''))[:30] for c in cols))
284
- return '\n'.join(lines) + ENV_NOTICE_DATA
285
-
286
- if name == 'query_distinct':
287
- table = args.get('table', '').replace('"', '').replace("'", '')
288
- column = args.get('column', '').replace('"', '').replace("'", '')
289
- db = args.get('db')
290
- limit = min(int(args.get('limit', 50)), 50)
291
- sql = (
292
- 'SELECT `%s` AS val, COUNT(*) AS cnt FROM `%s` '
293
- 'GROUP BY `%s` ORDER BY cnt DESC LIMIT %d'
294
- ) % (column, table, column, limit)
295
- cols, rows = _execute_query(sql, db=db, max_rows=limit)
296
- if not rows:
297
- return '无数据 (表=%s 列=%s)' % (table, column)
298
- # 取值范围可信(和生产一致), 但计数是测试环境分布(不可信)
299
- lines = ['枚举值 (表=%s 列=%s, 取值范围可信):' % (table, column)]
300
- for r in rows:
301
- lines.append(' %s: %s 次 (计数仅测试环境)' % (r.get('val', ''), r.get('cnt', '')))
302
- return '\n'.join(lines) + ENV_NOTICE_DATA
303
-
304
- return '未知工具: %s' % name
305
-
306
-
307
- # ── MCP Stdio Protocol (JSON-RPC 2.0, 同构 kg_mcp_server.py) ──────────
308
- def _send(msg):
309
- _real_stdout.write(json.dumps(msg, ensure_ascii=False) + '\n')
310
- _real_stdout.flush()
311
-
312
-
313
- async def _handle(req):
314
- method = req.get('method', '')
315
- req_id = req.get('id')
316
- params = req.get('params', {})
317
-
318
- if method == 'initialize':
319
- # 启动时做一次内网校验, 不通就在 serverInfo 里提示
320
- ok, msg = check_intranet()
321
- info = {"name": "qoder-mysql", "version": "1.0.0"}
322
- if not ok:
323
- info['warning'] = msg
324
- _send({
325
- "jsonrpc": "2.0", "id": req_id,
326
- "result": {
327
- "protocolVersion": "2024-11-05",
328
- "capabilities": {"tools": {}},
329
- "serverInfo": info,
330
- }
331
- })
332
- elif method == 'notifications/initialized':
333
- pass
334
- elif method == 'tools/list':
335
- _send({"jsonrpc": "2.0", "id": req_id, "result": {"tools": TOOLS}})
336
- elif method == 'tools/call':
337
- tool_name = params.get('name', '')
338
- arguments = params.get('arguments', {})
339
- try:
340
- result_text = await asyncio.get_event_loop().run_in_executor(
341
- None, _execute_tool, tool_name, arguments)
342
- _send({
343
- "jsonrpc": "2.0", "id": req_id,
344
- "result": {"content": [{"type": "text", "text": result_text}],
345
- "isError": False}
346
- })
347
- except ValueError as e:
348
- # SQL 被拒 (只读校验)
349
- _send({
350
- "jsonrpc": "2.0", "id": req_id,
351
- "result": {"content": [{"type": "text",
352
- "text": "❌ " + str(e)}],
353
- "isError": True}
354
- })
355
- except Exception as e:
356
- _send({
357
- "jsonrpc": "2.0", "id": req_id,
358
- "result": {"content": [{"type": "text",
359
- "text": "[error] %s" % str(e)[:200]}],
360
- "isError": True}
361
- })
362
- elif method == 'ping':
363
- _send({"jsonrpc": "2.0", "id": req_id, "result": {}})
364
- elif req_id is not None:
365
- _send({"jsonrpc": "2.0", "id": req_id,
366
- "error": {"code": -32601, "message": "Method not found: %s" % method}})
367
-
368
-
369
- def main():
370
- import threading
371
- while True:
372
- try:
373
- line = sys.stdin.buffer.readline()
374
- except (EOFError, OSError):
375
- break
376
- if not line:
377
- break
378
- line = line.strip()
379
- if not line:
380
- continue
381
- try:
382
- req = json.loads(line.decode('utf-8'))
383
- loop = asyncio.new_event_loop()
384
- loop.run_until_complete(_handle(req))
385
- loop.close()
386
- except json.JSONDecodeError:
387
- pass
388
- except Exception as e:
389
- print('[mysql-mcp] error: %s' % str(e), file=sys.stderr)
390
-
391
-
392
- if __name__ == '__main__':
393
- try:
394
- main()
395
- except KeyboardInterrupt:
396
- pass
@@ -1,33 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Parse PRD files and build PRD index (data/index/prd-index.json).
5
-
6
- THIN WRAPPER: the actual parsing logic lives in git_sync.py (single
7
- source of truth - previously this file kept its own drifted copy of
8
- the parser and term maps). Kept for backwards compatibility.
9
-
10
- Usage:
11
- python parse_prds.py # rebuild prd-index.json + prd-code-map.json
12
- """
13
-
14
- import os
15
- import sys
16
-
17
- # UTF-8 stdio (防御性: stdout 被捕获时不崩溃)
18
- try:
19
- sys.stdout.reconfigure(encoding='utf-8', errors='replace')
20
- except (AttributeError, TypeError, OSError, IOError):
21
- try:
22
- sys.stdout.reconfigure(encoding='utf-8')
23
- except Exception:
24
- pass
25
-
26
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
27
-
28
- from git_sync import build_prd_index, build_prd_code_mapping, FAILURES
29
-
30
- if __name__ == '__main__':
31
- build_prd_index()
32
- build_prd_code_mapping()
33
- sys.exit(1 if FAILURES else 0)
@@ -1,51 +0,0 @@
1
- # role.py - Resolve role from member info
2
- import os, json, sys
3
-
4
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
5
- from common.paths import get_developer, get_developer_info
6
-
7
- BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
8
-
9
- def get_role(name=None):
10
- if not name:
11
- # Prefer role recorded directly in .developer
12
- info = get_developer_info()
13
- if info and info.get('role'):
14
- return info['role']
15
- name = get_developer()
16
- if not name:
17
- return None
18
- mf = os.path.join(BASE, 'workspace', 'members', name, 'member.json')
19
- if os.path.isfile(mf):
20
- try:
21
- with open(mf, encoding='utf-8') as f:
22
- return json.load(f).get('role')
23
- except (OSError, json.JSONDecodeError):
24
- return None
25
- return None
26
-
27
- def get_permissions(role):
28
- import yaml
29
- cfg = os.path.join(BASE, '.qoder', 'config.yaml')
30
- if not os.path.isfile(cfg):
31
- return []
32
- try:
33
- with open(cfg, encoding='utf-8') as f:
34
- config = yaml.safe_load(f) or {}
35
- except (OSError, yaml.YAMLError):
36
- return []
37
- roles = config.get('roles', {})
38
- r = roles.get(role, {})
39
- return r.get('permissions', [])
40
-
41
-
42
- if __name__ == '__main__':
43
- # CLI: 输出当前角色名 (供 skill 脚本动态读角色用)
44
- # 用法: ROLE=$(python role.py)
45
- try:
46
- sys.stdout.reconfigure(encoding='utf-8')
47
- except (AttributeError, IOError):
48
- pass
49
- r = get_role()
50
- print(r or 'pm') # 无角色默认 pm (软引导, 不报错)
51
-