@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,316 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ adapters.cli —— 纯文件降级 Adapter。
4
+
5
+ 设计模式: Adapter(纯 Python 文件实现,无任何宿主依赖)
6
+
7
+ 核心能力的兜底实现,保证纯 CLI / 任意宿主环境也能跑。
8
+ 这是"外挂式"的底牌:即便宿主什么能力都不给,工作流仍可用。
9
+
10
+ 实现:
11
+ - FileRepoCap: 向上找含 .qoder/scripts/ 的目录(复用 common.paths)
12
+ - FileIdentityCap: 读 .developer(复用 common.paths.get_developer)
13
+ - FileContextCap: 读配置/偏好文件
14
+ - FileMemoryCap: journal 文件记忆(纯文本追加,无语义检索)
15
+ """
16
+ from __future__ import annotations
17
+
18
+ from datetime import datetime
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+
23
+ def _import_paths():
24
+ """延迟导入 common.paths,避免循环依赖与硬失败。
25
+
26
+ Returns:
27
+ (get_repo_root, get_developer, get_developer_journal_dir) 元组,
28
+ 任一不可用则对应位为 None。
29
+ """
30
+ try:
31
+ from foundation.core.paths import (
32
+ get_repo_root,
33
+ get_developer,
34
+ get_developer_journal_dir,
35
+ )
36
+ return get_repo_root, get_developer, get_developer_journal_dir
37
+ except ImportError:
38
+ return None, None, None
39
+
40
+
41
+ class FileRepoCap:
42
+ """仓库根发现 —— 向上找含 .qoder/scripts/ 的目录。无 ~/.qoderwork/ 依赖。"""
43
+
44
+ available = True # 核心能力,永远可用
45
+
46
+ def root(self) -> Path:
47
+ get_repo_root, _, _ = _import_paths()
48
+ if get_repo_root:
49
+ try:
50
+ return get_repo_root()
51
+ except Exception:
52
+ pass
53
+ # 兜底:__file__ 向上找
54
+ current = Path(__file__).resolve()
55
+ for _ in range(10):
56
+ if (current / ".qoder" / "scripts").is_dir():
57
+ return current
58
+ parent = current.parent
59
+ if parent == current:
60
+ break
61
+ current = parent
62
+ return Path.cwd()
63
+
64
+
65
+ class FileIdentityCap:
66
+ """身份 —— 读 .developer 文件。"""
67
+
68
+ available = True
69
+
70
+ def who(self) -> str | None:
71
+ _, get_developer, _ = _import_paths()
72
+ if get_developer:
73
+ try:
74
+ dev = get_developer()
75
+ if dev:
76
+ return dev
77
+ except Exception:
78
+ pass
79
+ # 兜底:直接读 .developer
80
+ dev_file = FileRepoCap().root() / ".developer"
81
+ if dev_file.is_file():
82
+ return dev_file.read_text(encoding="utf-8").strip() or None
83
+ return None
84
+
85
+ def role(self) -> str | None:
86
+ """从 member.json 读取角色(pm/dev/qa/test/admin)。"""
87
+ try:
88
+ from foundation.identity.identity import get_role
89
+ return get_role(self.who())
90
+ except Exception:
91
+ return None
92
+
93
+ def zentao_account(self) -> str | None:
94
+ """从 member.json 读取禅道账号映射。"""
95
+ try:
96
+ from foundation.identity.identity import get_member
97
+ member = get_member(self.who() or "")
98
+ if member:
99
+ return member.get("zentao_account") or None
100
+ except Exception:
101
+ pass
102
+ return None
103
+
104
+
105
+ class FileContextCap:
106
+ """上下文 —— 读配置/偏好文件。"""
107
+
108
+ available = True
109
+
110
+ def preferences(self) -> dict:
111
+ """读 .qoder/settings.json 或 .qoder/config.yaml 中的偏好。"""
112
+ try:
113
+ repo = FileRepoCap().root()
114
+ import json as _json
115
+ # settings.json 优先
116
+ sf = repo / ".qoder" / "settings.json"
117
+ if sf.is_file():
118
+ with open(sf, encoding="utf-8") as f:
119
+ return _json.load(f)
120
+ # config.yaml 降级
121
+ import yaml
122
+ cf = repo / ".qoder" / "config.yaml"
123
+ if cf.is_file():
124
+ with open(cf, encoding="utf-8") as f:
125
+ return yaml.safe_load(f) or {}
126
+ except Exception:
127
+ pass
128
+ return {}
129
+
130
+ def recent_learnings(self, limit: int = 5) -> list[dict]:
131
+ """读当前开发者的 feedback.jsonl 最近 N 条学习事件。"""
132
+ try:
133
+ _, get_developer, get_journal = _import_paths()
134
+ if not (get_developer and get_journal):
135
+ return []
136
+ dev = get_developer()
137
+ if not dev:
138
+ return []
139
+ fb = get_journal(dev) / "feedback.jsonl"
140
+ if not fb.is_file():
141
+ return []
142
+ import json as _json
143
+ with open(fb, encoding="utf-8") as f:
144
+ lines = [l.strip() for l in f if l.strip()]
145
+ results = []
146
+ for line in reversed(lines[-limit:]):
147
+ try:
148
+ results.append(_json.loads(line))
149
+ except _json.JSONDecodeError:
150
+ continue
151
+ return results
152
+ except Exception:
153
+ return []
154
+
155
+
156
+ class FileMemoryCap:
157
+ """记忆 —— journal 文件降级(feedback.jsonl + memory.md)。
158
+
159
+ 存储位置: workspace/members/{dev}/journal/(复用 common.paths)。
160
+ available 探测到 journal 目录可写时为 True。
161
+
162
+ 实现:
163
+ - remember: 追加到 memory.md(带 key 标题)
164
+ - recall: 扫 memory.md + feedback.jsonl 关键词匹配(非语义,纯子串)
165
+ - get_fact: 精确匹配 memory.md 的 key 标题块
166
+ - list_recent: 返回最近 N 条(memory.md 最新 + feedback.jsonl 最新)
167
+ """
168
+
169
+ def __init__(self):
170
+ self._available = self._probe()
171
+
172
+ def _probe(self) -> bool:
173
+ try:
174
+ _, get_developer, get_journal = _import_paths()
175
+ if not (get_developer and get_journal):
176
+ return False
177
+ dev = get_developer()
178
+ if not dev:
179
+ return False
180
+ journal_dir = get_journal(dev)
181
+ journal_dir.mkdir(parents=True, exist_ok=True)
182
+ return journal_dir.is_dir()
183
+ except Exception:
184
+ return False
185
+
186
+ def _journal_dir(self):
187
+ _, get_developer, get_journal = _import_paths()
188
+ if not (get_developer and get_journal):
189
+ return None
190
+ try:
191
+ dev = get_developer()
192
+ if not dev:
193
+ return None
194
+ return get_journal(dev)
195
+ except Exception:
196
+ return None
197
+
198
+ @property
199
+ def available(self) -> bool:
200
+ return self._available
201
+
202
+ def recall(self, query: str) -> list[dict]:
203
+ """关键词检索:扫 memory.md + feedback.jsonl,返回含 query 的条目。"""
204
+ if not self._available or not query:
205
+ return []
206
+ jdir = self._journal_dir()
207
+ if jdir is None:
208
+ return []
209
+ results = []
210
+
211
+ # 1) memory.md 关键词匹配
212
+ mem_file = jdir / "memory.md"
213
+ if mem_file.is_file():
214
+ try:
215
+ content = mem_file.read_text(encoding="utf-8")
216
+ for block in content.split("\n## "):
217
+ if query.lower() in block.lower():
218
+ results.append({"source": "memory.md", "text": block.strip()})
219
+ except Exception:
220
+ pass
221
+
222
+ # 2) feedback.jsonl 关键词匹配
223
+ fb_file = jdir / "feedback.jsonl"
224
+ if fb_file.is_file():
225
+ import json as _json
226
+ try:
227
+ with open(fb_file, encoding="utf-8") as f:
228
+ for line in f:
229
+ line = line.strip()
230
+ if not line:
231
+ continue
232
+ try:
233
+ entry = _json.loads(line)
234
+ text = _json.dumps(entry, ensure_ascii=False)
235
+ if query.lower() in text.lower():
236
+ results.append({"source": "feedback.jsonl", "data": entry})
237
+ except _json.JSONDecodeError:
238
+ continue
239
+ except Exception:
240
+ pass
241
+
242
+ return results[:20] # 限制返回数
243
+
244
+ def remember(self, key: str, content: str) -> None:
245
+ if not self._available:
246
+ return
247
+ _, get_developer, get_journal = _import_paths()
248
+ if not (get_developer and get_journal):
249
+ return
250
+ try:
251
+ dev = get_developer()
252
+ journal_dir = get_journal(dev)
253
+ ts = datetime.now().strftime("%Y-%m-%d %H:%M")
254
+ entry = f"\n## [{ts}] {key}\n{content}\n"
255
+ with open(journal_dir / "memory.md", "a", encoding="utf-8") as f:
256
+ f.write(entry)
257
+ except Exception:
258
+ pass
259
+
260
+ def get_fact(self, key: str) -> str | None:
261
+ """精确匹配 memory.md 中以 key 为标题的块。"""
262
+ if not self._available or not key:
263
+ return None
264
+ jdir = self._journal_dir()
265
+ if jdir is None:
266
+ return None
267
+ mem_file = jdir / "memory.md"
268
+ if not mem_file.is_file():
269
+ return None
270
+ try:
271
+ content = mem_file.read_text(encoding="utf-8")
272
+ for block in content.split("\n## "):
273
+ # 块标题形如 "[2026-06-18 12:30] 某key"
274
+ if "] " + key in block or block.strip().endswith(key):
275
+ return block.strip()
276
+ except Exception:
277
+ pass
278
+ return None
279
+
280
+ def list_recent(self, limit: int = 10) -> list[dict]:
281
+ """返回最近 N 条记忆(memory.md 倒序 + feedback.jsonl 倒序)。"""
282
+ if not self._available:
283
+ return []
284
+ jdir = self._journal_dir()
285
+ if jdir is None:
286
+ return []
287
+ results = []
288
+
289
+ # memory.md 块倒序
290
+ mem_file = jdir / "memory.md"
291
+ if mem_file.is_file():
292
+ try:
293
+ content = mem_file.read_text(encoding="utf-8")
294
+ blocks = [b.strip() for b in content.split("\n## ") if b.strip()]
295
+ for b in reversed(blocks[-limit:]):
296
+ results.append({"source": "memory.md", "text": b})
297
+ except Exception:
298
+ pass
299
+
300
+ # feedback.jsonl 最后 N 条
301
+ fb_file = jdir / "feedback.jsonl"
302
+ if fb_file.is_file():
303
+ import json as _json
304
+ try:
305
+ with open(fb_file, encoding="utf-8") as f:
306
+ lines = [l.strip() for l in f if l.strip()]
307
+ for line in reversed(lines[-limit:]):
308
+ try:
309
+ entry = _json.loads(line)
310
+ results.append({"source": "feedback.jsonl", "data": entry})
311
+ except _json.JSONDecodeError:
312
+ continue
313
+ except Exception:
314
+ pass
315
+
316
+ return results[:limit]
@@ -0,0 +1,334 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ adapters.mcp —— L1 MCP 能力统一入口(已接通 transport 层)。
4
+
5
+ 设计模式: Adapter + Facade
6
+
7
+ 职责:
8
+ 把 McpRegistry(tool→server→transport 路由)适配成 McpCap 的
9
+ has_tool/call/list_tools 接口,供 Nexus Facade 统一暴露。
10
+
11
+ L1 能力定位:禅道/知识图谱/MySQL/蓝湖/playwright 已 MCP 化,
12
+ 通过本入口跨宿主统一调用,复用已有 MCP 资产,绝不重写它们。
13
+
14
+ 跨宿主行为:
15
+ - QoderWork / 任意宿主:通过 mcp_launcher spawn 本地 MCP server(stdio transport)
16
+ - 未来服务器:改 McpRegistry.SERVERS 配置 kind=http + url,工作流零改动
17
+ - 无可用 server:available=False,has_tool 返回 False(静默回退)
18
+
19
+ McpMemoryProvider:memory 职责链的 MCP provider,接通 kg.duckdb learning_events 表。
20
+ 当 mem0/Zep MCP server 更成熟时可替换为 MCP 版本,接口不变。
21
+ """
22
+ from __future__ import annotations
23
+
24
+ from typing import Any, Optional
25
+
26
+ from ..registry_mcp import McpRegistry
27
+ from protocol.transports import ToolResult
28
+
29
+
30
+ # 单例 Registry(同一进程复用进程池,避免重复 spawn)
31
+ _REGISTRY: Optional[McpRegistry] = None
32
+
33
+
34
+ def _get_registry() -> McpRegistry:
35
+ global _REGISTRY
36
+ if _REGISTRY is None:
37
+ _REGISTRY = McpRegistry()
38
+ return _REGISTRY
39
+
40
+
41
+ class McpCap:
42
+ """L1 MCP 能力统一入口 —— 复用已有 MCP server(禅道/知识图谱/MySQL/蓝湖)。
43
+
44
+ 工作流调用:
45
+ cap = nexus.resolve()
46
+ result = cap.mcp.call("search_code", {"keyword": "保险"})
47
+ # 自动路由:MCP 可用走 MCP,不可用走 wlkj.py(CLI 降级)
48
+ """
49
+
50
+ def __init__(self, mcp_servers=None):
51
+ # ★ 空 mcp_servers (= [] 或 None) 视为"不限制 = 用全部本地 server"。
52
+ # 原版: mcp_servers=[] (空列表, is not None) → filtered 成空 → _servers={} → 0 工具,
53
+ # 整个 cap.mcp 自废武功。但本地 stdio transport (StdioTransport) 通过 mcp_launcher
54
+ # spawn 子进程, 不依赖宿主是否原生支持 MCP —— CLI / Codex / Claude 同样能起 server。
55
+ # 故: 宿主未声明 mcp_servers 或声明为空 = "没有宿主原生限制, 用全部本地可起的"。
56
+ if mcp_servers: # 非空列表才过滤
57
+ from ..registry_mcp import SERVERS
58
+ filtered = {k: v for k, v in SERVERS.items() if k in mcp_servers}
59
+ self._registry = McpRegistry(servers=filtered)
60
+ else:
61
+ self._registry = _get_registry()
62
+
63
+ @property
64
+ def available(self) -> bool:
65
+ """是否有任一 MCP server 可用。"""
66
+ try:
67
+ return self._registry.available()
68
+ except Exception:
69
+ return False
70
+
71
+ # ------------------------------------------------------------------
72
+ # McpCap 接口
73
+ # ------------------------------------------------------------------
74
+
75
+ def has_tool(self, name: str) -> bool:
76
+ """某 MCP 工具是否可用(跨所有已配置 server 查找)。"""
77
+ try:
78
+ return self._registry.has_tool(name)
79
+ except Exception:
80
+ return False
81
+
82
+ def call(self, name: str, args: Optional[dict] = None,
83
+ server: Optional[str] = None) -> ToolResult:
84
+ """调用 MCP 工具(自动 CLI 降级)。
85
+
86
+ 优先走 MCP;MCP 不可用时自动降级到 wlkj.py CLI 脚本。
87
+ 工作流永远写 cap.mcp.call("tool_name", args),零宿主感知。
88
+ """
89
+ # 1. MCP 可用 → 走 MCP
90
+ result = self._registry.call(name, args, server=server)
91
+ if not result.is_error:
92
+ return result
93
+ # 2. CLI 降级(MCP 不可用或调用失败)
94
+ cli_text = self._cli_fallback(name, args or {})
95
+ if cli_text is not None:
96
+ return ToolResult(text=cli_text)
97
+ return result # 无降级映射,返回原始错误
98
+
99
+ # ------------------------------------------------------------------
100
+ # CLI 降级(MCP 不可用时走 wlkj.py)
101
+ # ------------------------------------------------------------------
102
+
103
+ # MCP 工具名 → (wlkj 命令, 基础参数) 映射
104
+ # 只映射有 wlkj.py 对应的知识图谱/任务工具。
105
+ # MySQL/禅道/蓝湖等 MCP 专属工具不映射(MCP 不可用时返回错误提示)。
106
+ _TOOL_TO_WLKJ = {
107
+ "search_code": ("kg", ["search"]),
108
+ "search_api": ("kg", ["api"]),
109
+ "search_prd": ("kg", ["prd"]),
110
+ "context_pack": ("kg", ["context"]),
111
+ "get_impact": ("kg", ["impact"]),
112
+ "context_360": ("kg", ["context360"]),
113
+ "coverage_matrix": ("kg", ["coverage"]),
114
+ "feature_overview": ("kg", ["feature"]),
115
+ "get_workflow": ("kg", ["workflow"]),
116
+ "multi_hop": ("kg", ["hop"]),
117
+ "search_wiki": ("kg", ["wiki"]),
118
+ "fill_prototype": ("kg", ["fill-prototype"]),
119
+ "get_design_system": ("kg", ["design-system"]),
120
+ "list_tasks": ("task", ["list"]),
121
+ "create_task": ("task", ["create"]),
122
+ "start_task": ("task", ["start"]),
123
+ "finish_task": ("task", ["finish"]),
124
+ }
125
+
126
+ def _cli_fallback(self, tool_name: str, args: dict) -> Optional[str]:
127
+ """MCP 工具名 → wlkj.py 执行。无映射返回 None。"""
128
+ entry = self._TOOL_TO_WLKJ.get(tool_name)
129
+ if entry is None:
130
+ return None
131
+ cmd, base_args = entry
132
+ cli_args = list(base_args)
133
+ # 从 MCP args 构建命令行参数
134
+ if "keyword" in args:
135
+ cli_args.append(str(args["keyword"]))
136
+ if "platform" in args and args["platform"]:
137
+ cli_args.extend(["--platform", str(args["platform"])])
138
+ if "role" in args and args["role"]:
139
+ cli_args.extend(["--role", str(args["role"])])
140
+ if "endpoint" in args:
141
+ cli_args.append(str(args["endpoint"]))
142
+ if "symbol" in args:
143
+ cli_args.append(str(args["symbol"]))
144
+ if "depth" in args:
145
+ cli_args.extend(["--depth", str(args["depth"])])
146
+ if "feature" in args:
147
+ cli_args.append(str(args["feature"]))
148
+ if "module" in args:
149
+ cli_args.append(str(args["module"]))
150
+ if "title" in args:
151
+ cli_args.append(str(args["title"]))
152
+ if "table" in args:
153
+ cli_args.append(str(args["table"]))
154
+ if "column" in args:
155
+ cli_args.append(str(args["column"]))
156
+ if "db" in args and args["db"]:
157
+ cli_args.extend(["--db", str(args["db"])])
158
+ if "env" in args and args["env"]:
159
+ cli_args.extend(["--env", str(args["env"])])
160
+ return self._run_wlkj(cmd, cli_args)
161
+
162
+ @staticmethod
163
+ def _run_wlkj(cmd: str, args: list) -> str:
164
+ """调 wlkj.py 执行,返回 stdout。"""
165
+ import subprocess, sys, os
166
+ try:
167
+ from foundation.core.paths import get_repo_root
168
+ repo = str(get_repo_root())
169
+ except Exception:
170
+ repo = os.getcwd()
171
+ wlkj = os.path.join(repo, ".qoder", "scripts", "orchestration", "wlkj.py")
172
+ try:
173
+ r = subprocess.run(
174
+ [sys.executable, wlkj, cmd] + args,
175
+ capture_output=True, text=True, timeout=60,
176
+ encoding="utf-8", errors="replace",
177
+ )
178
+ if r.returncode == 0:
179
+ return r.stdout.strip()
180
+ return "[cli-fallback error] %s" % (r.stderr or r.stdout)[:200]
181
+ except subprocess.TimeoutExpired:
182
+ return "[cli-fallback error] timeout (60s)"
183
+ except Exception as e:
184
+ return "[cli-fallback error] %s" % str(e)[:100]
185
+
186
+ def list_tools(self, keyword: str = "") -> list[dict]:
187
+ """枚举可用 MCP 工具(可选按 keyword 过滤)。"""
188
+ try:
189
+ self._registry._ensure_index()
190
+ tools = []
191
+ for server_name in self._registry._servers:
192
+ t = self._registry._pool.get(server_name)
193
+ if t is None:
194
+ continue
195
+ for tool in (t._tools_cache or []):
196
+ if keyword and keyword not in tool.get("name", ""):
197
+ continue
198
+ tool_copy = dict(tool)
199
+ tool_copy["_server"] = server_name
200
+ tools.append(tool_copy)
201
+ return tools
202
+ except Exception:
203
+ return []
204
+
205
+ def describe(self) -> dict:
206
+ """诊断:各 server 状态。"""
207
+ try:
208
+ return self._registry.describe()
209
+ except Exception as e:
210
+ return {"error": str(e)[:120]}
211
+
212
+
213
+ class McpMemoryProvider:
214
+ """memory 职责链的 MCP provider —— 接通 kg.duckdb learning_events 表。
215
+
216
+ 设计模式: Adapter(把 kg.duckdb 适配成 MemoryCap 契约)
217
+
218
+ 职责链位置: 链头优先(available=True),纯 CLI 环境也能用。
219
+ 当 mem0/Zep MCP server 更成熟时,可替换为 MCP 版本,接口不变。
220
+
221
+ 数据源: kg.duckdb 的 learning_events 表(id, ts, dev, event, data)。
222
+ 复用 learn_aggregate.py 的写入逻辑,保证 CLI 和 MCP 行为一致。
223
+ """
224
+
225
+ def __init__(self):
226
+ self._available: bool | None = None # 懒探测
227
+
228
+ @property
229
+ def available(self) -> bool:
230
+ """kg.duckdb 存在且可读写即为可用。"""
231
+ if self._available is None:
232
+ try:
233
+ from foundation.protocol.duckdb_conn import get_db
234
+ con = get_db()
235
+ con.execute("""CREATE TABLE IF NOT EXISTS learning_events (
236
+ id TEXT PRIMARY KEY, ts TEXT NOT NULL, dev TEXT NOT NULL,
237
+ event TEXT NOT NULL, data JSON)""")
238
+ con.close()
239
+ self._available = True
240
+ except Exception:
241
+ self._available = False
242
+ return self._available
243
+
244
+ def recall(self, query: str) -> list[dict]:
245
+ """语义检索记忆: 按 event 或 data 模糊匹配。"""
246
+ if not self.available:
247
+ return []
248
+ try:
249
+ from foundation.protocol.duckdb_conn import get_db
250
+ con = get_db()
251
+ rows = con.execute(
252
+ """SELECT id, ts, dev, event, data FROM learning_events
253
+ WHERE event LIKE '%' || ? || '%' OR data LIKE '%' || ? || '%'
254
+ ORDER BY ts DESC LIMIT 20""",
255
+ [query, query],
256
+ ).fetchall()
257
+ con.close()
258
+ return [
259
+ {"id": r[0], "ts": r[1], "dev": r[2], "event": r[3], "data": r[4]}
260
+ for r in rows
261
+ ]
262
+ except Exception:
263
+ return []
264
+
265
+ def remember(self, key: str, content: str) -> None:
266
+ """写入一条记忆: 插入 learning_events 表。"""
267
+ if not self.available:
268
+ return
269
+ try:
270
+ import hashlib
271
+ import json
272
+ import time as _time
273
+ from foundation.protocol.duckdb_conn import get_db, init_schema
274
+ con = get_db()
275
+ init_schema(con)
276
+ eid = hashlib.md5(("%s:%s:%s" % (key, content, _time.time())).encode()).hexdigest()[:16]
277
+ ts = _time.strftime("%Y-%m-%dT%H:%M:%S")
278
+ data = json.dumps({"content": content}, ensure_ascii=False)
279
+ con.execute(
280
+ "INSERT OR IGNORE INTO learning_events(id, ts, dev, event, data) VALUES (?,?,?,?,?)",
281
+ [eid, ts, "", key, data],
282
+ )
283
+ con.close()
284
+ except Exception:
285
+ pass
286
+
287
+ def get_fact(self, key: str) -> str | None:
288
+ """按 event 精确查找,返回 data 字段(JSON 字符串)。"""
289
+ if not self.available:
290
+ return None
291
+ try:
292
+ from foundation.protocol.duckdb_conn import get_db
293
+ con = get_db()
294
+ row = con.execute(
295
+ "SELECT data FROM learning_events WHERE event = ? ORDER BY ts DESC LIMIT 1",
296
+ [key],
297
+ ).fetchone()
298
+ con.close()
299
+ if not row:
300
+ return None
301
+ data = row[0]
302
+ if isinstance(data, str):
303
+ return data
304
+ import json as _json
305
+ return _json.dumps(data, ensure_ascii=False) if data else None
306
+ except Exception:
307
+ return None
308
+
309
+ def list_recent(self, limit: int = 10) -> list[dict]:
310
+ """最近 N 条记忆。"""
311
+ if not self.available:
312
+ return []
313
+ try:
314
+ import json as _json
315
+ from foundation.protocol.duckdb_conn import get_db
316
+ con = get_db()
317
+ rows = con.execute(
318
+ "SELECT id, ts, dev, event, data FROM learning_events ORDER BY ts DESC LIMIT ?",
319
+ [limit],
320
+ ).fetchall()
321
+ con.close()
322
+ results = []
323
+ for r in rows:
324
+ data = r[4]
325
+ # DuckDB JSON 列可能返回字符串,需解析
326
+ if isinstance(data, str):
327
+ try:
328
+ data = _json.loads(data)
329
+ except (_json.JSONDecodeError, TypeError):
330
+ data = {"raw": data}
331
+ results.append({"id": r[0], "ts": r[1], "dev": r[2], "event": r[3], "data": data or {}})
332
+ return results
333
+ except Exception:
334
+ return []