@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,801 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
- """
4
- Knowledge Graph MCP Server for QoderWork (pure Python, no SDK dependency).
5
-
6
- Exposes the QODER knowledge graph as MCP tools that QoderWork's AI agent
7
- can call directly. Uses the MCP stdio protocol (JSON-RPC 2.0 over stdin/stdout)
8
- implemented from scratch — no external dependencies beyond the standard library.
9
-
10
- Registered in ~/.qoderwork/mcp.json as "qoder-knowledge-graph".
11
- Tools surface as mcp__qoder-knowledge-graph__<tool> to the AI.
12
-
13
- Each tool captures the stdout of the existing search_index/context_pack
14
- functions, so no logic is duplicated — this is a thin stdio→MCP wrapper.
15
- """
16
- import io
17
- import os
18
- import sys
19
- import json
20
- import asyncio
21
-
22
- # Ensure UTF-8 output
23
- try:
24
- sys.stdout.reconfigure(encoding='utf-8', errors='replace')
25
- sys.stderr.reconfigure(encoding='utf-8', errors='replace')
26
- except Exception:
27
- pass
28
-
29
- # Locate the scripts dir
30
- SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
31
- BASE_DIR = os.path.dirname(os.path.dirname(SCRIPTS_DIR)) # repo root (.qoder 的上级)
32
- sys.path.insert(0, SCRIPTS_DIR)
33
-
34
- # Suppress all non-JSON output on stdout (MCP protocol uses stdout exclusively)
35
- # Redirect any stray prints to stderr
36
- _real_stdout = sys.stdout
37
- sys.stdout = sys.stderr # default: prints go to stderr
38
-
39
-
40
- def _capture(func, *args, **kwargs):
41
- """Run a function, capture its stdout (which the scripts print to)."""
42
- old = sys.stdout
43
- buf = io.StringIO()
44
- sys.stdout = buf
45
- try:
46
- func(*args, **kwargs)
47
- except Exception as e:
48
- buf.write('\n[error] %s' % str(e))
49
- finally:
50
- sys.stdout = old
51
- return buf.getvalue().strip()
52
-
53
-
54
- # Lazy imports
55
- _search = None
56
- _context = None
57
-
58
- def _ensure_imports():
59
- global _search, _context
60
- if _search is None:
61
- import search_index as _search
62
- if _context is None:
63
- import context_pack as _context
64
-
65
-
66
- # ── MCP Tool Definitions ─────────────────────────────────────────────
67
-
68
- TOOLS = [
69
- {
70
- "name": "search_code",
71
- "description": "搜索代码: 按关键词查找代码文件在哪。例: search_code(keyword='考勤') 或 search_code(keyword='车辆', platform='web')",
72
- "inputSchema": {
73
- "type": "object",
74
- "properties": {
75
- "keyword": {"type": "string", "description": "搜索关键词(中文或英文)"},
76
- "platform": {"type": "string", "enum": ["web", "app"],
77
- "description": "平台: web=PC管理端, app=移动端。可选"},
78
- },
79
- "required": ["keyword"],
80
- },
81
- },
82
- {
83
- "name": "search_api",
84
- "description": "搜索API端点: 按关键词查找后端接口。例: search_api(keyword='salary')",
85
- "inputSchema": {
86
- "type": "object",
87
- "properties": {"keyword": {"type": "string", "description": "API关键词"}},
88
- "required": ["keyword"],
89
- },
90
- },
91
- {
92
- "name": "search_prd",
93
- "description": "搜索PRD: 查已有需求文档,防止重复造轮子。例: search_prd(keyword='考勤')",
94
- "inputSchema": {
95
- "type": "object",
96
- "properties": {"keyword": {"type": "string", "description": "PRD关键词"}},
97
- "required": ["keyword"],
98
- },
99
- },
100
- {
101
- "name": "get_impact",
102
- "description": "影响分析: 改某接口/路径会影响哪些前端页面和按钮?评估改动风险。例: get_impact(endpoint='/asset') 或 get_impact(endpoint='export')",
103
- "inputSchema": {
104
- "type": "object",
105
- "properties": {"endpoint": {"type": "string",
106
- "description": "API端点路径或关键词, 如 /asset/list 或 export"}},
107
- "required": ["endpoint"],
108
- },
109
- },
110
- {
111
- "name": "context_360",
112
- "description": "360度视图: 查看符号(端点/函数/处理器)的全部关联信息。例: context_360(symbol='handleExport') 或 context_360(symbol='/system/role')",
113
- "inputSchema": {
114
- "type": "object",
115
- "properties": {"symbol": {"type": "string", "description": "符号名: 端点/函数名/处理器名"}},
116
- "required": ["symbol"],
117
- },
118
- },
119
- {
120
- "name": "context_pack",
121
- "description": "上下文打包: 一次返回关键词相关的全部上下文(代码+页面+字段+PRD+API+布局指纹)。避免来回查。例: context_pack(keyword='车辆', platform='web')",
122
- "inputSchema": {
123
- "type": "object",
124
- "properties": {
125
- "keyword": {"type": "string", "description": "业务关键词"},
126
- "platform": {"type": "string", "enum": ["web", "app"],
127
- "description": "平台: web或app。可选"},
128
- "page_type": {"type": "string",
129
- "description": "页面类型: table-page/form-page/detail-page。可选"},
130
- "role": {"type": "string", "enum": ["pm", "design", "dev", "test", "admin"],
131
- "description": "角色裁剪: pm=字段+PRD+API, design=页面+布局+风格, dev=代码+API, test=代码+PRD+API。可选"},
132
- },
133
- "required": ["keyword"],
134
- },
135
- },
136
- {
137
- "name": "table_impact",
138
- "description": "改表影响分析: 改某数据库表会影响哪些 Entity 类和接口? 跨层遍历(数据层→代码层)。例: table_impact(table='quality_case') → 找到 QualityCase 类 + 影响的 Controller/接口",
139
- "inputSchema": {
140
- "type": "object",
141
- "properties": {
142
- "table": {"type": "string", "description": "数据库表名 (如 quality_case)"},
143
- },
144
- "required": ["table"],
145
- },
146
- },
147
- {
148
- "name": "coverage_matrix",
149
- "description": "功能×测试覆盖矩阵: 哪些功能有测试覆盖, 哪些是空白? 标出回归测试盲区。例: coverage_matrix()",
150
- "inputSchema": {
151
- "type": "object",
152
- "properties": {},
153
- },
154
- },
155
- {
156
- "name": "feature_overview",
157
- "description": "功能画像: 一个功能的完整画像(端点+按钮+测试+页面)。例: feature_overview(feature='资产管理') 或 feature_overview(feature='asset')",
158
- "inputSchema": {
159
- "type": "object",
160
- "properties": {
161
- "feature": {"type": "string", "description": "功能名(中/英文都行): 资产管理/asset/hr"},
162
- },
163
- "required": ["feature"],
164
- },
165
- },
166
- {
167
- "name": "get_workflow",
168
- "description": "业务流程: 查看一个模块的完整操作链(query→create→audit→archive→export→delete)。数据源: DuckDB workflows 表(168条/13模块)。优先匹配英文模块名(assets/cost/hr/maintenance等), 中文输入会返回候选模块列表。例: get_workflow(module='assets') 或 get_workflow(module='cost')",
169
- "inputSchema": {
170
- "type": "object",
171
- "properties": {
172
- "module": {"type": "string", "description": "模块名(英文优先): assets/cost/hr/maintenance/operation/qualityAndSafety/veh/settings/dashboard/flow/systems/monitor/quasafe"},
173
- },
174
- "required": ["module"],
175
- },
176
- },
177
- {
178
- "name": "search_wiki",
179
- "description": "搜索Repo Wiki: 查Qoder IDE生成的模块级语义文档(架构/职责/依赖), 比读源码省token。写PRD前优先查。数据源: wiki-index.json(730关键词)。注意: 不会自动构建索引, 索引缺失会提示手动跑。例: search_wiki(keyword='考勤') 或 search_wiki(keyword='权限')",
180
- "inputSchema": {
181
- "type": "object",
182
- "properties": {"keyword": {"type": "string", "description": "Wiki关键词(中/英文)"}},
183
- "required": ["keyword"],
184
- },
185
- },
186
- {
187
- "name": "multi_hop",
188
- "description": "知识图谱多跳遍历: 从一个符号出发, 沿edges递归N跳, 找出全部可达实体(页面→接口→控制器→测试)。知识图谱核心能力。数据源: DuckDB edges表(8251边)。支持别名解析(传中文/端点/函数名自动转entity_id)。例: multi_hop(symbol='资产管理') 或 multi_hop(symbol='/asset/list', max_depth=3)",
189
- "inputSchema": {
190
- "type": "object",
191
- "properties": {
192
- "symbol": {"type": "string", "description": "起点符号: 可传entity_id(E:/asset/list)、别名(资产管理)、端点(/asset/list)或函数名"},
193
- "max_depth": {"type": "integer", "default": 3, "description": "最大跳数(1-5), 默认3。越大越全但越慢"},
194
- },
195
- "required": ["symbol"],
196
- },
197
- },
198
- {
199
- "name": "search_field",
200
- "description": "搜索字段: 查某个字段在哪些页面用过、中文标题是什么。例: search_field(field='plateNo') 或 search_field(field='车牌号')",
201
- "inputSchema": {
202
- "type": "object",
203
- "properties": {
204
- "field": {"type": "string", "description": "字段名(英文field或中文标题)"},
205
- },
206
- "required": ["field"],
207
- },
208
- },
209
- {
210
- "name": "list_modules",
211
- "description": "模块清单: 列出项目所有业务模块及其中文名。例: list_modules() 或 list_modules(platform='web')",
212
- "inputSchema": {
213
- "type": "object",
214
- "properties": {
215
- "platform": {"type": "string", "enum": ["web", "app"], "description": "平台过滤。可选"},
216
- },
217
- },
218
- },
219
- {
220
- "name": "fill_prototype",
221
- "description": "原型预填: 用真实代码数据(表格列/表单字段/布局参数)自动填模板,输出80%完成的HTML草稿+数据清单。AI只需微调20%。出原型前必须调。例: fill_prototype(keyword='营业外合同', platform='web')",
222
- "inputSchema": {
223
- "type": "object",
224
- "properties": {
225
- "keyword": {"type": "string", "description": "业务关键词"},
226
- "platform": {"type": "string", "enum": ["web", "app"],
227
- "description": "平台: web或app。默认web"},
228
- },
229
- "required": ["keyword"],
230
- },
231
- },
232
- {
233
- "name": "get_design_system",
234
- "description": "设计系统查询: 返回项目完整设计规范(布局指纹/颜色token/页面模式/组件表/图标规则/behaviors)。出任何原型前必须先调。例: get_design_system() 或 get_design_system(platform='web')",
235
- "inputSchema": {
236
- "type": "object",
237
- "properties": {
238
- "platform": {"type": "string", "enum": ["web", "app"],
239
- "description": "平台: 只返回指定平台的指纹。不传则返回全部"},
240
- },
241
- },
242
- },
243
- ]
244
-
245
-
246
-
247
-
248
- # Role-based section filtering for context_pack output
249
- ROLE_SECTIONS = {
250
- 'pm': {'1', '2', '2b', '3', '4', '5', '7'}, # PM: no code files, no style refs
251
- 'design': {'2', '2b', '3', '6'}, # Design: pages+layout+fields+style
252
- 'dev': {'1', '3', '5', '7'}, # Dev: code+fields+API+wiki
253
- 'test': {'1', '4', '5'}, # Test: code+PRD+API
254
- 'admin': {'4', '5'}, # Admin: PRD+API overview
255
- }
256
-
257
- def _filter_by_role(text, role):
258
- """Filter context_pack output by role: keep only relevant sections."""
259
- import re
260
- visible = ROLE_SECTIONS.get(role.lower())
261
- if not visible:
262
- return text
263
- sections = re.split(r'(\n## \d+[ab]?\.)', text)
264
- result = sections[0] # header before first section
265
- i = 1
266
- while i < len(sections):
267
- header = sections[i]
268
- num_m = re.match(r'\n## (\d+[ab]?)\.', header)
269
- if num_m and num_m.group(1) in visible:
270
- result += header
271
- if i + 1 < len(sections):
272
- result += sections[i + 1]
273
- i += 2
274
- else:
275
- i += 2 # skip this section
276
- return result.strip()
277
-
278
-
279
- # ── Tool implementations (added 2026-06-17) ──────────────────────────
280
- # 三个工具的底层能力均已实测: get_workflow/multi_hop 走 DuckDB(有数据),
281
- # search_wiki 走 wiki-index.json(730词)。均不依赖已失效的旧 JSON 索引。
282
-
283
- def _tool_get_workflow(module):
284
- """业务操作链查询。直查 DuckDB workflows 表, 带脏数据清洗 + 模糊匹配。
285
-
286
- 审核: workflows 表 168 行/13 模块, 脏数据约 25 行(endpoint='None' 或
287
- button 泄漏模板语法 '{{'), 清洗后链路完整。模块名均为英文, 中文输入会
288
- 模糊查 entities.cn 返回候选。
289
- """
290
- import kg_duckdb as K
291
- q = (module or '').strip()
292
- if not q:
293
- return "请提供模块名。可选: assets/cost/hr/maintenance/operation/qualityAndSafety/veh/settings/dashboard/flow/systems/monitor/quasafe"
294
- con = K.get_db()
295
- try:
296
- # 1) 精确 + 模糊匹配 workflows.module (英文)
297
- hits = con.execute(
298
- "SELECT DISTINCT module FROM workflows WHERE module = ? OR module LIKE '%' || ? || '%' ORDER BY module",
299
- [q.lower(), q.lower()]
300
- ).fetchall()
301
- if not hits:
302
- # 2) 中文兜底: 查 entities 里 cn 匹配的, 给候选
303
- cn_hits = con.execute(
304
- "SELECT canonical, cn FROM entities WHERE type IN ('FEATURE','MODULE') AND cn <> '' AND (cn LIKE '%' || ? OR cn LIKE '%' || ? || '%')",
305
- [q, q]
306
- ).fetchall()
307
- if cn_hits:
308
- cands = ', '.join('%s(%s)' % (c[0], c[1]) for c in cn_hits[:8])
309
- return "未在 workflows 表找到模块 '%s'。但 entities 表有相近的中文名, 可换这些英文模块名重试:\n %s" % (q, cands)
310
- all_mods = [r[0] for r in con.execute("SELECT DISTINCT module FROM workflows ORDER BY module").fetchall()]
311
- return "未找到模块 '%s'。现有模块: %s" % (q, ', '.join(all_mods))
312
-
313
- out = []
314
- for (mod,) in hits:
315
- # 清洗: 过滤空端点和模板泄漏按钮
316
- rows = con.execute("""
317
- SELECT state, seq, button, endpoint FROM workflows
318
- WHERE module = ?
319
- AND endpoint <> 'None' AND endpoint <> ''
320
- AND button NOT LIKE '{{%'
321
- ORDER BY seq, state
322
- """, [mod]).fetchall()
323
- if not rows:
324
- continue
325
- # 按 seq 分组展示操作链
326
- out.append('=== %s (%d 步, 已清洗脏数据) ===' % (mod, len(rows)))
327
- cur_seq = -1
328
- for st, sq, btn, ep in rows:
329
- if sq != cur_seq:
330
- out.append(' [%d] %s' % (sq, st))
331
- cur_seq = sq
332
- out.append(' %-16s -> %s' % (btn, ep))
333
- out.append('')
334
- return '\n'.join(out).strip() or "模块 '%s' 清洗后无有效操作链" % q
335
- finally:
336
- try: con.close()
337
- except Exception: pass
338
-
339
-
340
- def _tool_search_wiki(keyword):
341
- """Repo Wiki 搜索。只读 wiki-index.json, 不触发自动构建(慢操作, 不适合 MCP 同步调用)。
342
-
343
- 审核: repowiki.search_wiki 函数在, wiki-index.json 有 730 关键词。
344
- search_index.search_wiki_cli 会在索引缺失时调 build_wiki_index (可能跑很久),
345
- 这里绕开它直接调底层 search_wiki, 索引缺失即报错让用户手动跑。
346
- """
347
- kw = (keyword or '').strip()
348
- if not kw:
349
- return "请提供搜索关键词"
350
- from common.paths import DATA_INDEX_DIR as _idx, PROJECT_ROOT as _base
351
- wiki_path = os.path.join(str(_idx), 'wiki-index.json')
352
- if not os.path.isfile(wiki_path):
353
- return ("wiki-index.json 不存在。请在终端手动构建(一次性):\n"
354
- " python .qoder/scripts/search_index.py wiki\n"
355
- "(需要先在 Qoder IDE 对项目生成 Repo Wiki)")
356
- try:
357
- from common.repowiki import search_wiki
358
- from common.terms import get_cn_map_with_auto
359
- except ImportError as e:
360
- return "Wiki 搜索模块缺失: %s" % str(e)
361
- code_dir = os.path.join(_base, 'data', 'code')
362
- try:
363
- cn_map = get_cn_map_with_auto()
364
- results = search_wiki(kw, str(_idx), code_dir, cn_map)
365
- except Exception as e:
366
- return "Wiki 搜索出错: %s" % str(e)
367
- if not results:
368
- return "No wiki pages match: %s" % kw
369
- out = ['Wiki pages matching: %s (%d found)' % (kw, len(results)), '']
370
- for i, r in enumerate(results, 1):
371
- out.append(' %d. [%s] %s' % (i, r.get('project', '?'), r.get('title', '')))
372
- deps = r.get('dependent_files') or []
373
- if deps:
374
- out.append(' 依赖: %s%s' % (', '.join(deps[:2]),
375
- (' (共 %d 个文件)' % len(deps)) if len(deps) > 2 else ''))
376
- if r.get('md_path'):
377
- try:
378
- rel = os.path.relpath(r['md_path'], _base)
379
- except ValueError:
380
- rel = r['md_path']
381
- out.append(' 文档: %s' % rel)
382
- out.append('')
383
- out.append(' 提示: 读 wiki 文档 (带行号引用) 比读源码更省 token')
384
- return '\n'.join(out)
385
-
386
-
387
- def _tool_multi_hop(symbol, max_depth=3):
388
- """知识图谱多跳遍历。先别名解析, 再递归 CTE 遍历。
389
-
390
- 审核: kg_duckdb.multi_hop 实测从 E:/code 3 跳到 C:SysSmsCodeController。
391
- edges 表 8251 边。用户可能传中文名/端点/函数名, 需先走 aliases 解析成
392
- entity_id。
393
- """
394
- import kg_duckdb as K
395
- sym = (symbol or '').strip()
396
- if not sym:
397
- return "请提供起点符号 (entity_id / 端点 / 函数名 / 中文别名)"
398
- try:
399
- depth = int(max_depth)
400
- except (TypeError, ValueError):
401
- depth = 3
402
- depth = max(1, min(5, depth)) # 钳制 1-5
403
- con = K.get_db()
404
- try:
405
- # 1) 尝试解析为 entity_id: 直接匹配 / 别名表 / 模糊匹配 entity_id
406
- start_id = sym
407
- # 1a) 别名精确匹配 (小写)
408
- resolved = con.execute(
409
- "SELECT entity_id FROM aliases WHERE alias = ?", [sym.lower()]
410
- ).fetchone()
411
- if resolved:
412
- start_id = resolved[0]
413
- else:
414
- # 1b) entity_id 模糊匹配 (用户传端点 /asset/list 或函数名)
415
- fuzzy = con.execute(
416
- "SELECT id FROM entities WHERE id = ? OR id LIKE '%' || ? || '%' LIMIT 1",
417
- [sym, sym]
418
- ).fetchone()
419
- if fuzzy:
420
- start_id = fuzzy[0]
421
- else:
422
- # 1c) edges 里直接含该符号作为端点
423
- edge_hit = con.execute(
424
- "SELECT from_id FROM edges WHERE from_id LIKE '%' || ? || '%' LIMIT 1",
425
- [sym]
426
- ).fetchone()
427
- if edge_hit:
428
- start_id = edge_hit[0]
429
-
430
- # 2) 递归 CTE 遍历
431
- try:
432
- hops = K.multi_hop(con, start_id, max_depth=depth)
433
- except Exception as e:
434
- return "遍历出错 (起点=%s): %s" % (start_id, str(e))
435
-
436
- if not hops or (len(hops) == 1 and hops[0][1] == 0):
437
- return "从 '%s' (解析为 %s) 出发无可达节点。可能该符号不在图谱中, 或无出边。" % (sym, start_id)
438
-
439
- # 3) 按 depth 分组, 取每层的实体详情 (最多展示)
440
- from collections import defaultdict
441
- by_depth = defaultdict(list)
442
- for eid, d in hops:
443
- by_depth[d].append(eid)
444
-
445
- out = ['=== 多跳遍历: %s (解析为 %s) ===' % (sym, start_id)]
446
- out.append('可达 %d 个实体, 最大 %d 跳:' % (len(hops), max(by_depth.keys())))
447
- out.append('')
448
- for d in sorted(by_depth.keys()):
449
- eids = by_depth[d]
450
- out.append('[第 %d 跳] %d 个实体:' % (d, len(eids)))
451
- # 拉每个实体的 canonical/cn 信息
452
- shown = 0
453
- for eid in eids:
454
- if shown >= 12:
455
- out.append(' ... 还有 %d 个' % (len(eids) - 12))
456
- break
457
- info = con.execute(
458
- "SELECT type, canonical, cn FROM entities WHERE id = ?", [eid]
459
- ).fetchone()
460
- if info:
461
- t, c, cn = info
462
- label = '%s / %s' % (c, cn) if cn and cn != c else c
463
- out.append(' %-14s %-12s %s' % ('[%s]' % t, eid[:40], label))
464
- else:
465
- out.append(' %-14s %s' % ('[edge]', eid[:60]))
466
- shown += 1
467
- out.append('')
468
- return '\n'.join(out).strip()
469
- finally:
470
- try: con.close()
471
- except Exception: pass
472
-
473
-
474
- def _tool_search_field(field):
475
- from common.paths import DATA_INDEX_DIR
476
- from kg_duckdb import get_db
477
- con = get_db()
478
- rows = con.execute(
479
- "SELECT field, title, count, sample_files FROM fields WHERE LOWER(field) LIKE ? OR LOWER(title) LIKE ? LIMIT 20",
480
- ['%' + field.lower() + '%', '%' + field.lower() + '%']
481
- ).fetchall()
482
- con.close()
483
- if not rows:
484
- return "未找到字段 '%s'" % field
485
- lines = ["字段搜索 '%s' (%d个匹配)" % (field, len(rows))]
486
- for f, t, c, s in rows:
487
- lines.append(" %s -> %s (用了%d次)" % (f, t, c))
488
- return '\n'.join(lines)
489
-
490
-
491
- def _tool_list_modules(platform=''):
492
- from kg_duckdb import get_db
493
- con = get_db()
494
- if platform == 'web':
495
- rows = con.execute("SELECT dir, cn, project FROM modules WHERE project LIKE '%fywl%' ORDER BY dir").fetchall()
496
- elif platform == 'app':
497
- rows = con.execute("SELECT dir, cn, project FROM modules WHERE project LIKE '%Carmg%' OR project LIKE '%H5%' ORDER BY dir").fetchall()
498
- else:
499
- rows = con.execute("SELECT dir, cn, project FROM modules ORDER BY project, dir").fetchall()
500
- con.close()
501
- if not rows:
502
- return "无模块数据"
503
- lines = ["模块清单 (%d个)" % len(rows)]
504
- cur = ""
505
- for d, cn, proj in rows:
506
- if proj != cur:
507
- cur = proj
508
- lines.append("[%s]" % proj)
509
- lines.append(" %s -> %s" % (d, cn))
510
- return '\n'.join(lines)
511
-
512
-
513
- def _execute_tool(name, arguments):
514
- """Execute a tool and return result text."""
515
- _ensure_imports()
516
-
517
- if name == "search_code":
518
- kw = arguments.get("keyword", "")
519
- plat = arguments.get("platform") or ""
520
- return _capture(_search.search_keywords, kw, plat) or "无匹配结果"
521
-
522
- elif name == "search_api":
523
- return _capture(_search.search_api, arguments.get("keyword", "")) or "无匹配结果"
524
-
525
- elif name == "search_prd":
526
- return _capture(_search.search_prds, arguments.get("keyword", "")) or "无匹配PRD"
527
-
528
- elif name == "get_impact":
529
- return _capture(_search.search_impact, arguments.get("endpoint", "")) or "未找到匹配端点"
530
-
531
- elif name == "context_360":
532
- return _capture(_search.search_context360, arguments.get("symbol", "")) or "未找到匹配符号"
533
-
534
- elif name == "context_pack":
535
- kw = arguments.get("keyword", "")
536
- plat = arguments.get("platform", "")
537
- pt = arguments.get("page_type", "")
538
- role = arguments.get("role", "")
539
- full = _capture(_context._compute_and_print, kw, plat, pt)
540
- if not full:
541
- return "无匹配上下文"
542
- if role:
543
- return _filter_by_role(full, role)
544
- return full
545
-
546
- elif name == "fill_prototype":
547
- # 调用 fill_prototype.py 的 main 逻辑 (捕获 stdout)
548
- kw = arguments.get("keyword", "")
549
- plat = arguments.get("platform", "web")
550
- import subprocess as _sp
551
- script = os.path.join(SCRIPTS_DIR, 'fill_prototype.py')
552
- try:
553
- r = _sp.run([sys.executable, script, kw, '--platform', plat],
554
- capture_output=True, text=True, encoding='utf-8',
555
- errors='replace', timeout=30, cwd=SCRIPTS_DIR)
556
- return r.stdout or "fill_prototype 无输出 (可能索引缺失)"
557
- except Exception as e:
558
- return "[error] fill_prototype: %s" % str(e)
559
-
560
- elif name == "get_design_system":
561
- # 返回 DESIGN.md 内容 (若不存在则生成)
562
- import subprocess as _sp
563
- design_md = os.path.join(BASE_DIR, 'data', 'design', 'DESIGN.md')
564
- if not os.path.isfile(design_md):
565
- gen_script = os.path.join(SCRIPTS_DIR, 'gen_design_doc.py')
566
- if os.path.isfile(gen_script):
567
- _sp.run([sys.executable, gen_script], capture_output=True, timeout=30)
568
- if os.path.isfile(design_md):
569
- content = open(design_md, encoding='utf-8').read()
570
- # 若指定了 platform, 只返回该平台的布局指纹段
571
- plat = arguments.get("platform")
572
- if plat:
573
- lines = content.split('\n')
574
- result = []
575
- in_section = False
576
- for line in lines:
577
- if line.startswith('### ') and plat.lower() in line.lower():
578
- in_section = True
579
- elif line.startswith('### ') and in_section:
580
- break
581
- if in_section:
582
- result.append(line)
583
- return '\n'.join(result) if result else content
584
- return content
585
- return "DESIGN.md 不存在且生成失败。请手动跑: python gen_design_doc.py"
586
-
587
- elif name == "table_impact":
588
- # 改表影响分析: 表 → entity_table_map → Entity → 代码图谱 Controller/接口
589
- from common.paths import DATA_INDEX_DIR as _idx
590
- import duckdb as _ddb
591
- db_path = str(_idx / 'kg_db.duckdb')
592
- if not os.path.isfile(db_path):
593
- return "数据层图谱未构建。请先跑: python .qoder/scripts/kg_build_db.py"
594
- table = arguments.get("table", "").strip()
595
- try:
596
- con = _ddb.connect(db_path, read_only=True)
597
- except Exception as e:
598
- return "数据层图谱暂时不可用(可能正在重建): %s。稍后重试。" % str(e)[:60]
599
- try:
600
- # 1. 表 → Entity
601
- ents = con.execute(
602
- "SELECT DISTINCT entity_class FROM entity_table_map WHERE table_name = ?",
603
- [table]
604
- ).fetchall()
605
- ent_list = [r[0] for r in ents]
606
- if not ent_list:
607
- return "表 '%s' 在 entity_table_map 中无映射 (可能该表无 @TableName 注解的 Entity)" % table
608
- lines = ["改表影响分析: %s" % table]
609
- lines.append(" 对应 Entity: %s" % ', '.join(ent_list))
610
- # 2. 表的真实列 (给个概览)
611
- cols = con.execute(
612
- "SELECT column_name, data_type, column_comment FROM db_columns "
613
- "WHERE table_name = ? LIMIT 10", [table]
614
- ).fetchall()
615
- if cols:
616
- lines.append(" 表字段 (%d+):" % len(cols))
617
- for cn, dt, cc in cols:
618
- lines.append(" %s (%s) %s" % (cn, dt, (cc or '')[:30]))
619
- # 3. Entity → 代码图谱 (attach kg.duckdb 查接口/Controller)
620
- try:
621
- con.execute("ATTACH '%s' AS code (READ_ONLY)" % str(_idx / 'kg.duckdb'))
622
- lines.append(" 影响的接口/控制器:")
623
- found = 0
624
- for ec in ent_list:
625
- refs = con.execute(
626
- "SELECT type, canonical FROM code.entities "
627
- "WHERE contains(canonical, ?) AND type IN ('CONTROLLER','ENDPOINT') "
628
- "LIMIT 12", [ec]
629
- ).fetchall()
630
- for t, c in refs:
631
- lines.append(" [%s] %s" % (t, c))
632
- found += 1
633
- if not found:
634
- lines.append(" (代码图谱中未找到引用 %s 的接口)" % ent_list[0])
635
- con.execute("DETACH code")
636
- except Exception as e:
637
- lines.append(" (代码图谱查询失败: %s)" % str(e)[:60])
638
- return '\n'.join(lines)
639
- finally:
640
- con.close()
641
-
642
- elif name == "coverage_matrix":
643
- from common.graph_traverse import CodeGraph
644
- from common.paths import DATA_INDEX_DIR as _idx
645
- g = CodeGraph(str(_idx))
646
- result = g.coverage_matrix()
647
- return result or "无法生成覆盖矩阵"
648
-
649
- elif name == "feature_overview":
650
- from common.graph_traverse import CodeGraph
651
- from common.paths import DATA_INDEX_DIR as _idx
652
- g = CodeGraph(str(_idx))
653
- result = g.feature_overview(arguments.get("feature", ""))
654
- if result and '不存在' not in result:
655
- return result
656
- # DuckDB 兜底: entity-registry.json 缺失时, 用 DuckDB 直接查
657
- feat = arguments.get("feature", "")
658
- fl = feat.lower()
659
- try:
660
- import duckdb as _ddb
661
- con = _ddb.connect(str(_idx / 'kg.duckdb'), read_only=True)
662
- # 端点
663
- eps = con.execute(
664
- "SELECT canonical FROM entities WHERE type='ENDPOINT' AND "
665
- "(lower(canonical) LIKE ? OR lower(canonical) LIKE ?) LIMIT 15",
666
- ['%'+fl+'%', '%'+feat+'%']).fetchall()
667
- # 按钮
668
- btns = con.execute(
669
- "SELECT canonical FROM entities WHERE type='BUTTON' AND "
670
- "lower(canonical) LIKE ? LIMIT 10", ['%'+fl+'%']).fetchall()
671
- con.close()
672
- lines = ['功能画像: %s (DuckDB兜底)' % feat]
673
- if eps:
674
- lines.append('端点(%d):' % len(eps))
675
- for (e,) in eps[:10]:
676
- lines.append(' %s' % e)
677
- if btns:
678
- lines.append('按钮(%d):' % len(btns))
679
- for (b,) in btns[:5]:
680
- lines.append(' %s' % b)
681
- if not eps and not btns:
682
- lines.append(' (无匹配。换关键词重试)')
683
- return chr(10).join(lines)
684
- except Exception as e:
685
- return '未找到功能 "%s" (DuckDB兜底也失败: %s)' % (feat, str(e)[:60])
686
-
687
- elif name == "get_workflow":
688
- return _tool_get_workflow(arguments.get("module", ""))
689
-
690
- elif name == "search_wiki":
691
- return _tool_search_wiki(arguments.get("keyword", ""))
692
-
693
- elif name == "multi_hop":
694
- return _tool_multi_hop(arguments.get("symbol", ""),
695
- arguments.get("max_depth", 3))
696
-
697
- elif name == "search_field":
698
- return _tool_search_field(arguments.get("field", ""))
699
-
700
- elif name == "list_modules":
701
- return _tool_list_modules(arguments.get("platform", ""))
702
-
703
- return "未知工具: %s" % name
704
-
705
-
706
- # ── MCP Stdio Protocol (JSON-RPC 2.0) ─────────────────────────────────
707
-
708
- def _send(msg):
709
- """Send a JSON-RPC message on stdout (MCP transport)."""
710
- _real_stdout.write(json.dumps(msg, ensure_ascii=False) + '\n')
711
- _real_stdout.flush()
712
-
713
-
714
- async def _handle(req):
715
- """Handle a single JSON-RPC request."""
716
- method = req.get("method", "")
717
- req_id = req.get("id")
718
- params = req.get("params", {})
719
-
720
- if method == "initialize":
721
- _send({
722
- "jsonrpc": "2.0", "id": req_id,
723
- "result": {
724
- "protocolVersion": "2024-11-05",
725
- "capabilities": {"tools": {}},
726
- "serverInfo": {"name": "qoder-knowledge-graph", "version": "1.0.0"},
727
- }
728
- })
729
-
730
- elif method == "notifications/initialized":
731
- pass # no response needed for notifications
732
-
733
- elif method == "tools/list":
734
- _send({"jsonrpc": "2.0", "id": req_id, "result": {"tools": TOOLS}})
735
-
736
- elif method == "tools/call":
737
- tool_name = params.get("name", "")
738
- arguments = params.get("arguments", {})
739
- try:
740
- result_text = await asyncio.get_event_loop().run_in_executor(
741
- None, _execute_tool, tool_name, arguments)
742
- _send({
743
- "jsonrpc": "2.0", "id": req_id,
744
- "result": {
745
- "content": [{"type": "text", "text": result_text}],
746
- "isError": False,
747
- }
748
- })
749
- except Exception as e:
750
- _send({
751
- "jsonrpc": "2.0", "id": req_id,
752
- "result": {
753
- "content": [{"type": "text", "text": "[error] %s" % str(e)}],
754
- "isError": True,
755
- }
756
- })
757
-
758
- elif method == "ping":
759
- _send({"jsonrpc": "2.0", "id": req_id, "result": {}})
760
-
761
- else:
762
- if req_id is not None:
763
- _send({"jsonrpc": "2.0", "id": req_id,
764
- "error": {"code": -32601, "message": "Method not found: %s" % method}})
765
-
766
-
767
- def main():
768
- """Read JSON-RPC requests from stdin (synchronous readline), dispatch.
769
-
770
- Synchronous is more reliable than asyncio on Windows Proactor event loop.
771
- Tool execution runs in a thread pool to avoid blocking on long queries.
772
- """
773
- import threading
774
-
775
- while True:
776
- try:
777
- line = sys.stdin.buffer.readline()
778
- except (EOFError, OSError):
779
- break
780
- if not line:
781
- break
782
- line = line.strip()
783
- if not line:
784
- continue
785
- try:
786
- req = json.loads(line.decode('utf-8'))
787
- # Handle synchronously (tools are fast enough; context_pack ~1-2s)
788
- loop = asyncio.new_event_loop()
789
- loop.run_until_complete(_handle(req))
790
- loop.close()
791
- except json.JSONDecodeError:
792
- pass
793
- except Exception as e:
794
- print('[kg-mcp] error: %s' % str(e), file=sys.stderr)
795
-
796
-
797
- if __name__ == "__main__":
798
- try:
799
- main()
800
- except KeyboardInterrupt:
801
- pass