@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,246 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ nexus.registry —— 宿主发现 + Nexus Facade。
4
+
5
+ 设计模式:
6
+ - Registry(HOST_TABLE 单一事实源,泛化 common.active_task._ENV_SESSION_KEYS)
7
+ - Facade(resolve() 一行入口)
8
+ - Bridge(Cap 抽象 × 宿主实现 正交)
9
+
10
+ 主轴: 工作流面向能力编程,不面向宿主。
11
+ cap = nexus.resolve()
12
+ cap.memory.recall(...) # 不管在 QoderWork 还是 CLI,同一行代码
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ from typing import Any
18
+
19
+ from .caps import (
20
+ NoOpCronCap,
21
+ NoOpNotifyCap,
22
+ NoOpPresentCap,
23
+ NoOpSandboxCap,
24
+ )
25
+
26
+
27
+ # =============================================================================
28
+ # HOST_TABLE —— 宿主注册表
29
+ # -----------------------------------------------------------------------------
30
+ # 泛化 common/active_task.py:_ENV_SESSION_KEYS 的 7 宿主表:
31
+ # 从"只探测身份"扩展为"身份 + 能力清单"。
32
+ #
33
+ # 现在最小形态: Python dict。
34
+ # 未来完整形态: manifests/*.yaml 声明式文件(每宿主一份)。
35
+ # 迁移路径: dict → yaml,resolve() 接口不变,工作流零改动。
36
+ # =============================================================================
37
+
38
+ HOST_TABLE: dict[str, dict] = {
39
+ # ── Qoder 产品线(第一宿主系列)─────────────────────────────
40
+ "qoder-work": {
41
+ "detect": ("QODER_SESSION_ID",),
42
+ # features = 当前已实现 (非 NoOp) 的能力。notify/present/sandbox 暂无
43
+ # 宿主原生 Adapter,声明即误报可用,故不列。后续补齐 Adapter 再加。
44
+ "features": ("mcp", "memory", "cron"),
45
+ "mcp_servers": ["qoder-knowledge-graph", "qoder-zentao", "qoder-mysql", "lanhu"],
46
+ "note": "QoderWork 桌面 Agent,全能力",
47
+ },
48
+ "qoder-ide": {
49
+ "detect": ("QODER_IDE",),
50
+ "features": ("mcp", "memory"),
51
+ "mcp_servers": ["qoder-knowledge-graph", "qoder-zentao", "qoder-mysql"],
52
+ "note": "Qoder IDE (VSCode 扩展)",
53
+ },
54
+ "qoder-quest": {
55
+ "detect": ("QODER_QUEST",),
56
+ "features": ("mcp", "memory"),
57
+ "mcp_servers": ["qoder-knowledge-graph", "qoder-zentao"],
58
+ "note": "Qoder Quest,自然语言入口",
59
+ },
60
+ # ── 其他宿主(通用 AI 工具)────────────────────────────────
61
+ "codex": {
62
+ "detect": ("CODEX_SESSION_ID", "CODEX_THREAD_ID"),
63
+ "features": ("repo", "identity"),
64
+ "mcp_servers": [],
65
+ "note": "OpenAI Codex CLI",
66
+ },
67
+ "claude": {
68
+ "detect": ("CLAUDE_SESSION_ID", "CLAUDE_CODE_SESSION_ID"),
69
+ "features": ("repo", "identity"),
70
+ "mcp_servers": [],
71
+ "note": "Claude Code",
72
+ },
73
+ "cursor": {
74
+ "detect": ("CURSOR_SESSION_ID",),
75
+ "features": ("repo", "identity"),
76
+ "mcp_servers": [],
77
+ },
78
+ "opencode": {
79
+ "detect": ("OPENCODE_SESSION_ID", "OPENCODE_RUN_ID"),
80
+ "features": ("repo", "identity"),
81
+ "mcp_servers": [],
82
+ },
83
+ "gemini": {
84
+ "detect": ("GEMINI_SESSION_ID",),
85
+ "features": ("repo", "identity"),
86
+ "mcp_servers": [],
87
+ },
88
+ "copilot": {
89
+ "detect": ("COPILOT_SESSION_ID",),
90
+ "features": ("repo", "identity"),
91
+ "mcp_servers": [],
92
+ },
93
+ # ── 兜底 ──────────────────────────────────────────────────
94
+ "cli": {
95
+ "detect": (), # 兜底:无 env 命中即为 CLI / 任意宿主
96
+ "features": ("repo", "identity", "context", "memory", "mcp"),
97
+ # 本地 stdio transport 经 mcp_launcher spawn 子进程, 不依赖宿主原生 MCP。
98
+ # 故 CLI 能起全部本地 server (kg/zentao/mysql/lanhu), 与 QoderWork 等价。
99
+ "mcp_servers": ["qoder-knowledge-graph", "qoder-zentao", "qoder-mysql", "lanhu"],
100
+ "note": "纯 CLI 兜底; MCP 走本地 spawn (非宿主原生), 其余走文件降级",
101
+ },
102
+ }
103
+
104
+
105
+ def detect_host() -> str:
106
+ """探测当前宿主名。
107
+
108
+ 遍历 HOST_TABLE,第一个 env 命中的返回;无则 'cli'。
109
+ 单一事实源 —— 替代散落在 active_task / autotest / mcp_launcher 的多处宿主探测。
110
+ """
111
+ for name, info in HOST_TABLE.items():
112
+ if name == "cli":
113
+ continue
114
+ for env_var in info.get("detect", ()):
115
+ if os.environ.get(env_var):
116
+ return name
117
+ return "cli"
118
+
119
+
120
+ # =============================================================================
121
+ # Nexus Facade
122
+ # =============================================================================
123
+
124
+ class Nexus:
125
+ """宿主能力 Facade —— 聚合所有能力,对外统一入口。
126
+
127
+ 工作流永远只调 nexus.resolve() 拿到本对象,再访问 .memory/.mcp/...,
128
+ 不直接 import 任何 adapter(Facade 模式)。
129
+ """
130
+
131
+ def __init__(self, host_name: str, repo, identity, context, memory,
132
+ cron, notify, present, sandbox, mcp):
133
+ self.host_name = host_name
134
+ self.repo = repo
135
+ self.identity = identity
136
+ self.context = context
137
+ self.memory = memory
138
+ self.cron = cron
139
+ self.notify = notify
140
+ self.present = present
141
+ self.sandbox = sandbox
142
+ self.mcp = mcp
143
+
144
+ def describe(self) -> dict:
145
+ """诊断:当前宿主 + 各能力 available 状态。"""
146
+ return {
147
+ "host": self.host_name,
148
+ "capabilities": {
149
+ "repo": _avail(self.repo),
150
+ "identity": _avail(self.identity),
151
+ "context": _avail(self.context),
152
+ "memory": _avail(self.memory),
153
+ "cron": _avail(self.cron),
154
+ "notify": _avail(self.notify),
155
+ "present": _avail(self.present),
156
+ "sandbox": _avail(self.sandbox),
157
+ "mcp": _avail(self.mcp),
158
+ },
159
+ }
160
+
161
+
162
+ def _avail(cap) -> bool:
163
+ return bool(getattr(cap, "available", False))
164
+
165
+
166
+ # =============================================================================
167
+ # 能力装配(Bridge: Cap 抽象 × 宿主实现 正交)
168
+ # -----------------------------------------------------------------------------
169
+ # 每个 _resolve_xxx 是一个能力的装配点。新增能力(场景 B)= 加一个 _resolve_xxx
170
+ # + Nexus 属性。新增宿主实现 = 在对应 _resolve_xxx 里按 host 名分支。
171
+ # =============================================================================
172
+
173
+ def _resolve_repo(host: str):
174
+ """核心能力:所有宿主都用文件降级版(无 ~/.qoderwork 依赖)。"""
175
+ from .adapters.cli import FileRepoCap
176
+ return FileRepoCap()
177
+
178
+
179
+ def _resolve_identity(host: str):
180
+ from .adapters.cli import FileIdentityCap
181
+ return FileIdentityCap()
182
+
183
+
184
+ def _resolve_context(host: str):
185
+ from .adapters.cli import FileContextCap
186
+ return FileContextCap()
187
+
188
+
189
+ def _resolve_memory(host: str):
190
+ """memory —— 职责链样板(见 memory_chain.py)。"""
191
+ from .memory_chain import build_memory_chain
192
+ return build_memory_chain(host)
193
+
194
+
195
+ def _resolve_cron(host: str):
196
+ from .adapters.qw import QwCronCap
197
+ return QwCronCap() # 文件系统 cron,不依赖宿主
198
+
199
+
200
+ def _resolve_notify(host: str):
201
+ return NoOpNotifyCap()
202
+
203
+
204
+ def _resolve_present(host: str):
205
+ """L2 富展示 —— QoderWork 调 qoder_show_widget 嵌入对话; 其余 NoOp 降级表格。"""
206
+ if host in ("qoder-work", "qoder-ide"):
207
+ try:
208
+ from .adapters.qw import QwPresentCap
209
+ return QwPresentCap()
210
+ except Exception:
211
+ pass
212
+ return NoOpPresentCap()
213
+
214
+
215
+ def _resolve_sandbox(host: str):
216
+ return NoOpSandboxCap()
217
+
218
+
219
+ def _resolve_mcp(host: str):
220
+ """L1 MCP 统一入口 —— 根据宿主声明的 mcp_servers 决定 spawn 哪些。"""
221
+ from .adapters.mcp import McpCap
222
+ info = HOST_TABLE.get(host, {})
223
+ mcp_servers = info.get("mcp_servers")
224
+ return McpCap(mcp_servers=mcp_servers)
225
+
226
+
227
+ def resolve() -> Nexus:
228
+ """解析当前环境,返回 Nexus Facade。
229
+
230
+ 一行入口: cap = nexus.resolve()
231
+ 内部: detect_host → 按宿主装配各 Cap → 注入 NoOp 兜底。
232
+ 工作流代码零宿主感知。
233
+ """
234
+ host = detect_host()
235
+ return Nexus(
236
+ host_name=host,
237
+ repo=_resolve_repo(host),
238
+ identity=_resolve_identity(host),
239
+ context=_resolve_context(host),
240
+ memory=_resolve_memory(host),
241
+ cron=_resolve_cron(host),
242
+ notify=_resolve_notify(host),
243
+ present=_resolve_present(host),
244
+ sandbox=_resolve_sandbox(host),
245
+ mcp=_resolve_mcp(host),
246
+ )
@@ -0,0 +1,250 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ nexus.registry_mcp —— MCP 工具路由(tool → server → transport)。
4
+
5
+ 设计模式: Registry + Strategy(配合 transports 层)
6
+
7
+ 职责:
8
+ 1. 声明现有 MCP server 清单及其默认 transport(配置驱动,留远程化扩展点)。
9
+ 2. 维护 server → Transport 实例池(复用子进程/连接)。
10
+ 3. 构建 tool 名 → server 名 的反向索引(懒加载,首次需要时遍历 tools/list)。
11
+ 4. resolve_tool(tool_name) → 该 tool 所在 server 的 Transport。
12
+
13
+ 扩展点(配置驱动远程化):
14
+ 未来知识图谱上服务器,把 SERVERS["qoder-knowledge-graph"]["kind"] 改成 "http"
15
+ + 填 url,工作流 (cap.mcp.call) 零改动。
16
+ 这是"满足现在同时留扩展给未来"的核心机制。
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import os
21
+ from pathlib import Path
22
+ from typing import Optional
23
+
24
+ from protocol.transports import get_transport, Transport, NoOpTransport, ToolResult
25
+
26
+
27
+ # =============================================================================
28
+ # SERVERS —— MCP server 清单 + 默认 transport 配置(单一事实源)
29
+ # -----------------------------------------------------------------------------
30
+ # 每个条目:
31
+ # kind: 传输方式 ("stdio"|"cli"|"http")
32
+ # launcher_arg: mcp_launcher 接受的子名(多数同 key)
33
+ # url: http 时的远程 endpoint(stdio/cli 不用)
34
+ # 未来改这里即可切换部署形态,工作流零改动。
35
+ # =============================================================================
36
+
37
+ SERVERS: dict[str, dict] = {
38
+ "qoder-knowledge-graph": {
39
+ "kind": "stdio",
40
+ "launcher_arg": "kg",
41
+ "note": "知识图谱。本地分发;未来可上服务器 → 改 kind=http + url",
42
+ },
43
+ "qoder-zentao": {
44
+ "kind": "stdio",
45
+ "launcher_arg": "zentao",
46
+ "note": "禅道任务/Bug/需求",
47
+ },
48
+ "qoder-mysql": {
49
+ "kind": "stdio",
50
+ "launcher_arg": "mysql",
51
+ "note": "只读 MySQL 结构/数据",
52
+ },
53
+ "lanhu": {
54
+ "kind": "stdio",
55
+ "launcher_arg": "lanhu",
56
+ "note": "蓝湖设计稿",
57
+ },
58
+ }
59
+
60
+
61
+ def _get_repo_root() -> Path:
62
+ """从 common.paths 获取仓库根(复用既有工具,不重复造轮子)。"""
63
+ try:
64
+ from foundation.core.paths import get_repo_root
65
+ return get_repo_root()
66
+ except Exception:
67
+ return Path(__file__).resolve().parent.parent.parent.parent
68
+
69
+
70
+ class McpRegistry:
71
+ """MCP 工具路由 + Transport 实例池。
72
+
73
+ 线程安全说明:实例池按 server 名缓存 Transport,每个 Transport 自带锁。
74
+ 反向索引构建期间对多 server 并发(线程池),构建后只读。
75
+ """
76
+
77
+ def __init__(self, repo_root: Optional[Path] = None, servers: Optional[dict] = None):
78
+ self._repo = repo_root or _get_repo_root()
79
+ self._servers = servers if servers is not None else SERVERS
80
+ self._pool: dict[str, Transport] = {}
81
+ self._tool_to_server: Optional[dict[str, str]] = None # 懒构建
82
+ self._index_lock_built = False
83
+
84
+ # ------------------------------------------------------------------
85
+ # Transport 实例池
86
+ # ------------------------------------------------------------------
87
+
88
+ def _get_or_create(self, server_name: str) -> Transport:
89
+ """按 server 名取/建 Transport 实例(复用)。"""
90
+ if server_name not in self._pool:
91
+ cfg = self._servers.get(server_name, {})
92
+ kind = cfg.get("kind", "stdio")
93
+ launcher_arg = cfg.get("launcher_arg", server_name)
94
+ if kind == "stdio":
95
+ self._pool[server_name] = get_transport(
96
+ launcher_arg, kind="stdio", repo_root=self._repo,
97
+ )
98
+ elif kind == "http":
99
+ self._pool[server_name] = get_transport(
100
+ server_name, kind="http",
101
+ url=cfg.get("url", ""), token=cfg.get("token"),
102
+ )
103
+ else:
104
+ self._pool[server_name] = get_transport(
105
+ server_name, kind=kind, repo_root=self._repo, **cfg,
106
+ )
107
+ return self._pool[server_name]
108
+
109
+ # ------------------------------------------------------------------
110
+ # 反向索引(tool 名 → server 名)
111
+ # ------------------------------------------------------------------
112
+
113
+ def _build_index(self) -> None:
114
+ """对所有 server 发 tools/list,建 tool→server 反向索引。
115
+
116
+ 并发构建(各 server 独立 transport,互不干扰)。
117
+ 不可用的 server 跳过(其 tool 不进索引)。
118
+ """
119
+ import concurrent.futures
120
+
121
+ index: dict[str, str] = {}
122
+
123
+ def _probe(server_name: str) -> tuple[str, list[dict]]:
124
+ try:
125
+ t = self._get_or_create(server_name)
126
+ tools = t.list_tools()
127
+ return server_name, tools
128
+ except Exception:
129
+ return server_name, []
130
+
131
+ with concurrent.futures.ThreadPoolExecutor(max_workers=len(self._servers) or 1) as ex:
132
+ for server_name, tools in ex.map(_probe, list(self._servers.keys())):
133
+ for tool in tools:
134
+ tname = tool.get("name", "")
135
+ if tname:
136
+ index[tname] = server_name
137
+
138
+ self._tool_to_server = index
139
+ # ★ 修复: 全空索引不标记为"已建好"。
140
+ # 原版无论结果如何都设 _index_lock_built=True + 赋空 dict,
141
+ # 导致 _ensure_index (判 is None) 永不重建 → 一次冷启动竞态失败就永久 0 工具。
142
+ # 现改为: 空 = 视为未建, 下次调用会重试 (并发冷启动是间歇性的, 重试通常成功)。
143
+ if index:
144
+ self._index_lock_built = True
145
+ else:
146
+ # 保留 None, 下次 _ensure_index 会重 _build_index
147
+ self._tool_to_server = None
148
+
149
+ def _ensure_index(self) -> None:
150
+ if self._tool_to_server is None:
151
+ self._build_index()
152
+
153
+ # ------------------------------------------------------------------
154
+ # 公共 API
155
+ # ------------------------------------------------------------------
156
+
157
+ def resolve_tool(self, tool_name: str) -> Optional[Transport]:
158
+ """按 tool 名解析其所在 server 的 Transport。未知返回 None。"""
159
+ self._ensure_index()
160
+ server = (self._tool_to_server or {}).get(tool_name)
161
+ if not server:
162
+ return None
163
+ return self._get_or_create(server)
164
+
165
+ def has_tool(self, tool_name: str) -> bool:
166
+ """某 tool 是否可用(先查反向索引;未命中尝试解析)。"""
167
+ self._ensure_index()
168
+ if tool_name in (self._tool_to_server or {}):
169
+ return True
170
+ t = self.resolve_tool(tool_name)
171
+ if t is None:
172
+ return False
173
+ try:
174
+ return t.has_tool(tool_name)
175
+ except Exception:
176
+ return False
177
+
178
+ def call(self, tool_name: str, args: Optional[dict] = None,
179
+ server: Optional[str] = None) -> ToolResult:
180
+ """调用工具。
181
+
182
+ Args:
183
+ tool_name: 工具名(如 list_execution_tasks)。
184
+ args: 工具参数。
185
+ server: 显式指定 server 名(跳过反向索引,避免首次开销)。
186
+ Returns:
187
+ ToolResult。未知工具/不可用返回 is_error=True 的空结果,不抛异常。
188
+ """
189
+ transport: Transport
190
+ if server:
191
+ if server not in self._servers:
192
+ return ToolResult(text="未知 server: %s" % server, is_error=True)
193
+ transport = self._get_or_create(server)
194
+ else:
195
+ transport = self.resolve_tool(tool_name) or NoOpTransport()
196
+ if not transport.available:
197
+ return ToolResult(
198
+ text="transport 不可用 (server=%s, tool=%s)" % (server or "?", tool_name),
199
+ is_error=True,
200
+ )
201
+ return transport.call_tool(tool_name, args or {})
202
+
203
+ def available(self) -> bool:
204
+ """任一 server 可用即为 True。"""
205
+ self._ensure_index()
206
+ return bool(self._tool_to_server)
207
+
208
+ def describe(self) -> dict:
209
+ """诊断:各 server 的 transport 状态 + 已索引工具数 + 延迟 + PID。"""
210
+ import time as _time
211
+ self._ensure_index()
212
+ servers_status = {}
213
+ for name in self._servers:
214
+ t = self._pool.get(name)
215
+ indexed = [tn for tn, sn in (self._tool_to_server or {}).items() if sn == name]
216
+
217
+ # latency: 测量 list_tools 的首次延迟(已在 _build_index 期间完成,此处重新测)
218
+ latency_ms = None
219
+ if t and t.available:
220
+ try:
221
+ t0 = _time.monotonic()
222
+ t.list_tools() # 有缓存,很快
223
+ latency_ms = round((_time.monotonic() - t0) * 1000, 1)
224
+ except Exception:
225
+ pass
226
+
227
+ # spawn_pid: stdio transport 的子进程 PID
228
+ spawn_pid = None
229
+ if t and hasattr(t, "_proc") and t._proc is not None:
230
+ spawn_pid = getattr(t._proc, "pid", None)
231
+
232
+ servers_status[name] = {
233
+ "kind": self._servers[name].get("kind", "stdio"),
234
+ "available": bool(t and t.available),
235
+ "tools_indexed": len(indexed),
236
+ "latency_ms": latency_ms,
237
+ "spawn_pid": spawn_pid,
238
+ }
239
+ return {"servers": servers_status, "total_tools": len(self._tool_to_server or {})}
240
+
241
+ def close_all(self) -> None:
242
+ """关闭所有 Transport(进程退出/连接释放)。"""
243
+ for t in self._pool.values():
244
+ try:
245
+ t.close()
246
+ except Exception:
247
+ pass
248
+ self._pool.clear()
249
+ self._tool_to_server = None
250
+ self._index_lock_built = False