@hupan56/wlkj 3.0.0 → 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 (237) 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 +40 -16
  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 +31 -11
  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/{task → domain/task}/git_sync.py +40 -21
  100. package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
  101. package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
  102. package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
  103. package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
  104. package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
  105. package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
  106. package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
  107. package/templates/qoder/scripts/foundation/__init__.py +0 -0
  108. package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
  109. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  110. package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
  111. package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
  112. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
  113. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  114. package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
  115. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  116. package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
  117. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
  118. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  119. package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
  120. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
  121. package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
  122. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
  123. package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
  124. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  125. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  126. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +2 -2
  127. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  128. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  129. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  130. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  131. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  132. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  133. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  134. package/templates/qoder/scripts/{common → foundation/protocol}/mcp_base.py +1 -1
  135. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  136. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  137. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  138. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  139. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  140. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  141. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  142. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  143. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  144. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  145. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  146. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  147. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  148. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  149. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  150. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  151. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  152. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  153. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  154. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  155. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  156. package/templates/qoder/scripts/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
  157. package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
  158. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  159. package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
  160. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  161. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  162. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  163. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  164. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  165. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  166. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  167. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  168. package/templates/qoder/scripts/validation/__init__.py +0 -0
  169. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  170. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  171. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  172. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  173. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  174. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  175. package/templates/qoder/scripts/{report → validation/metrics}/eval_prd.py +17 -10
  176. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  177. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  178. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  179. package/templates/qoder/scripts/{test → validation/test}/autotest.py +64 -4
  180. package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
  181. package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
  182. package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
  183. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  184. package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
  185. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  186. package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
  187. package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
  188. package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
  189. package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
  190. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  191. package/templates/qoder/settings.json +30 -0
  192. package/templates/qoder/skills/design-import/SKILL.md +22 -13
  193. package/templates/qoder/skills/design-review/SKILL.md +19 -10
  194. package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
  195. package/templates/qoder/skills/prd-review/SKILL.md +6 -4
  196. package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
  197. package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
  198. package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
  199. package/templates/qoder/skills/test-generator/SKILL.md +7 -5
  200. package/templates/qoder/skills/wl-code/SKILL.md +23 -11
  201. package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
  202. package/templates/qoder/skills/wl-design/SKILL.md +5 -3
  203. package/templates/qoder/skills/wl-init/SKILL.md +6 -6
  204. package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
  205. package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
  206. package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
  207. package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
  208. package/templates/qoder/skills/wl-report/SKILL.md +25 -23
  209. package/templates/qoder/skills/wl-search/SKILL.md +149 -78
  210. package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
  211. package/templates/qoder/skills/wl-status/SKILL.md +48 -16
  212. package/templates/qoder/skills/wl-task/SKILL.md +92 -10
  213. package/templates/qoder/skills/wl-test/SKILL.md +75 -29
  214. package/templates/qoder/templates/prd-full-template.md +7 -0
  215. package/templates/root/AGENTS.md +275 -236
  216. package/templates/root/requirements.txt +3 -0
  217. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  218. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  219. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
  220. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
  221. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
  222. package/templates/.qoder/learning/eval-history.jsonl +0 -28
  223. package/templates/data/index/wiki-index.json +0 -8
  224. package/templates/qoder/nul +0 -4
  225. package/templates/qoder/scripts/kg/extract.py +0 -398
  226. package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
  227. package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
  228. package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
  229. package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
  230. package/templates/qoder/scripts/task/__init__.py +0 -11
  231. package/templates/qoder/scripts/test/__init__.py +0 -11
  232. package/templates/qoder/scripts/wlkj.py +0 -116
  233. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  234. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  235. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  236. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  237. /package/templates/qoder/scripts/{test → validation/test}/secure-ls.js +0 -0
@@ -0,0 +1,56 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ nexus.transports —— 能力传输策略层。
4
+
5
+ 设计模式: Strategy + Factory
6
+
7
+ 三态对应部署形态:
8
+ - stdio: 本地 spawn MCP server(现状默认)
9
+ - cli: 非 MCP 宿主兜底(直接跑脚本)— P2
10
+ - http: 远程服务器(未来)— P3
11
+
12
+ 工作流只通过 McpCap 调用,McpCap 经 McpRegistry 路由到具体 Transport。
13
+ 配置驱动 transport 选择 —— 改一行配置,本地→远程切换,工作流零改动。
14
+ """
15
+ from __future__ import annotations
16
+
17
+ from typing import Optional
18
+
19
+ from .base import Transport, ToolResult, NoOpTransport
20
+ from .stdio import StdioTransport
21
+ from .http import RemoteHttpTransport
22
+ from .cli import CliTransport
23
+
24
+
25
+ def get_transport(server_name: str, kind: str = "stdio",
26
+ repo_root=None, **kwargs) -> Transport:
27
+ """传输策略工厂。
28
+
29
+ Args:
30
+ server_name: MCP server 名(诊断 + launcher 参数)。
31
+ kind: 传输方式 —— "stdio" | "cli" | "http"。
32
+ repo_root: 仓库根(stdio 用,定位 mcp_launcher)。
33
+ **kwargs: 各 transport 特定参数(http 的 url/token 等)。
34
+ Returns:
35
+ Transport 实例;未知 kind 返回 NoOpTransport(兜底不抛错)。
36
+ """
37
+ if kind == "stdio":
38
+ return StdioTransport(server_name, repo_root=repo_root)
39
+ if kind == "http":
40
+ return RemoteHttpTransport(
41
+ url=kwargs.get("url", ""),
42
+ token=kwargs.get("token"),
43
+ server_name=server_name,
44
+ )
45
+ if kind == "cli":
46
+ script = kwargs.get("script")
47
+ from pathlib import Path
48
+ return CliTransport(script=Path(script) if script else Path(), **{k: v for k, v in kwargs.items() if k != "script"})
49
+ return NoOpTransport()
50
+
51
+
52
+ __all__ = [
53
+ "Transport", "ToolResult", "NoOpTransport",
54
+ "StdioTransport", "RemoteHttpTransport", "CliTransport",
55
+ "get_transport",
56
+ ]
@@ -0,0 +1,87 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ transports.base —— 能力传输策略抽象。
4
+
5
+ 设计模式: Strategy(策略模式)
6
+
7
+ 一个能力(如知识图谱)的"传输方式"是可替换策略:
8
+ - StdioTransport (现状: 本地 spawn MCP server, stdin/stdout 传 JSON-RPC)
9
+ - CliTransport (兜底: 直接跑 kg.py 等脚本, 解析 stdout) — P2
10
+ - RemoteHttpTransport (未来: HTTP POST 到服务器) — P3
11
+
12
+ 工作流面向"能力"编程 (cap.mcp.call("table_impact")),不感知传输细节。
13
+ 配置驱动切换 transport —— 知识图谱从本地 stdio 迁到服务器 http,
14
+ 只需改 McpRegistry 配置 + 填 url,工作流代码零改动。
15
+
16
+ Transport 与具体 MCP server 解耦:Transport 是"一种传输方式",
17
+ McpRegistry 负责"哪个 tool 走哪个 server 的哪个 transport"。
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from dataclasses import dataclass, field
22
+ from typing import Any, Protocol, runtime_checkable
23
+
24
+
25
+ @dataclass
26
+ class ToolResult:
27
+ """工具调用结果(所有 Transport 统一返回类型)。
28
+
29
+ Attributes:
30
+ text: 工具返回的文本内容(MCP 的 content[0].text,或 CLI 的 stdout)。
31
+ is_error: 是否为错误结果(MCP 的 isError,或异常捕获)。
32
+ raw: 原始响应(诊断用,调用方一般不直接用)。
33
+ """
34
+ text: str = ""
35
+ is_error: bool = False
36
+ raw: Any = field(default=None)
37
+
38
+ def __str__(self) -> str:
39
+ return self.text
40
+
41
+
42
+ @runtime_checkable
43
+ class Transport(Protocol):
44
+ """能力传输策略契约。
45
+
46
+ 所有 transport(stdio/http/cli)必须实现此接口。
47
+ McpCap 只通过此接口与底层通信,不感知传输细节。
48
+ """
49
+
50
+ available: bool
51
+
52
+ def list_tools(self) -> list[dict]:
53
+ """枚举此 transport 后端提供的工具(tools/list 语义)。"""
54
+ ...
55
+
56
+ def has_tool(self, name: str) -> bool:
57
+ """某工具是否可用。"""
58
+ ...
59
+
60
+ def call_tool(self, name: str, args: dict | None = None) -> ToolResult:
61
+ """调用工具,返回 ToolResult。失败时 is_error=True 但不抛异常(让调用方决定降级)。"""
62
+ ...
63
+
64
+ def close(self) -> None:
65
+ """清理连接/子进程。"""
66
+ ...
67
+
68
+
69
+ class NoOpTransport:
70
+ """Transport 空实现 —— Null Object。
71
+
72
+ 无可用 transport 时兜底:available=False,方法静默返回空,绝不抛错。
73
+ """
74
+
75
+ available = False
76
+
77
+ def list_tools(self) -> list[dict]:
78
+ return []
79
+
80
+ def has_tool(self, name: str) -> bool:
81
+ return False
82
+
83
+ def call_tool(self, name: str, args: dict | None = None) -> ToolResult:
84
+ return ToolResult(text="", is_error=True)
85
+
86
+ def close(self) -> None:
87
+ pass
@@ -0,0 +1,132 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ transports.cli —— CLI 传输策略(兜底形态)。
4
+
5
+ 设计模式: Strategy(与 StdioTransport 同接口,可替换)
6
+
7
+ 定位:非 QoderWork 宿主(ZCode/Codex/纯 CLI)没有 MCP 客户端时,
8
+ 通过直接跑 kg.py 等脚本子进程获得等价能力。
9
+ 复用 kg_capabilities.py(kg.py 与 kg_mcp_server.py 的共享单一真源),
10
+ 保证输出与 MCP 一致。
11
+
12
+ 机制:
13
+ command_map 映射 tool_name → (subcommand, args_adapter):
14
+ - subcommand: CLI 子命令名(如 "search_code")
15
+ - args_adapter: callable(dict) -> list[str] 参数翻译函数(可选)
16
+ CliTransport 用 subprocess.run 跑脚本 + 子命令,解析 stdout JSON。
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import subprocess
22
+ import sys
23
+ from pathlib import Path
24
+ from typing import Callable, Optional
25
+
26
+ from .base import ToolResult
27
+
28
+ # 默认超时(秒)
29
+ _TIMEOUT = 60
30
+
31
+
32
+ class CliTransport:
33
+ """CLI 传输策略(子进程跑脚本,解析 stdout)。
34
+
35
+ Args:
36
+ script: 脚本路径(如 .qoder/scripts/domain/kg/kg.py)。
37
+ command_map: tool 名 → (subcommand, args_adapter) 映射。
38
+ subcommand: CLI 子命令名。
39
+ args_adapter: callable(dict) -> list[str](可选,None 表示无额外参数)。
40
+ """
41
+
42
+ def __init__(self, script: Path, command_map: Optional[dict] = None):
43
+ self._script = script
44
+ self._command_map = command_map or {}
45
+ self._tools_cache: Optional[list[dict]] = None
46
+ self._tool_index: Optional[set[str]] = None
47
+
48
+ @property
49
+ def available(self) -> bool:
50
+ return self._script.is_file()
51
+
52
+ def list_tools(self) -> list[dict]:
53
+ # CLI 无统一 tools/list;按 command_map 的 key 作为可用工具清单
54
+ if self._tools_cache is None:
55
+ self._tools_cache = [{"name": k} for k in self._command_map]
56
+ self._tool_index = set(self._command_map.keys())
57
+ return self._tools_cache
58
+
59
+ def has_tool(self, name: str) -> bool:
60
+ if self._tool_index is None:
61
+ self.list_tools()
62
+ return name in (self._tool_index or set())
63
+
64
+ def call_tool(self, name: str, args: dict | None = None) -> ToolResult:
65
+ if name not in self._command_map:
66
+ return ToolResult(text="CLI 未映射工具: %s" % name, is_error=True)
67
+
68
+ entry = self._command_map[name]
69
+ if isinstance(entry, tuple):
70
+ subcommand = entry[0]
71
+ adapter = entry[1] if len(entry) > 1 else None
72
+ else:
73
+ subcommand = entry
74
+ adapter = None
75
+
76
+ # 构建 CLI 命令: python script.py subcommand [adapted_args...]
77
+ cmd = [sys.executable, str(self._script), subcommand]
78
+ if adapter and args:
79
+ try:
80
+ cmd.extend(adapter(args))
81
+ except Exception as e:
82
+ return ToolResult(text="参数翻译失败: %s" % str(e)[:120], is_error=True)
83
+ elif args:
84
+ # 默认: key=value 格式
85
+ for k, v in args.items():
86
+ if v is not None:
87
+ cmd.extend(["--%s" % k, str(v)])
88
+
89
+ try:
90
+ proc = subprocess.run(
91
+ cmd,
92
+ capture_output=True,
93
+ text=True,
94
+ timeout=_TIMEOUT,
95
+ encoding="utf-8",
96
+ errors="replace",
97
+ )
98
+ except subprocess.TimeoutExpired:
99
+ return ToolResult(text="CLI 超时 (%ds): %s" % (_TIMEOUT, name), is_error=True)
100
+ except Exception as e:
101
+ return ToolResult(text="CLI 执行失败: %s" % str(e)[:120], is_error=True)
102
+
103
+ # 解析 stdout:优先 JSON,降级纯文本
104
+ stdout = (proc.stdout or "").strip()
105
+ stderr = (proc.stderr or "").strip()
106
+
107
+ if proc.returncode != 0 and not stdout:
108
+ return ToolResult(
109
+ text="CLI 错误 (exit=%d): %s" % (proc.returncode, stderr[:200]),
110
+ is_error=True,
111
+ )
112
+
113
+ # 尝试 JSON 解析
114
+ if stdout:
115
+ try:
116
+ data = json.loads(stdout)
117
+ # MCP server 输出格式: {"content": [{"text": "..."}]}
118
+ if isinstance(data, dict) and "content" in data:
119
+ text = data["content"][0].get("text", "") if data["content"] else ""
120
+ return ToolResult(text=text, is_error=data.get("isError", False))
121
+ # 纯 JSON 字符串或列表
122
+ if isinstance(data, str):
123
+ return ToolResult(text=data)
124
+ return ToolResult(text=json.dumps(data, ensure_ascii=False))
125
+ except (json.JSONDecodeError, IndexError, KeyError):
126
+ pass
127
+
128
+ # 降级: 纯文本输出
129
+ return ToolResult(text=stdout or stderr or "(无输出)")
130
+
131
+ def close(self) -> None:
132
+ pass # CLI 无状态,每次调用 spawn 子进程
@@ -0,0 +1,141 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ transports.http —— 远程 HTTP MCP 传输策略。
4
+
5
+ 设计模式: Strategy(与 StdioTransport 同接口,可替换)
6
+
7
+ 定位:满足现在的本地 stdio,留扩展给未来的服务器化。
8
+ 知识图谱/禅道等能力未来可上服务器,以 HTTP MCP (Streamable HTTP) 暴露。
9
+ 届时只需在 McpRegistry 配置把 server 的 transport 从 "stdio" 改成 "http" + 填 url,
10
+ 工作流代码 (cap.mcp.call) 零改动。
11
+
12
+ 机制:
13
+ POST {url}/mcp 发 JSON-RPC 2.0 请求,解析 JSON 响应。
14
+ 支持 Bearer token 认证。
15
+ 超时可配置,失败自动分类(网络/超时/协议错误)。
16
+
17
+ 依赖:仅 stdlib(urllib),无第三方包。
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import urllib.error
23
+ import urllib.request
24
+ from typing import Optional
25
+
26
+ from .base import ToolResult
27
+
28
+ # 默认超时(秒)
29
+ _TIMEOUT = 30
30
+ # MCP 协议版本
31
+ _PROTOCOL_VERSION = "2024-11-05"
32
+
33
+
34
+ class RemoteHttpTransport:
35
+ """远程 HTTP MCP 传输策略。
36
+
37
+ Args:
38
+ url: 远程 MCP endpoint(如 https://kg.corp/mcp)。
39
+ token: 认证 token(Bearer)。可选。
40
+ server_name: server 名(诊断用)。
41
+ timeout: 请求超时秒数。
42
+ """
43
+
44
+ def __init__(self, url: str, token: Optional[str] = None,
45
+ server_name: str = "", timeout: int = _TIMEOUT):
46
+ self._url = (url or "").rstrip("/")
47
+ self._token = token
48
+ self._server = server_name
49
+ self._timeout = timeout
50
+ self._tools_cache: Optional[list[dict]] = None
51
+ self._tool_index: Optional[set[str]] = None
52
+ self._req_id = 0
53
+ self._last_error: Optional[str] = None
54
+
55
+ @property
56
+ def available(self) -> bool:
57
+ """有 url 即视为可尝试(真实可达性在首次调用时检验)。"""
58
+ return bool(self._url)
59
+
60
+ def _next_id(self) -> int:
61
+ self._req_id += 1
62
+ return self._req_id
63
+
64
+ def _post_jsonrpc(self, method: str, params: dict | None = None) -> dict:
65
+ """发 JSON-RPC 请求,返回响应 dict。
66
+
67
+ 失败抛 RuntimeError(调用方捕获转 ToolResult)。
68
+ """
69
+ rid = self._next_id()
70
+ body: dict = {"jsonrpc": "2.0", "id": rid, "method": method}
71
+ if params is not None:
72
+ body["params"] = params
73
+
74
+ data = json.dumps(body, ensure_ascii=False).encode("utf-8")
75
+ endpoint = self._url + "/mcp"
76
+
77
+ req = urllib.request.Request(
78
+ endpoint,
79
+ data=data,
80
+ headers={"Content-Type": "application/json"},
81
+ method="POST",
82
+ )
83
+ if self._token:
84
+ req.add_header("Authorization", "Bearer %s" % self._token)
85
+
86
+ try:
87
+ with urllib.request.urlopen(req, timeout=self._timeout) as resp:
88
+ raw = resp.read().decode("utf-8", errors="replace")
89
+ except urllib.error.HTTPError as e:
90
+ raise RuntimeError("HTTP %d: %s" % (e.code, e.read().decode("utf-8", errors="replace")[:200]))
91
+ except urllib.error.URLError as e:
92
+ raise RuntimeError("连接失败: %s" % str(e.reason)[:120])
93
+ except TimeoutError:
94
+ raise RuntimeError("超时 (%ds)" % self._timeout)
95
+
96
+ try:
97
+ result = json.loads(raw)
98
+ except json.JSONDecodeError:
99
+ raise RuntimeError("非 JSON 响应: %s" % raw[:120])
100
+
101
+ if "error" in result:
102
+ msg = result["error"].get("message", "unknown")
103
+ raise RuntimeError("MCP error: %s" % str(msg)[:200])
104
+
105
+ return result
106
+
107
+ def list_tools(self) -> list[dict]:
108
+ """POST tools/list,解析工具清单。"""
109
+ if self._tools_cache is not None:
110
+ return self._tools_cache
111
+ try:
112
+ resp = self._post_jsonrpc("tools/list")
113
+ tools = resp.get("result", {}).get("tools", [])
114
+ self._tools_cache = tools
115
+ self._tool_index = {t.get("name", "") for t in tools}
116
+ except Exception as e:
117
+ self._last_error = str(e)[:120]
118
+ self._tools_cache = []
119
+ self._tool_index = set()
120
+ return self._tools_cache
121
+
122
+ def has_tool(self, name: str) -> bool:
123
+ if self._tool_index is None:
124
+ self.list_tools()
125
+ return name in (self._tool_index or set())
126
+
127
+ def call_tool(self, name: str, args: dict | None = None) -> ToolResult:
128
+ """POST tools/call,解析 content[0].text。"""
129
+ try:
130
+ resp = self._post_jsonrpc("tools/call", {"name": name, "arguments": args or {}})
131
+ except Exception as e:
132
+ return ToolResult(text="HTTP transport 错误: %s" % str(e)[:120], is_error=True)
133
+
134
+ result = resp.get("result", {})
135
+ content = result.get("content", [])
136
+ text = content[0].get("text", "") if content else ""
137
+ return ToolResult(text=text, is_error=result.get("isError", False), raw=resp)
138
+
139
+ def close(self) -> None:
140
+ # HTTP 无长连接需关(urllib 无连接池)
141
+ pass