@hupan56/wlkj 3.0.0 → 3.1.3

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 (247) hide show
  1. package/bin/cli.js +1435 -1373
  2. package/package.json +1 -1
  3. package/templates/qoder/commands/optional/wl-insight.md +6 -4
  4. package/templates/qoder/commands/optional/wl-report.md +20 -39
  5. package/templates/qoder/commands/optional/wl-status.md +4 -4
  6. package/templates/qoder/commands/wl-code.md +2 -2
  7. package/templates/qoder/commands/wl-design.md +5 -5
  8. package/templates/qoder/commands/wl-init.md +3 -3
  9. package/templates/qoder/commands/wl-prd.md +26 -13
  10. package/templates/qoder/commands/wl-req.md +3 -3
  11. package/templates/qoder/commands/wl-search.md +78 -47
  12. package/templates/qoder/commands/wl-task.md +343 -40
  13. package/templates/qoder/commands/wl-test.md +10 -10
  14. package/templates/qoder/config.yaml +35 -13
  15. package/templates/qoder/hooks/post-tool-use.py +181 -0
  16. package/templates/qoder/hooks/session-start.py +82 -1
  17. package/templates/qoder/hooks/stop-eval.py +207 -0
  18. package/templates/qoder/hooks/user-prompt-submit.py +139 -0
  19. package/templates/qoder/rules/wl-pipeline.md +70 -8
  20. package/templates/qoder/scripts/README.md +78 -115
  21. package/templates/qoder/scripts/capability/__init__.py +26 -0
  22. package/templates/qoder/scripts/capability/__main__.py +72 -0
  23. package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
  24. package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
  25. package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
  26. package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
  27. package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
  28. package/templates/qoder/scripts/capability/caps/context.py +36 -0
  29. package/templates/qoder/scripts/capability/caps/cron.py +50 -0
  30. package/templates/qoder/scripts/capability/caps/identity.py +43 -0
  31. package/templates/qoder/scripts/capability/caps/memory.py +71 -0
  32. package/templates/qoder/scripts/capability/caps/notify.py +41 -0
  33. package/templates/qoder/scripts/capability/caps/present.py +48 -0
  34. package/templates/qoder/scripts/capability/caps/repo.py +31 -0
  35. package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
  36. package/templates/qoder/scripts/capability/chain.py +92 -0
  37. package/templates/qoder/scripts/capability/memory_chain.py +41 -0
  38. package/templates/qoder/scripts/capability/registry.py +246 -0
  39. package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
  40. package/templates/qoder/scripts/capability/smoke_test.py +211 -0
  41. package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
  42. package/templates/qoder/scripts/{report → deployment/setup}/__init__.py +11 -11
  43. package/templates/qoder/scripts/{setup → deployment/setup}/carriers.py +33 -26
  44. package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
  45. package/templates/qoder/scripts/{setup → deployment/setup}/init_doctor.py +55 -23
  46. package/templates/qoder/scripts/{setup → deployment/setup}/install_qoderwork.py +20 -12
  47. package/templates/qoder/scripts/{setup → deployment/setup}/platform_doctor.py +8 -2
  48. package/templates/qoder/scripts/{setup → deployment/setup}/repo_root.py +113 -112
  49. package/templates/qoder/scripts/{setup → deployment/setup}/setup.py +35 -12
  50. package/templates/qoder/scripts/{setup → deployment/setup}/setup_lanhu.py +13 -7
  51. package/templates/qoder/scripts/domain/__init__.py +0 -0
  52. package/templates/qoder/scripts/domain/design/__init__.py +0 -0
  53. package/templates/qoder/scripts/{report → domain/design}/fill_prototype.py +12 -6
  54. package/templates/qoder/scripts/{report → domain/design}/gen_design_doc.py +8 -2
  55. package/templates/qoder/scripts/{setup → domain/kg}/__init__.py +11 -11
  56. package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
  57. package/templates/qoder/scripts/{kg → domain/kg/build}/build_entity_registry.py +7 -2
  58. package/templates/qoder/scripts/{kg → domain/kg/build}/build_relations.py +7 -2
  59. package/templates/qoder/scripts/{kg → domain/kg/build}/build_style_index.py +13 -7
  60. package/templates/qoder/scripts/{kg → domain/kg/build}/build_workflows.py +7 -2
  61. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build.py +76 -9
  62. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build_db.py +7 -2
  63. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_incremental.py +11 -6
  64. package/templates/qoder/scripts/{report → domain/kg/build}/learn_aggregate.py +9 -4
  65. package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
  66. package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
  67. package/templates/qoder/scripts/{common → domain/kg/extract}/test_extract.py +115 -115
  68. package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
  69. package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
  70. package/templates/qoder/scripts/{kg → domain/kg/graph}/kg_link_db.py +7 -2
  71. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
  72. package/templates/qoder/scripts/{kg → domain/kg}/kg.py +263 -30
  73. package/templates/qoder/scripts/{common → domain/kg}/kg_capabilities.py +182 -182
  74. package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
  75. package/templates/qoder/scripts/{kg → domain/kg/search}/context_pack.py +30 -9
  76. package/templates/qoder/scripts/{kg → domain/kg/search}/enrich_prompt.py +10 -4
  77. package/templates/qoder/scripts/{kg → domain/kg/search}/prefetch.py +33 -8
  78. package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
  79. package/templates/qoder/scripts/{kg → domain/kg/search}/search_index.py +143 -77
  80. package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
  81. package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
  82. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  83. package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
  84. package/templates/qoder/scripts/{kg → domain/kg/storage}/kg_duckdb.py +38 -14
  85. package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
  86. package/templates/qoder/scripts/{report → domain/learning}/learn.py +8 -3
  87. package/templates/qoder/scripts/{mcp → domain/report}/__init__.py +11 -11
  88. package/templates/qoder/scripts/{report → domain/report}/add_session.py +9 -3
  89. package/templates/qoder/scripts/{report → domain/report}/export.py +10 -5
  90. package/templates/qoder/scripts/{report → domain/report}/report.py +9 -4
  91. package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
  92. package/templates/qoder/scripts/{report → domain/report}/role.py +10 -4
  93. package/templates/qoder/scripts/{report → domain/report}/status.py +112 -9
  94. package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
  95. package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
  96. package/templates/qoder/scripts/{report → domain/requirement}/archive_prd.py +8 -2
  97. package/templates/qoder/scripts/{report → domain/requirement}/req.py +228 -222
  98. package/templates/qoder/scripts/{kg → domain/task}/__init__.py +11 -11
  99. package/templates/qoder/scripts/domain/task/__pycache__/syncgate.cpython-39.pyc +0 -0
  100. package/templates/qoder/scripts/{task → domain/task}/git_sync.py +40 -21
  101. package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
  102. package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
  103. package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
  104. package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
  105. package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
  106. package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
  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/__pycache__/__init__.cpython-39.pyc +0 -0
  110. package/templates/qoder/scripts/foundation/__pycache__/bootstrap.cpython-39.pyc +0 -0
  111. package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
  112. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  113. package/templates/qoder/scripts/foundation/core/__pycache__/__init__.cpython-39.pyc +0 -0
  114. package/templates/qoder/scripts/foundation/core/__pycache__/paths.cpython-39.pyc +0 -0
  115. package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
  116. package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
  117. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
  118. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  119. package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
  120. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  121. package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
  122. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
  123. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  124. package/templates/qoder/scripts/foundation/identity/__pycache__/__init__.cpython-39.pyc +0 -0
  125. package/templates/qoder/scripts/foundation/identity/__pycache__/identity.cpython-39.pyc +0 -0
  126. package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
  127. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
  128. package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
  129. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
  130. package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
  131. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  132. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  133. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +2 -2
  134. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  135. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  136. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  137. package/templates/qoder/scripts/foundation/io/__pycache__/__init__.cpython-39.pyc +0 -0
  138. package/templates/qoder/scripts/foundation/io/__pycache__/atomicio.cpython-39.pyc +0 -0
  139. package/templates/qoder/scripts/foundation/io/__pycache__/filelock.cpython-39.pyc +0 -0
  140. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  141. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  142. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  143. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  144. package/templates/qoder/scripts/{common → foundation/protocol}/mcp_base.py +1 -1
  145. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  146. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  147. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  148. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  149. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  150. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  151. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  152. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  153. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  154. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  155. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  156. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  157. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  158. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  159. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  160. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  161. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  162. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  163. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  164. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  165. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  166. package/templates/qoder/scripts/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
  167. package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
  168. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  169. package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
  170. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  171. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  172. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  173. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  174. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  175. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  176. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  177. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  178. package/templates/qoder/scripts/validation/__init__.py +0 -0
  179. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  180. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  181. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  182. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  183. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  184. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  185. package/templates/qoder/scripts/{report → validation/metrics}/eval_prd.py +17 -10
  186. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  187. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  188. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  189. package/templates/qoder/scripts/{test → validation/test}/autotest.py +64 -4
  190. package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
  191. package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
  192. package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
  193. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  194. package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
  195. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  196. package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
  197. package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
  198. package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
  199. package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
  200. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  201. package/templates/qoder/settings.json +30 -0
  202. package/templates/qoder/skills/design-import/SKILL.md +22 -13
  203. package/templates/qoder/skills/design-review/SKILL.md +19 -10
  204. package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
  205. package/templates/qoder/skills/prd-review/SKILL.md +6 -4
  206. package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
  207. package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
  208. package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
  209. package/templates/qoder/skills/test-generator/SKILL.md +7 -5
  210. package/templates/qoder/skills/wl-code/SKILL.md +23 -11
  211. package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
  212. package/templates/qoder/skills/wl-design/SKILL.md +5 -3
  213. package/templates/qoder/skills/wl-init/SKILL.md +6 -6
  214. package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
  215. package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
  216. package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
  217. package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
  218. package/templates/qoder/skills/wl-report/SKILL.md +25 -23
  219. package/templates/qoder/skills/wl-search/SKILL.md +149 -78
  220. package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
  221. package/templates/qoder/skills/wl-status/SKILL.md +48 -16
  222. package/templates/qoder/skills/wl-task/SKILL.md +92 -10
  223. package/templates/qoder/skills/wl-test/SKILL.md +75 -29
  224. package/templates/qoder/templates/prd-full-template.md +7 -0
  225. package/templates/root/AGENTS.md +275 -236
  226. package/templates/root/requirements.txt +3 -0
  227. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  228. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  229. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
  230. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
  231. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
  232. package/templates/.qoder/learning/eval-history.jsonl +0 -28
  233. package/templates/data/index/wiki-index.json +0 -8
  234. package/templates/qoder/nul +0 -4
  235. package/templates/qoder/scripts/kg/extract.py +0 -398
  236. package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
  237. package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
  238. package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
  239. package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
  240. package/templates/qoder/scripts/task/__init__.py +0 -11
  241. package/templates/qoder/scripts/test/__init__.py +0 -11
  242. package/templates/qoder/scripts/wlkj.py +0 -116
  243. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  244. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  245. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  246. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  247. /package/templates/qoder/scripts/{test → 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()
@@ -2,9 +2,15 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
4
4
  import os as _o, sys as _s
5
- _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
5
+ _f = _o.path.abspath(__file__)
6
+ for _ in range(10):
7
+ _f = _o.path.dirname(_f)
8
+ _cp = _o.path.join(_f, 'foundation')
9
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
10
+ break
6
11
  if _cp not in _s.path: _s.path.insert(0, _cp)
7
12
  from bootstrap import setup; setup()
13
+ from foundation.core.paths import get_repo_root
8
14
 
9
15
  """
10
16
  lanhu_stdio_wrapper.py - 蓝湖 MCP STDIO 启动包装(角色隔离 + QoderWork 自动开关)
@@ -16,7 +22,7 @@ lanhu_stdio_wrapper.py - 蓝湖 MCP STDIO 启动包装(角色隔离 + QoderWor
16
22
  做什么:
17
23
  1. 定位仓库根
18
24
  2. 读 .qoder/.developer → 当前角色 (UI设计师/PM/开发...)
19
- 3. 读 workspace/members/{角色}/.secrets/lanhu.env → 该角色的蓝湖 cookie
25
+ 3. 读 workspace/members/{角色}/.private/secrets/lanhu.env → 该角色的蓝湖 cookie
20
26
  4. 注入环境变量 (LANHU_COOKIE + MCP_TRANSPORT=stdio + LANHU_USER_NAME)
21
27
  5. os.execv() 拉起 lanhu_mcp_server.py (用 venv 的 3.10+ python)
22
28
 
@@ -24,7 +30,7 @@ lanhu_stdio_wrapper.py - 蓝湖 MCP STDIO 启动包装(角色隔离 + QoderWor
24
30
  蓝湖权限按角色分 (UI 能改稿, PM/开发只读)。
25
31
  cookie 必须按角色隔离, 换 .developer 时跟着换。
26
32
  wrapper 每次启动读当前角色 → 用对应 cookie → 权限正确。
27
- cookie 不进 mcp.json (那是机器级全局, 不按人), 仍按角色存 .secrets/。
33
+ cookie 不进 mcp.json (那是机器级全局, 不按人), 仍按角色存 .private/secrets/。
28
34
 
29
35
  为什么用 os.execv 而非 subprocess:
30
36
  execv 让 lanhu server 直接替换 wrapper 进程。
@@ -45,7 +51,7 @@ except (AttributeError, TypeError, OSError):
45
51
 
46
52
  # 定位仓库根: 本文件在 <repo>/.qoder/scripts/ → 上三级
47
53
  SCRIPT_DIR = Path(__file__).resolve().parent
48
- BASE = SCRIPT_DIR.parent.parent # repo root
54
+ BASE = get_repo_root()
49
55
 
50
56
 
51
57
  def _find_repo_root():
@@ -83,7 +89,7 @@ def main():
83
89
  dev = _current_developer()
84
90
  if not dev:
85
91
  sys.stderr.write('[lanhu-wrapper] 未初始化开发者 (.qoder/.developer 不存在)。\n')
86
- sys.stderr.write(' 跑 /wl-init 或 python .qoder/scripts/setup.py 初始化。\n')
92
+ sys.stderr.write(' 跑 /wl-init 或 npx @hupan56/wlkj init <你的名字> 初始化。\n')
87
93
  sys.exit(1)
88
94
 
89
95
  # 2. 该角色的 cookie
@@ -11,7 +11,12 @@ v3.0 合并自: check_mcp(启动条件) + check_mcp_launch(实际启动)。
11
11
  """
12
12
  # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
13
13
  import os as _o, sys as _s
14
- _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
14
+ _f = _o.path.abspath(__file__)
15
+ for _ in range(10):
16
+ _f = _o.path.dirname(_f)
17
+ _cp = _o.path.join(_f, 'foundation')
18
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
19
+ break
15
20
  if _cp not in _s.path: _s.path.insert(0, _cp)
16
21
  from bootstrap import setup; setup()
17
22
 
@@ -55,7 +60,7 @@ if sys.platform == 'win32':
55
60
  pass
56
61
 
57
62
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
58
- from common.paths import get_repo_root
63
+ from foundation.core.paths import get_repo_root
59
64
 
60
65
  BASE = get_repo_root()
61
66
  HOME = Path(os.environ.get('USERPROFILE') or os.path.expanduser('~'))
@@ -190,7 +195,7 @@ def check_launcher_file(fix=False):
190
195
  def check_kg():
191
196
  """4. kg MCP: duckdb + kg_mcp_server.py。"""
192
197
  print('\n--- 4. 知识图谱 MCP (kg) ---')
193
- server = BASE / '.qoder' / 'scripts' / 'kg_mcp_server.py'
198
+ server = BASE / '.qoder' / 'scripts' / 'protocol' / 'mcp' / 'kg_mcp_server.py'
194
199
  if not server.is_file():
195
200
  problem('kg_mcp_server.py 不存在', '引擎不完整, 重跑 npx wlkj update')
196
201
  return
@@ -205,7 +210,7 @@ def check_kg():
205
210
  def check_mysql():
206
211
  """5. mysql MCP: pymysql + config.yaml mysql 段。"""
207
212
  print('\n--- 5. MySQL MCP ---')
208
- server = BASE / '.qoder' / 'scripts' / 'mysql_mcp_server.py'
213
+ server = BASE / '.qoder' / 'scripts' / 'protocol' / 'mcp' / 'mysql_mcp_server.py'
209
214
  if not server.is_file():
210
215
  problem('mysql_mcp_server.py 不存在')
211
216
  return
@@ -229,9 +234,36 @@ def check_mysql():
229
234
  say(WARN_TAG, 'mysql 依赖就绪, 但 config.yaml 无 mysql 段 (按需配)')
230
235
 
231
236
 
237
+ def check_zentao():
238
+ """6. zentao MCP: requests + zentao_mcp_server.py + 个人凭据。"""
239
+ print('\n--- 6. 禅道 MCP (zentao) ---')
240
+ server = BASE / '.qoder' / 'scripts' / 'protocol' / 'mcp' / 'zentao_mcp_server.py'
241
+ if not server.is_file():
242
+ problem('zentao_mcp_server.py 不存在')
243
+ return
244
+ py = _find_python_with('requests')
245
+ if not py:
246
+ problem('找不到装了 requests 的 Python', 'pip install requests')
247
+ return
248
+ # 个人凭据 (每人一个禅道账号, 从 .private/secrets/zentao.env 读)
249
+ try:
250
+ from foundation.core.paths import get_developer, get_secrets_dir
251
+ dev = get_developer(BASE)
252
+ except Exception:
253
+ dev = None
254
+ if not dev:
255
+ say(WARN_TAG, 'zentao 依赖就绪, 但未初始化开发者 (跑 /wl-init)')
256
+ return
257
+ env_file = Path(get_secrets_dir(dev)) / 'zentao.env'
258
+ if env_file.is_file():
259
+ say(OK_TAG, 'zentao 就绪 (角色=%s, 凭据已配)' % dev)
260
+ else:
261
+ say(WARN_TAG, 'zentao 依赖就绪, 但角色 %s 的凭据未配 (%s)' % (dev, env_file))
262
+
263
+
232
264
  def check_lanhu():
233
- """6. lanhu MCP: venv + cookie。三态 (就绪/未装/未配)。"""
234
- print('\n--- 6. 蓝湖 MCP (lanhu, 可选) ---')
265
+ """7. lanhu MCP: venv + cookie。三态 (就绪/未装/未配)。"""
266
+ print('\n--- 7. 蓝湖 MCP (lanhu, 可选) ---')
235
267
  venv = BASE / 'external' / 'lanhu-mcp' / '.venv'
236
268
  server = BASE / 'external' / 'lanhu-mcp' / 'lanhu_mcp_server.py'
237
269
  if not venv.is_dir() or not server.is_file():
@@ -244,7 +276,7 @@ def check_lanhu():
244
276
  return
245
277
  # 当前角色 cookie
246
278
  try:
247
- from common.paths import get_developer
279
+ from foundation.core.paths import get_developer
248
280
  dev = get_developer(BASE)
249
281
  except Exception:
250
282
  dev = None
@@ -259,8 +291,8 @@ def check_lanhu():
259
291
 
260
292
 
261
293
  def check_playwright():
262
- """7. playwright MCP: node + cli.js/npx。"""
263
- print('\n--- 7. Playwright MCP (浏览器测试) ---')
294
+ """8. playwright MCP: node + cli.js/npx。"""
295
+ print('\n--- 8. Playwright MCP (浏览器测试) ---')
264
296
  import shutil
265
297
  node = shutil.which('node')
266
298
  if not node:
@@ -295,6 +327,7 @@ def main_conditions():
295
327
  check_launcher_file(fix)
296
328
  check_kg()
297
329
  check_mysql()
330
+ check_zentao()
298
331
  check_lanhu()
299
332
  check_playwright()
300
333
 
@@ -314,14 +347,14 @@ def main_conditions():
314
347
 
315
348
 
316
349
 
317
- # ==================== 来自 check_mcp_launch.py (实际启动诊断) ====================
350
+ # ==================== 实际启动诊断 (原 check_mcp_launch.py 合并而来) ====================
318
351
  """
319
- check_mcp_launch.py - 诊断 MCP 为什么黄色
352
+ mcp_doctor.py - 诊断 MCP 为什么黄色
320
353
 
321
354
  逐个模拟 mcp_launcher.py 启动每个 MCP, 发送 initialize 请求,
322
355
  看哪个 MCP 启动失败/崩溃/不响应。输出明确的诊断结论。
323
356
 
324
- 用法: python .qoder/scripts/check_mcp_launch.py
357
+ 用法: python .qoder/scripts/protocol/mcp/mcp_doctor.py
325
358
  """
326
359
  import json
327
360
  import os
@@ -329,8 +362,8 @@ import subprocess
329
362
  import sys
330
363
  from pathlib import Path
331
364
 
332
- SCRIPTS_DIR = Path(__file__).resolve().parent
333
- BASE = SCRIPTS_DIR.parent.parent # repo root
365
+ SCRIPTS_DIR = get_repo_root()
366
+ BASE = get_repo_root() # repo root (统一用 foundation 定位, 不手算 parent)
334
367
 
335
368
  MCP_NAMES = ['kg', 'mysql', 'zentao', 'lanhu']
336
369