@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
@@ -0,0 +1,478 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
4
+ import os as _o, sys as _s
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
11
+ if _cp not in _s.path: _s.path.insert(0, _cp)
12
+ from bootstrap import setup; setup()
13
+ from foundation.core.paths import get_repo_root
14
+
15
+ """
16
+ Knowledge Graph MCP Server for QoderWork (pure Python, no SDK dependency).
17
+
18
+ Exposes the QODER knowledge graph as MCP tools that QoderWork's AI agent
19
+ can call directly. Uses the MCP stdio protocol (JSON-RPC 2.0 over stdin/stdout)
20
+ implemented from scratch — no external dependencies beyond the standard library.
21
+
22
+ Registered in ~/.qoderwork/mcp.json as "qoder-knowledge-graph".
23
+ Tools surface as mcp__qoder-knowledge-graph__<tool> to the AI.
24
+
25
+ v3.1 重构: 用 @tool 装饰器 + ToolRegistryMeta 元类, 消除:
26
+ - 手写 TOOLS 列表 (78-253 行)
27
+ - _execute_tool 的 17 个 if/elif 分支 (386-576 行)
28
+ 两处合一, 新增工具只需加一个 @tool 方法。
29
+ """
30
+ import io
31
+ import os
32
+ import sys
33
+ import json
34
+ import subprocess
35
+
36
+ # Ensure UTF-8 output
37
+ try:
38
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
39
+ sys.stderr.reconfigure(encoding='utf-8', errors='replace')
40
+ except Exception:
41
+ pass
42
+
43
+ # Locate the scripts dir
44
+ SCRIPTS_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # scripts/ 根
45
+ BASE_DIR = os.path.dirname(os.path.dirname(SCRIPTS_DIR)) # repo 根
46
+ sys.path.insert(0, SCRIPTS_DIR)
47
+ _KG_DIR = os.path.dirname(os.path.abspath(__file__))
48
+ if _KG_DIR not in sys.path: sys.path.insert(0, _KG_DIR)
49
+
50
+
51
+ # ── stdout 捕获辅助 ───────────────────────────────────────────────────
52
+ # 底层脚本 (search_index/context_pack) 用 print 输出, MCP 需要捕获为字符串返回。
53
+
54
+ def _capture(func, *args, **kwargs):
55
+ """Run a function, capture its stdout.
56
+
57
+ v3.0: sys.stdout 在 run() 后已是 stderr (基类 _setup_streams 重定向),
58
+ 这里捕获时临时换回 buf, finally 还原。
59
+ """
60
+ old = sys.stdout
61
+ buf = io.StringIO()
62
+ sys.stdout = buf
63
+ try:
64
+ func(*args, **kwargs)
65
+ except Exception as e:
66
+ buf.write('\n[error] %s' % str(e))
67
+ finally:
68
+ sys.stdout = old
69
+ return buf.getvalue().strip()
70
+
71
+
72
+ # Lazy imports (首次调用工具时才 import, 加快启动)
73
+ _search = None
74
+ _context = None
75
+
76
+ def _ensure_imports():
77
+ global _search, _context
78
+ if _search is None:
79
+ from domain.kg.search import search_index as _search
80
+ if _context is None:
81
+ from domain.kg.search import context_pack as _context
82
+
83
+
84
+ # ── MCP Server (继承 BaseMCPServer, @tool 装饰器自动分发) ──────────────
85
+
86
+ from foundation.protocol.mcp_base import BaseMCPServer, tool
87
+
88
+
89
+ class KnowledgeGraphMCPServer(BaseMCPServer):
90
+ """知识图谱 MCP Server。
91
+
92
+ v3.1: 每个工具用 @tool 装饰, 元类自动收集 TOOLS + 自动 handle_tool 分发。
93
+ 新增工具 = 加一个 @tool 方法, 无需改 TOOLS 列表或 if 链。
94
+ """
95
+
96
+ SERVER_NAME = "qoder-knowledge-graph"
97
+ SERVER_VERSION = "1.1.0"
98
+
99
+ # ── 搜索类 (转发到 search_index) ──────────────────────────────────
100
+
101
+ @tool("search_code",
102
+ "搜索代码: 按关键词查找代码文件在哪。例: search_code(keyword='考勤') 或 search_code(keyword='车辆', platform='web')",
103
+ {"type": "object", "properties": {
104
+ "keyword": {"type": "string", "description": "搜索关键词(中文或英文)"},
105
+ "platform": {"type": "string", "enum": ["web", "app"], "description": "平台: web=PC管理端, app=移动端。可选"},
106
+ }, "required": ["keyword"]})
107
+ def _t_search_code(self, args):
108
+ _ensure_imports()
109
+ kw = args.get("keyword", "")
110
+ plat = args.get("platform") or ""
111
+ return _capture(_search.search_keywords, kw, plat) or "无匹配结果"
112
+
113
+ @tool("search_api",
114
+ "搜索API端点: 按关键词查找后端接口。例: search_api(keyword='salary')",
115
+ {"type": "object", "properties": {
116
+ "keyword": {"type": "string", "description": "API关键词"},
117
+ }, "required": ["keyword"]})
118
+ def _t_search_api(self, args):
119
+ _ensure_imports()
120
+ return _capture(_search.search_api, args.get("keyword", "")) or "无匹配结果"
121
+
122
+ @tool("search_prd",
123
+ "搜索PRD: 查已有需求文档,防止重复造轮子。例: search_prd(keyword='考勤')",
124
+ {"type": "object", "properties": {
125
+ "keyword": {"type": "string", "description": "PRD关键词"},
126
+ }, "required": ["keyword"]})
127
+ def _t_search_prd(self, args):
128
+ _ensure_imports()
129
+ return _capture(_search.search_prds, args.get("keyword", "")) or "无匹配PRD"
130
+
131
+ @tool("get_impact",
132
+ "影响分析: 改某接口/路径会影响哪些前端页面和按钮?评估改动风险。例: get_impact(endpoint='/asset') 或 get_impact(endpoint='export')",
133
+ {"type": "object", "properties": {
134
+ "endpoint": {"type": "string", "description": "API端点路径或关键词, 如 /asset/list 或 export"},
135
+ }, "required": ["endpoint"]})
136
+ def _t_get_impact(self, args):
137
+ _ensure_imports()
138
+ return _capture(_search.search_impact, args.get("endpoint", "")) or "未找到匹配端点"
139
+
140
+ @tool("context_360",
141
+ "360度视图: 查看符号(端点/函数/处理器)的全部关联信息。例: context_360(symbol='handleExport') 或 context_360(symbol='/system/role')",
142
+ {"type": "object", "properties": {
143
+ "symbol": {"type": "string", "description": "符号名: 端点/函数名/处理器名"},
144
+ }, "required": ["symbol"]})
145
+ def _t_context_360(self, args):
146
+ _ensure_imports()
147
+ return _capture(_search.search_context360, args.get("symbol", "")) or "未找到匹配符号"
148
+
149
+ @tool("context_pack",
150
+ "上下文打包: 一次返回关键词相关的全部上下文(代码+页面+字段+PRD+API+布局指纹)。避免来回查。例: context_pack(keyword='车辆', platform='web')",
151
+ {"type": "object", "properties": {
152
+ "keyword": {"type": "string", "description": "业务关键词"},
153
+ "platform": {"type": "string", "enum": ["web", "app"], "description": "平台: web或app。可选"},
154
+ "page_type": {"type": "string", "description": "页面类型: table-page/form-page/detail-page。可选"},
155
+ "role": {"type": "string", "enum": ["pm", "design", "dev", "test", "admin"],
156
+ "description": "角色裁剪: pm=字段+PRD+API, design=页面+布局+风格, dev=代码+API, test=代码+PRD+API。可选"},
157
+ }, "required": ["keyword"]})
158
+ def _t_context_pack(self, args):
159
+ _ensure_imports()
160
+ kw = args.get("keyword", "")
161
+ plat = args.get("platform", "")
162
+ pt = args.get("page_type", "")
163
+ role = args.get("role", "")
164
+ full = _capture(_context._compute_and_print, kw, plat, pt)
165
+ if not full:
166
+ return "无匹配上下文"
167
+ if role:
168
+ try:
169
+ from foundation.identity.roles import filter_by_role
170
+ return filter_by_role(full, role)
171
+ except Exception:
172
+ return full
173
+ return full
174
+
175
+ # ── 知识图谱查询类 (DuckDB 直查) ──────────────────────────────────
176
+
177
+ @tool("get_workflow",
178
+ "业务流程: 查看一个模块的完整操作链(query→create→audit→archive→export→delete)。数据源: DuckDB workflows 表。优先匹配英文模块名, 中文输入会返回候选模块列表。例: get_workflow(module='assets') 或 get_workflow(module='cost')",
179
+ {"type": "object", "properties": {
180
+ "module": {"type": "string", "description": "模块名(英文优先): assets/cost/hr/maintenance/operation/qualityAndSafety/veh/settings/dashboard/flow/systems/monitor/quasafe"},
181
+ }, "required": ["module"]})
182
+ def _t_get_workflow(self, args):
183
+ from domain.kg.kg_capabilities import workflow_query
184
+ from domain.kg.storage.kg_duckdb import get_db
185
+ q = (args.get("module") or '').strip()
186
+ if not q:
187
+ return "请提供模块名。可选: assets/cost/hr/maintenance/operation/qualityAndSafety/veh/settings/dashboard/flow/systems/monitor/quasafe"
188
+ con = get_db()
189
+ try:
190
+ return workflow_query(con, q)
191
+ finally:
192
+ try: con.close()
193
+ except Exception: pass
194
+
195
+ @tool("multi_hop",
196
+ "知识图谱多跳遍历: 从一个符号出发, 沿edges递归N跳, 找出全部可达实体(页面→接口→控制器→测试)。知识图谱核心能力。支持别名解析(传中文/端点/函数名自动转entity_id)。例: multi_hop(symbol='资产管理') 或 multi_hop(symbol='/asset/list', max_depth=3)",
197
+ {"type": "object", "properties": {
198
+ "symbol": {"type": "string", "description": "起点符号: 可传entity_id、别名(资产管理)、端点(/asset/list)或函数名"},
199
+ "max_depth": {"type": "integer", "default": 3, "description": "最大跳数(1-5), 默认3。越大越全但越慢"},
200
+ }, "required": ["symbol"]})
201
+ def _t_multi_hop(self, args):
202
+ from domain.kg.kg_capabilities import hop_query
203
+ from domain.kg.storage.kg_duckdb import get_db, multi_hop
204
+ sym = (args.get("symbol") or '').strip()
205
+ if not sym:
206
+ return "请提供起点符号 (entity_id / 端点 / 函数名 / 中文别名)"
207
+ try:
208
+ depth = int(args.get("max_depth", 3))
209
+ except (TypeError, ValueError):
210
+ depth = 3
211
+ depth = max(1, min(5, depth))
212
+ con = get_db()
213
+ try:
214
+ return hop_query(con, sym, depth, multi_hop_fn=multi_hop)
215
+ finally:
216
+ try: con.close()
217
+ except Exception: pass
218
+
219
+ @tool("search_wiki",
220
+ "搜索Repo Wiki: 查Qoder IDE生成的模块级语义文档(架构/职责/依赖), 比读源码省token。写PRD前优先查。例: search_wiki(keyword='考勤') 或 search_wiki(keyword='权限')",
221
+ {"type": "object", "properties": {
222
+ "keyword": {"type": "string", "description": "Wiki关键词(中/英文)"},
223
+ }, "required": ["keyword"]})
224
+ def _t_search_wiki(self, args):
225
+ from domain.kg.kg_capabilities import wiki_query
226
+ kw = (args.get("keyword") or '').strip()
227
+ if not kw:
228
+ return "请提供搜索关键词"
229
+ return wiki_query(kw)
230
+
231
+ @tool("search_field",
232
+ "搜索字段: 查某个字段在哪些页面用过、中文标题是什么。例: search_field(field='plateNo') 或 search_field(field='车牌号')",
233
+ {"type": "object", "properties": {
234
+ "field": {"type": "string", "description": "字段名(英文field或中文标题)"},
235
+ }, "required": ["field"]})
236
+ def _t_search_field(self, args):
237
+ from domain.kg.storage.kg_duckdb import get_db
238
+ field = (args.get("field") or '').strip()
239
+ con = get_db()
240
+ try:
241
+ rows = con.execute(
242
+ "SELECT field, title, count, sample_files FROM fields "
243
+ "WHERE LOWER(field) LIKE ? OR LOWER(title) LIKE ? LIMIT 20",
244
+ ['%' + field.lower() + '%', '%' + field.lower() + '%']
245
+ ).fetchall()
246
+ finally:
247
+ con.close()
248
+ if not rows:
249
+ return "未找到字段 '%s'" % field
250
+ lines = ["字段搜索 '%s' (%d个匹配)" % (field, len(rows))]
251
+ for f, t, c, s in rows:
252
+ lines.append(" %s -> %s (用了%d次)" % (f, t, c))
253
+ return '\n'.join(lines)
254
+
255
+ @tool("list_modules",
256
+ "模块清单: 列出项目所有业务模块及其中文名。例: list_modules() 或 list_modules(platform='web')",
257
+ {"type": "object", "properties": {
258
+ "platform": {"type": "string", "enum": ["web", "app"], "description": "平台过滤。可选"},
259
+ }})
260
+ def _t_list_modules(self, args):
261
+ from domain.kg.storage.kg_duckdb import get_db
262
+ platform = args.get("platform", "")
263
+ con = get_db()
264
+ try:
265
+ if platform == 'web':
266
+ rows = con.execute("SELECT dir, cn, project FROM modules WHERE project LIKE '%fywl%' ORDER BY dir").fetchall()
267
+ elif platform == 'app':
268
+ rows = con.execute("SELECT dir, cn, project FROM modules WHERE project LIKE '%Carmg%' OR project LIKE '%H5%' ORDER BY dir").fetchall()
269
+ else:
270
+ rows = con.execute("SELECT dir, cn, project FROM modules ORDER BY project, dir").fetchall()
271
+ finally:
272
+ con.close()
273
+ if not rows:
274
+ return "无模块数据"
275
+ lines = ["模块清单 (%d个)" % len(rows)]
276
+ cur = ""
277
+ for d, cn, proj in rows:
278
+ if proj != cur:
279
+ cur = proj
280
+ lines.append("[%s]" % proj)
281
+ lines.append(" %s -> %s" % (d, cn))
282
+ return '\n'.join(lines)
283
+
284
+ @tool("coverage_matrix",
285
+ "功能×测试覆盖矩阵: 哪些功能有测试覆盖, 哪些是空白? 标出回归测试盲区。例: coverage_matrix()",
286
+ {"type": "object", "properties": {}})
287
+ def _t_coverage_matrix(self, args):
288
+ from domain.kg.graph.graph_traverse import CodeGraph
289
+ from foundation.core.paths import DATA_INDEX_DIR
290
+ g = CodeGraph(str(DATA_INDEX_DIR))
291
+ return g.coverage_matrix() or "无法生成覆盖矩阵"
292
+
293
+ @tool("feature_overview",
294
+ "功能画像: 一个功能的完整画像(端点+按钮+测试+页面)。例: feature_overview(feature='资产管理') 或 feature_overview(feature='asset')",
295
+ {"type": "object", "properties": {
296
+ "feature": {"type": "string", "description": "功能名(中/英文都行): 资产管理/asset/hr"},
297
+ }, "required": ["feature"]})
298
+ def _t_feature_overview(self, args):
299
+ from domain.kg.graph.graph_traverse import CodeGraph
300
+ from foundation.core.paths import DATA_INDEX_DIR
301
+ g = CodeGraph(str(DATA_INDEX_DIR))
302
+ result = g.feature_overview(args.get("feature", ""))
303
+ if result and '不存在' not in result:
304
+ return result
305
+ # DuckDB 兜底: entity-registry.json 缺失时, 用 DuckDB 直接查
306
+ feat = args.get("feature", "")
307
+ fl = feat.lower()
308
+ try:
309
+ import duckdb as _ddb
310
+ con = _ddb.connect(str(DATA_INDEX_DIR / 'kg.duckdb'), read_only=True)
311
+ eps = con.execute(
312
+ "SELECT canonical FROM entities WHERE type='ENDPOINT' AND "
313
+ "(lower(canonical) LIKE ? OR lower(canonical) LIKE ?) LIMIT 15",
314
+ ['%' + fl + '%', '%' + feat + '%']).fetchall()
315
+ btns = con.execute(
316
+ "SELECT canonical FROM entities WHERE type='BUTTON' AND "
317
+ "lower(canonical) LIKE ? LIMIT 10", ['%' + fl + '%']).fetchall()
318
+ con.close()
319
+ lines = ['功能画像: %s (DuckDB兜底)' % feat]
320
+ if eps:
321
+ lines.append('端点(%d):' % len(eps))
322
+ for (e,) in eps[:10]:
323
+ lines.append(' %s' % e)
324
+ if btns:
325
+ lines.append('按钮(%d):' % len(btns))
326
+ for (b,) in btns[:5]:
327
+ lines.append(' %s' % b)
328
+ if not eps and not btns:
329
+ lines.append(' (无匹配。换关键词重试)')
330
+ return chr(10).join(lines)
331
+ except Exception as e:
332
+ return '未找到功能 "%s" (DuckDB兜底也失败: %s)' % (feat, str(e)[:60])
333
+
334
+ # ── 跨层影响分析 ──────────────────────────────────────────────────
335
+
336
+ @tool("table_impact",
337
+ "改表影响分析: 改某数据库表会影响哪些 Entity 类和接口? 跨层遍历(数据层→代码层)。例: table_impact(table='quality_case') → 找到 QualityCase 类 + 影响的 Controller/接口",
338
+ {"type": "object", "properties": {
339
+ "table": {"type": "string", "description": "数据库表名 (如 quality_case)"},
340
+ }, "required": ["table"]})
341
+ def _t_table_impact(self, args):
342
+ from foundation.core.paths import DATA_INDEX_DIR
343
+ import duckdb as _ddb
344
+ db_path = str(DATA_INDEX_DIR / 'kg_db.duckdb')
345
+ if not os.path.isfile(db_path):
346
+ return "数据层图谱未构建。请先跑: python .qoder/scripts/domain/kg/build/kg_build_db.py"
347
+ table = (args.get("table") or "").strip()
348
+ try:
349
+ con = _ddb.connect(db_path, read_only=True)
350
+ except Exception as e:
351
+ return "数据层图谱暂时不可用(可能正在重建): %s。稍后重试。" % str(e)[:60]
352
+ try:
353
+ ents = con.execute(
354
+ "SELECT DISTINCT entity_class FROM entity_table_map WHERE table_name = ?",
355
+ [table]
356
+ ).fetchall()
357
+ ent_list = [r[0] for r in ents]
358
+ if not ent_list:
359
+ return "表 '%s' 在 entity_table_map 中无映射 (可能该表无 @TableName 注解的 Entity)" % table
360
+ lines = ["改表影响分析: %s" % table]
361
+ lines.append(" 对应 Entity: %s" % ', '.join(ent_list))
362
+ cols = con.execute(
363
+ "SELECT column_name, data_type, column_comment FROM db_columns "
364
+ "WHERE table_name = ? LIMIT 10", [table]
365
+ ).fetchall()
366
+ if cols:
367
+ lines.append(" 表字段 (%d+):" % len(cols))
368
+ for cn, dt, cc in cols:
369
+ lines.append(" %s (%s) %s" % (cn, dt, (cc or '')[:30]))
370
+ try:
371
+ con.execute("ATTACH '%s' AS code (READ_ONLY)" % str(DATA_INDEX_DIR / 'kg.duckdb'))
372
+ lines.append(" 影响的接口/控制器:")
373
+ found = 0
374
+ for ec in ent_list:
375
+ refs = con.execute(
376
+ "SELECT type, canonical FROM code.entities "
377
+ "WHERE contains(canonical, ?) AND type IN ('CONTROLLER','ENDPOINT') "
378
+ "LIMIT 12", [ec]
379
+ ).fetchall()
380
+ for t, c in refs:
381
+ lines.append(" [%s] %s" % (t, c))
382
+ found += 1
383
+ if not found:
384
+ lines.append(" (代码图谱中未找到引用 %s 的接口)" % ent_list[0])
385
+ con.execute("DETACH code")
386
+ except Exception as e:
387
+ lines.append(" (代码图谱查询失败: %s)" % str(e)[:60])
388
+ return '\n'.join(lines)
389
+ finally:
390
+ con.close()
391
+
392
+ # ── 原型/设计类 (子进程调用) ──────────────────────────────────────
393
+
394
+ @tool("fill_prototype",
395
+ "原型预填: 用真实代码数据(表格列/表单字段/布局参数)自动填模板,输出80%完成的HTML草稿+数据清单。AI只需微调20%。出原型前必须调。例: fill_prototype(keyword='营业外合同', platform='web')",
396
+ {"type": "object", "properties": {
397
+ "keyword": {"type": "string", "description": "业务关键词"},
398
+ "platform": {"type": "string", "enum": ["web", "app"], "description": "平台: web或app。默认web"},
399
+ }, "required": ["keyword"]})
400
+ def _t_fill_prototype(self, args):
401
+ from foundation.core.paths import find_script
402
+ kw = args.get("keyword", "")
403
+ plat = args.get("platform", "web")
404
+ script = find_script('fill_prototype.py')
405
+ if script is None:
406
+ return "fill_prototype.py 未找到 (脚本迁移后路径未更新)"
407
+ try:
408
+ r = subprocess.run([sys.executable, str(script), kw, '--platform', plat],
409
+ capture_output=True, text=True, encoding='utf-8',
410
+ errors='replace', timeout=30, cwd=str(script.parent))
411
+ return r.stdout or "fill_prototype 无输出 (可能索引缺失)"
412
+ except Exception as e:
413
+ return "[error] fill_prototype: %s" % str(e)
414
+
415
+ @tool("get_design_system",
416
+ "设计系统查询: 返回项目完整设计规范(布局指纹/颜色token/页面模式/组件表/图标规则/behaviors)。出任何原型前必须先调。例: get_design_system() 或 get_design_system(platform='web')",
417
+ {"type": "object", "properties": {
418
+ "platform": {"type": "string", "enum": ["web", "app"], "description": "平台: 只返回指定平台的指纹。不传则返回全部"},
419
+ }})
420
+ def _t_get_design_system(self, args):
421
+ from foundation.core.paths import find_script
422
+ design_md = os.path.join(BASE_DIR, 'data', 'design', 'DESIGN.md')
423
+ if not os.path.isfile(design_md):
424
+ gen_script = find_script('gen_design_doc.py')
425
+ if gen_script is not None:
426
+ subprocess.run([sys.executable, str(gen_script)], capture_output=True, timeout=30)
427
+ if os.path.isfile(design_md):
428
+ content = open(design_md, encoding='utf-8').read()
429
+ plat = args.get("platform")
430
+ if plat:
431
+ lines = content.split('\n')
432
+ result = []
433
+ in_section = False
434
+ for line in lines:
435
+ if line.startswith('### ') and plat.lower() in line.lower():
436
+ in_section = True
437
+ elif line.startswith('### ') and in_section:
438
+ break
439
+ if in_section:
440
+ result.append(line)
441
+ return '\n'.join(result) if result else content
442
+ return content
443
+ return "DESIGN.md 不存在且生成失败。请手动跑: python .qoder/scripts/design/gen_design_doc.py"
444
+
445
+ @tool("semantic_search",
446
+ "语义搜索(概念检索): 用自然语言找代码/API/功能,不依赖精确关键词。查'报销'能命中 expense/reimburse。需先构建embedding(管理员跑 kg_semantic.py build-embeddings)。例: semantic_search(query='报销流程') 或 semantic_search(query='车辆调度')",
447
+ {"type": "object", "properties": {
448
+ "query": {"type": "string", "description": "自然语言查询(中/英文均可)"},
449
+ "top_k": {"type": "integer", "default": 10, "description": "返回前N条, 默认10"},
450
+ }, "required": ["query"]})
451
+ def _t_semantic_search(self, args):
452
+ from domain.kg.graph.kg_semantic import semantic_search, embedding_status
453
+ query = (args.get("query") or '').strip()
454
+ if not query:
455
+ return "请提供查询关键词"
456
+ try:
457
+ top_k = int(args.get("top_k", 10))
458
+ except (TypeError, ValueError):
459
+ top_k = 10
460
+ results = semantic_search(query, top_k=top_k)
461
+ if results is None:
462
+ st = embedding_status()
463
+ if not st['available']:
464
+ return ("语义检索未启用: sentence-transformers 未安装。\n"
465
+ "启用方法: pip install sentence-transformers\n"
466
+ "(95MB 模型首次自动下载, 之后完全离线)")
467
+ elif st['embeddings_count'] == 0:
468
+ return ("语义检索未构建向量。请管理员运行:\n"
469
+ " python .qoder/scripts/domain/kg/graph/kg_semantic.py build-embeddings")
470
+ return None
471
+ lines = ["语义搜索: '%s' (top %d)" % (query, len(results))]
472
+ for eid, text, score in results:
473
+ lines.append(" %.3f %s" % (score, text[:70]))
474
+ return '\n'.join(lines)
475
+
476
+
477
+ if __name__ == "__main__":
478
+ KnowledgeGraphMCPServer().run()