@hupan56/wlkj 3.3.12 → 3.3.14

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 (103) hide show
  1. package/bin/cli.js +1 -23
  2. package/package.json +29 -29
  3. package/templates/qoder/agents/design-agent.md +20 -0
  4. package/templates/qoder/agents/spec-generator.md +21 -0
  5. package/templates/qoder/commands/optional/wl-spec.md +2 -12
  6. package/templates/qoder/commands/optional/wl-status.md +0 -8
  7. package/templates/qoder/commands/wl-code.md +0 -8
  8. package/templates/qoder/commands/wl-commit.md +1 -11
  9. package/templates/qoder/commands/wl-init.md +129 -129
  10. package/templates/qoder/commands/wl-prd.md +0 -24
  11. package/templates/qoder/commands/wl-search.md +11 -26
  12. package/templates/qoder/commands/wl-task.md +1 -11
  13. package/templates/qoder/commands/wl-test.md +0 -8
  14. package/templates/qoder/config.yaml +8 -3
  15. package/templates/qoder/hooks/session-start.py +365 -384
  16. package/templates/qoder/hooks/stop-eval.py +254 -383
  17. package/templates/qoder/scripts/capability/adapters/mcp.py +314 -322
  18. package/templates/qoder/scripts/capability/registry.py +248 -245
  19. package/templates/qoder/scripts/capability/registry_mcp.py +4 -10
  20. package/templates/qoder/scripts/capability/smoke_test_report.json.new +94 -0
  21. package/templates/qoder/scripts/deployment/setup/init_doctor.py +2 -6
  22. package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +14 -0
  23. package/templates/qoder/scripts/deployment/setup/setup.py +2 -16
  24. package/templates/qoder/scripts/domain/integration/return_to_platform.py +2 -4
  25. package/templates/qoder/scripts/domain/integration/spec_upload.py +4 -4
  26. package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +12 -12
  27. package/templates/qoder/scripts/domain/kg/build/build_relations.py +12 -12
  28. package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +10 -0
  29. package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +57 -0
  30. package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +62 -0
  31. package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +146 -0
  32. package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +64 -0
  33. package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +52 -0
  34. package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +104 -0
  35. package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +55 -0
  36. package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +95 -0
  37. package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +8 -0
  38. package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +22 -0
  39. package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +55 -0
  40. package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +90 -0
  41. package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +9 -0
  42. package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +206 -0
  43. package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +132 -0
  44. package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +80 -0
  45. package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +59 -0
  46. package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +93 -0
  47. package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +421 -0
  48. package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +9 -0
  49. package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +79 -0
  50. package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +99 -0
  51. package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +69 -0
  52. package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +78 -0
  53. package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +8 -0
  54. package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +105 -0
  55. package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +153 -0
  56. package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +120 -0
  57. package/templates/qoder/scripts/domain/kg/graph/kg_link_db.py +235 -0
  58. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +5 -41
  59. package/templates/qoder/scripts/domain/kg/kg.py +0 -14
  60. package/templates/qoder/scripts/domain/kg/search/enrich_prompt.py +238 -0
  61. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  62. package/templates/qoder/scripts/domain/kg/switch_project.py +2 -2
  63. package/templates/qoder/scripts/domain/kg/sync_repowiki.py +109 -0
  64. package/templates/qoder/scripts/domain/task/wlkj_panel.py +1348 -1503
  65. package/templates/qoder/scripts/engine/poller.py +219 -0
  66. package/templates/qoder/scripts/foundation/bootstrap.py +145 -145
  67. package/templates/qoder/scripts/orchestration/wlkj.py +189 -306
  68. package/templates/qoder/settings.json +0 -8
  69. package/templates/qoder/skills/wl-spec/SKILL.md +1 -1
  70. package/templates/qoder/commands/wl-data.md +0 -46
  71. package/templates/qoder/commands/wl-fix.md +0 -47
  72. package/templates/qoder/commands/wl-knowledge.md +0 -41
  73. package/templates/qoder/commands/wl-review.md +0 -49
  74. package/templates/qoder/contracts/insight.md +0 -55
  75. package/templates/qoder/hooks/pre-tool-use-commit.py +0 -124
  76. package/templates/qoder/scripts/foundation/io/context_cache.py +0 -94
  77. package/templates/qoder/scripts/tool_guide.md +0 -70
  78. package/templates/qoder/scripts/validation/eval/alignment_matrix.py +0 -176
  79. package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +0 -110
  80. package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +0 -104
  81. package/templates/qoder/scripts/validation/eval/bf_score.py +0 -218
  82. package/templates/qoder/scripts/validation/eval/code_flywheel.py +0 -150
  83. package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +0 -156
  84. package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +0 -23
  85. package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +0 -143
  86. package/templates/qoder/scripts/validation/eval/gradient_matrix.py +0 -261
  87. package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +0 -33
  88. package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +0 -105
  89. package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +0 -118
  90. package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +0 -128
  91. package/templates/qoder/scripts/validation/eval/prd_flywheel.py +0 -148
  92. package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +0 -166
  93. package/templates/qoder/scripts/validation/eval/recall_flywheel.py +0 -148
  94. package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +0 -139
  95. package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +0 -188
  96. package/templates/qoder/scripts/validation/eval/task_flywheel.py +0 -124
  97. package/templates/qoder/scripts/validation/eval/token_flywheel.py +0 -88
  98. package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +0 -177
  99. package/templates/qoder/scripts/validation/metrics/lint_cases.py +0 -170
  100. package/templates/qoder/scripts/validation/test/test_context_cache.py +0 -78
  101. package/templates/qoder/scripts/validation/test/test_lint_cases.py +0 -60
  102. package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +0 -70
  103. package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +0 -1
@@ -1,245 +1,248 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- capability.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 = 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
- NoOpPresentCap,
21
- )
22
-
23
-
24
- # =============================================================================
25
- # HOST_TABLE —— 宿主注册表
26
- # -----------------------------------------------------------------------------
27
- # 泛化 common/active_task.py:_ENV_SESSION_KEYS 的 7 宿主表:
28
- # 从"只探测身份"扩展为"身份 + 能力清单"。
29
- #
30
- # 现在最小形态: Python dict。
31
- # 未来完整形态: manifests/*.yaml 声明式文件(每宿主一份)。
32
- # 迁移路径: dict → yaml,resolve() 接口不变,工作流零改动。
33
- # =============================================================================
34
-
35
- HOST_TABLE: dict[str, dict] = {
36
- # ── Qoder 系列(第一宿主系列,2026-07 对齐最新产品线)─────────────
37
- # 主开发宿主 + 办公产品。(Qoder CLI / Cloud Agents 后续按需再加;QoderWake 架构性不接。)
38
- # features = 引擎已接(非 NoOp adapter);native = 产品原生能力(仅建模,不算实现)。
39
- "qoder-desktop": {
40
- # Qoder 1.0 "Autonomous Development Desktop"(原 qoder-ide;Quest Window/Agent Mode 是其模式,故原 qoder-quest 合并于此)
41
- "detect": ("QODER_DESKTOP", "QODER_IDE"),
42
- "features": ("mcp", "memory", "cron", "present"),
43
- "native": ("agent-mode", "context-engine", "vm-sandbox", "hooks", "skills", "mcp-sse"),
44
- "mcp_servers": ["qoder-knowledge-graph", "qoder-zentao", "qoder-mysql", "lanhu"],
45
- "note": "Qoder Desktop/IDE 1.0(主开发宿主;Quest 是其模式,非独立宿主)",
46
- },
47
- "qoder-work": {
48
- # QoderWork:办公产品(非开发者,文档/报告/PPT/M365)。~/.qoderwork/ 投影 + qoder_show_widget 是其接入点。
49
- "detect": ("QODER_SESSION_ID",),
50
- "features": ("mcp", "memory", "cron", "present"),
51
- "native": ("office-agent", "m365", "human-in-loop", "hooks", "show-widget"),
52
- "mcp_servers": ["qoder-knowledge-graph", "qoder-zentao", "qoder-mysql", "lanhu"],
53
- "note": "QoderWork 桌面办公 Agent(非开发者;文档/PPT/M365);~/.qoderwork/ 投影入口",
54
- },
55
- # ── 其他宿主(通用 AI 工具)────────────────────────────────
56
- "codex": {
57
- "detect": ("CODEX_SESSION_ID", "CODEX_THREAD_ID"),
58
- "features": ("repo", "identity"),
59
- "mcp_servers": [],
60
- "note": "OpenAI Codex CLI",
61
- },
62
- "claude": {
63
- "detect": ("CLAUDE_SESSION_ID", "CLAUDE_CODE_SESSION_ID"),
64
- "features": ("repo", "identity"),
65
- "mcp_servers": [],
66
- "note": "Claude Code",
67
- },
68
- "cursor": {
69
- "detect": ("CURSOR_SESSION_ID",),
70
- "features": ("repo", "identity"),
71
- "mcp_servers": [],
72
- },
73
- "opencode": {
74
- "detect": ("OPENCODE_SESSION_ID", "OPENCODE_RUN_ID"),
75
- "features": ("repo", "identity"),
76
- "mcp_servers": [],
77
- },
78
- "gemini": {
79
- "detect": ("GEMINI_SESSION_ID",),
80
- "features": ("repo", "identity"),
81
- "mcp_servers": [],
82
- },
83
- "copilot": {
84
- "detect": ("COPILOT_SESSION_ID",),
85
- "features": ("repo", "identity"),
86
- "mcp_servers": [],
87
- },
88
- # ── 兜底 ──────────────────────────────────────────────────
89
- "cli": {
90
- "detect": (), # 兜底:无 env 命中即为 CLI / 任意宿主
91
- "features": ("repo", "identity", "context", "memory", "mcp"),
92
- # 本地 stdio transport 经 mcp_launcher spawn 子进程, 不依赖宿主原生 MCP。
93
- # CLI 能起全部本地 server (kg/zentao/mysql/lanhu), 与 QoderWork 等价。
94
- "mcp_servers": ["qoder-knowledge-graph", "qoder-zentao", "qoder-mysql", "lanhu"],
95
- "note": "纯 CLI 兜底; MCP 走本地 spawn (非宿主原生), 其余走文件降级",
96
- },
97
- }
98
-
99
-
100
- def detect_host() -> str:
101
- """探测当前宿主名。
102
-
103
- 遍历 HOST_TABLE,第一个 env 命中的返回;无则 'cli'。
104
- 单一事实源 —— 替代散落在 active_task / autotest / mcp_launcher 的多处宿主探测。
105
- """
106
- for name, info in HOST_TABLE.items():
107
- if name == "cli":
108
- continue
109
- for env_var in info.get("detect", ()):
110
- if os.environ.get(env_var):
111
- return name
112
- return "cli"
113
-
114
-
115
- # =============================================================================
116
- # Nexus Facade
117
- # =============================================================================
118
-
119
- class Nexus:
120
- """宿主能力 Facade —— 聚合所有能力,对外统一入口。
121
-
122
- 工作流永远只调 resolve() 拿到本对象,再访问 .memory/.mcp/...,
123
- 不直接 import 任何 adapter(Facade 模式)。
124
- """
125
-
126
- def __init__(self, host_name: str, repo, identity, context, memory,
127
- cron, notify, present, sandbox, mcp):
128
- self.host_name = host_name
129
- self.repo = repo
130
- self.identity = identity
131
- self.context = context
132
- self.memory = memory
133
- self.cron = cron
134
- self.notify = notify
135
- self.present = present
136
- self.sandbox = sandbox
137
- self.mcp = mcp
138
-
139
- def describe(self) -> dict:
140
- """诊断:当前宿主 + 各能力 available 状态。"""
141
- return {
142
- "host": self.host_name,
143
- "capabilities": {
144
- "repo": _avail(self.repo),
145
- "identity": _avail(self.identity),
146
- "context": _avail(self.context),
147
- "memory": _avail(self.memory),
148
- "cron": _avail(self.cron),
149
- "notify": _avail(self.notify),
150
- "present": _avail(self.present),
151
- "sandbox": _avail(self.sandbox),
152
- "mcp": _avail(self.mcp),
153
- },
154
- }
155
-
156
-
157
- def _avail(cap) -> bool:
158
- return bool(getattr(cap, "available", False))
159
-
160
-
161
- # =============================================================================
162
- # 能力装配(Bridge: Cap 抽象 × 宿主实现 正交)
163
- # -----------------------------------------------------------------------------
164
- # 每个 _resolve_xxx 是一个能力的装配点。新增能力(场景 B)= 加一个 _resolve_xxx
165
- # + Nexus 属性。新增宿主实现 = 在对应 _resolve_xxx 里按 host 名分支。
166
- # =============================================================================
167
-
168
- def _resolve_repo(host: str):
169
- """核心能力:所有宿主都用文件降级版(无 ~/.qoderwork 依赖)。"""
170
- from .adapters.cli import FileRepoCap
171
- return FileRepoCap()
172
-
173
-
174
- def _resolve_identity(host: str):
175
- from .adapters.cli import FileIdentityCap
176
- return FileIdentityCap()
177
-
178
-
179
- def _resolve_context(host: str):
180
- from .adapters.cli import FileContextCap
181
- return FileContextCap()
182
-
183
-
184
- def _resolve_memory(host: str):
185
- """memory —— 职责链样板(见 memory_chain.py)。"""
186
- from .memory_chain import build_memory_chain
187
- return build_memory_chain(host)
188
-
189
-
190
- def _resolve_cron(host: str):
191
- from .adapters.qw import QwCronCap
192
- return QwCronCap() # 文件系统 cron,不依赖宿主
193
-
194
-
195
- def _resolve_notify(host: str):
196
- """L2 通知 —— 配了 WLKJ_NOTIFY_WEBHOOKS→WebhookNotifyCap(飞书/企微/钉钉);否则 NoOp 降级。"""
197
- from .caps.notify import default_notify
198
- return default_notify()
199
-
200
-
201
- def _resolve_present(host: str):
202
- """L2 富展示 —— Qoder Desktop/Work 调 qoder_show_widget 嵌入对话; 其余 NoOp 降级表格。"""
203
- if host in ("qoder-desktop", "qoder-work"):
204
- try:
205
- from .adapters.qw import QwPresentCap
206
- return QwPresentCap()
207
- except Exception:
208
- pass
209
- return NoOpPresentCap()
210
-
211
-
212
- def _resolve_sandbox(host: str):
213
- """L2 安全执行 —— env WLKJ_SANDBOX≠off→SubprocessSandboxCap(进程级隔离+超时);off→NoOp。"""
214
- from .caps.sandbox import default_sandbox
215
- return default_sandbox()
216
-
217
-
218
- def _resolve_mcp(host: str):
219
- """L1 MCP 统一入口 —— 根据宿主声明的 mcp_servers 决定 spawn 哪些。"""
220
- from .adapters.mcp import McpCap
221
- info = HOST_TABLE.get(host, {})
222
- mcp_servers = info.get("mcp_servers")
223
- return McpCap(mcp_servers=mcp_servers)
224
-
225
-
226
- def resolve() -> Nexus:
227
- """解析当前环境,返回 Nexus Facade。
228
-
229
- 一行入口: cap = resolve()
230
- 内部: detect_host → 按宿主装配各 Cap → 注入 NoOp 兜底。
231
- 工作流代码零宿主感知。
232
- """
233
- host = detect_host()
234
- return Nexus(
235
- host_name=host,
236
- repo=_resolve_repo(host),
237
- identity=_resolve_identity(host),
238
- context=_resolve_context(host),
239
- memory=_resolve_memory(host),
240
- cron=_resolve_cron(host),
241
- notify=_resolve_notify(host),
242
- present=_resolve_present(host),
243
- sandbox=_resolve_sandbox(host),
244
- mcp=_resolve_mcp(host),
245
- )
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ capability.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 = 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 系列(第一宿主系列,2026-07 对齐最新产品线)─────────────
40
+ # 主开发宿主 + 办公产品。(Qoder CLI / Cloud Agents 后续按需再加;QoderWake 架构性不接。)
41
+ # features = 引擎已接(非 NoOp adapter);native = 产品原生能力(仅建模,不算实现)。
42
+ "qoder-desktop": {
43
+ # Qoder 1.0 "Autonomous Development Desktop"(原 qoder-ide;Quest Window/Agent Mode 是其模式,故原 qoder-quest 合并于此)
44
+ "detect": ("QODER_DESKTOP", "QODER_IDE"),
45
+ "features": ("mcp", "memory", "cron", "present"),
46
+ "native": ("agent-mode", "context-engine", "vm-sandbox", "hooks", "skills", "mcp-sse"),
47
+ "mcp_servers": ["qoder-knowledge-graph", "qoder-zentao", "qoder-mysql", "lanhu"],
48
+ "note": "Qoder Desktop/IDE 1.0(主开发宿主;Quest 是其模式,非独立宿主)",
49
+ },
50
+ "qoder-work": {
51
+ # QoderWork:办公产品(非开发者,文档/报告/PPT/M365)。~/.qoderwork/ 投影 + qoder_show_widget 是其接入点。
52
+ "detect": ("QODER_SESSION_ID",),
53
+ "features": ("mcp", "memory", "cron", "present"),
54
+ "native": ("office-agent", "m365", "human-in-loop", "hooks", "show-widget"),
55
+ "mcp_servers": ["qoder-knowledge-graph", "qoder-zentao", "qoder-mysql", "lanhu"],
56
+ "note": "QoderWork 桌面办公 Agent(非开发者;文档/PPT/M365);~/.qoderwork/ 投影入口",
57
+ },
58
+ # ── 其他宿主(通用 AI 工具)────────────────────────────────
59
+ "codex": {
60
+ "detect": ("CODEX_SESSION_ID", "CODEX_THREAD_ID"),
61
+ "features": ("repo", "identity"),
62
+ "mcp_servers": [],
63
+ "note": "OpenAI Codex CLI",
64
+ },
65
+ "claude": {
66
+ "detect": ("CLAUDE_SESSION_ID", "CLAUDE_CODE_SESSION_ID"),
67
+ "features": ("repo", "identity"),
68
+ "mcp_servers": [],
69
+ "note": "Claude Code",
70
+ },
71
+ "cursor": {
72
+ "detect": ("CURSOR_SESSION_ID",),
73
+ "features": ("repo", "identity"),
74
+ "mcp_servers": [],
75
+ },
76
+ "opencode": {
77
+ "detect": ("OPENCODE_SESSION_ID", "OPENCODE_RUN_ID"),
78
+ "features": ("repo", "identity"),
79
+ "mcp_servers": [],
80
+ },
81
+ "gemini": {
82
+ "detect": ("GEMINI_SESSION_ID",),
83
+ "features": ("repo", "identity"),
84
+ "mcp_servers": [],
85
+ },
86
+ "copilot": {
87
+ "detect": ("COPILOT_SESSION_ID",),
88
+ "features": ("repo", "identity"),
89
+ "mcp_servers": [],
90
+ },
91
+ # ── 兜底 ──────────────────────────────────────────────────
92
+ "cli": {
93
+ "detect": (), # 兜底:无 env 命中即为 CLI / 任意宿主
94
+ "features": ("repo", "identity", "context", "memory", "mcp"),
95
+ # 本地 stdio transport mcp_launcher spawn 子进程, 不依赖宿主原生 MCP。
96
+ # 故 CLI 能起全部本地 server (kg/zentao/mysql/lanhu), 与 QoderWork 等价。
97
+ "mcp_servers": ["qoder-knowledge-graph", "qoder-zentao", "qoder-mysql", "lanhu"],
98
+ "note": "纯 CLI 兜底; MCP 走本地 spawn (非宿主原生), 其余走文件降级",
99
+ },
100
+ }
101
+
102
+
103
+ def detect_host() -> str:
104
+ """探测当前宿主名。
105
+
106
+ 遍历 HOST_TABLE,第一个 env 命中的返回;无则 'cli'。
107
+ 单一事实源 —— 替代散落在 active_task / autotest / mcp_launcher 的多处宿主探测。
108
+ """
109
+ for name, info in HOST_TABLE.items():
110
+ if name == "cli":
111
+ continue
112
+ for env_var in info.get("detect", ()):
113
+ if os.environ.get(env_var):
114
+ return name
115
+ return "cli"
116
+
117
+
118
+ # =============================================================================
119
+ # Nexus Facade
120
+ # =============================================================================
121
+
122
+ class Nexus:
123
+ """宿主能力 Facade —— 聚合所有能力,对外统一入口。
124
+
125
+ 工作流永远只调 resolve() 拿到本对象,再访问 .memory/.mcp/...,
126
+ 不直接 import 任何 adapter(Facade 模式)。
127
+ """
128
+
129
+ def __init__(self, host_name: str, repo, identity, context, memory,
130
+ cron, notify, present, sandbox, mcp):
131
+ self.host_name = host_name
132
+ self.repo = repo
133
+ self.identity = identity
134
+ self.context = context
135
+ self.memory = memory
136
+ self.cron = cron
137
+ self.notify = notify
138
+ self.present = present
139
+ self.sandbox = sandbox
140
+ self.mcp = mcp
141
+
142
+ def describe(self) -> dict:
143
+ """诊断:当前宿主 + 各能力 available 状态。"""
144
+ return {
145
+ "host": self.host_name,
146
+ "capabilities": {
147
+ "repo": _avail(self.repo),
148
+ "identity": _avail(self.identity),
149
+ "context": _avail(self.context),
150
+ "memory": _avail(self.memory),
151
+ "cron": _avail(self.cron),
152
+ "notify": _avail(self.notify),
153
+ "present": _avail(self.present),
154
+ "sandbox": _avail(self.sandbox),
155
+ "mcp": _avail(self.mcp),
156
+ },
157
+ }
158
+
159
+
160
+ def _avail(cap) -> bool:
161
+ return bool(getattr(cap, "available", False))
162
+
163
+
164
+ # =============================================================================
165
+ # 能力装配(Bridge: Cap 抽象 × 宿主实现 正交)
166
+ # -----------------------------------------------------------------------------
167
+ # 每个 _resolve_xxx 是一个能力的装配点。新增能力(场景 B)= 加一个 _resolve_xxx
168
+ # + Nexus 属性。新增宿主实现 = 在对应 _resolve_xxx 里按 host 名分支。
169
+ # =============================================================================
170
+
171
+ def _resolve_repo(host: str):
172
+ """核心能力:所有宿主都用文件降级版(无 ~/.qoderwork 依赖)。"""
173
+ from .adapters.cli import FileRepoCap
174
+ return FileRepoCap()
175
+
176
+
177
+ def _resolve_identity(host: str):
178
+ from .adapters.cli import FileIdentityCap
179
+ return FileIdentityCap()
180
+
181
+
182
+ def _resolve_context(host: str):
183
+ from .adapters.cli import FileContextCap
184
+ return FileContextCap()
185
+
186
+
187
+ def _resolve_memory(host: str):
188
+ """memory —— 职责链样板(见 memory_chain.py)。"""
189
+ from .memory_chain import build_memory_chain
190
+ return build_memory_chain(host)
191
+
192
+
193
+ def _resolve_cron(host: str):
194
+ from .adapters.qw import QwCronCap
195
+ return QwCronCap() # 文件系统 cron,不依赖宿主
196
+
197
+
198
+ def _resolve_notify(host: str):
199
+ """L2 通知 —— 配了 WLKJ_NOTIFY_WEBHOOKS→WebhookNotifyCap(飞书/企微/钉钉);否则 NoOp 降级。"""
200
+ from .caps.notify import default_notify
201
+ return default_notify()
202
+
203
+
204
+ def _resolve_present(host: str):
205
+ """L2 富展示 —— Qoder Desktop/Work 调 qoder_show_widget 嵌入对话; 其余 NoOp 降级表格。"""
206
+ if host in ("qoder-desktop", "qoder-work"):
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
+ """L2 安全执行 —— env WLKJ_SANDBOX≠off→SubprocessSandboxCap(进程级隔离+超时);off→NoOp。"""
217
+ from .caps.sandbox import default_sandbox
218
+ return default_sandbox()
219
+
220
+
221
+ def _resolve_mcp(host: str):
222
+ """L1 MCP 统一入口 —— 根据宿主声明的 mcp_servers 决定 spawn 哪些。"""
223
+ from .adapters.mcp import McpCap
224
+ info = HOST_TABLE.get(host, {})
225
+ mcp_servers = info.get("mcp_servers")
226
+ return McpCap(mcp_servers=mcp_servers)
227
+
228
+
229
+ def resolve() -> Nexus:
230
+ """解析当前环境,返回 Nexus Facade。
231
+
232
+ 一行入口: cap = resolve()
233
+ 内部: detect_host → 按宿主装配各 Cap → 注入 NoOp 兜底。
234
+ 工作流代码零宿主感知。
235
+ """
236
+ host = detect_host()
237
+ return Nexus(
238
+ host_name=host,
239
+ repo=_resolve_repo(host),
240
+ identity=_resolve_identity(host),
241
+ context=_resolve_context(host),
242
+ memory=_resolve_memory(host),
243
+ cron=_resolve_cron(host),
244
+ notify=_resolve_notify(host),
245
+ present=_resolve_present(host),
246
+ sandbox=_resolve_sandbox(host),
247
+ mcp=_resolve_mcp(host),
248
+ )
@@ -51,7 +51,7 @@ _KNOWLEDGE_SERVER_ALIASES = ["wlinkj-knowledge", "qoder-knowledge-graph"]
51
51
  SERVERS: dict[str, dict] = {
52
52
  "qoder-knowledge-graph": {
53
53
  "kind": "http",
54
- "url": os.environ.get("WLKJ_BASE_URL", "http://10.89.7.120") + "/mcp",
54
+ "url": "http://127.0.0.1:10010/mcp",
55
55
  "note": "知识图谱。已改为 http 模式,指向资产平台 MCP 服务(POST /mcp JSON-RPC)",
56
56
  },
57
57
  "qoder-zentao": {
@@ -73,22 +73,16 @@ SERVERS: dict[str, dict] = {
73
73
 
74
74
 
75
75
  def _mcp_config_path() -> Path:
76
- """定位 mcp_config.json(在仓库根/宿主下)。
77
-
78
- ★ switch_project.py 实际写入位置是 {repo}/.qoder/mcp_config.json(云模式连云 bind 后),
79
- 必须优先查这里——否则读不到 token,transport 用空 token 调云 MCP 鉴权失败(历史 bug:
80
- 引擎 search 全返回"MCP返回非JSON"+递归降级套娃)。老形态 repo/mcp_config.json 兼容。
81
- """
76
+ """定位 mcp_config.json(在仓库根/宿主下)。"""
82
77
  repo = _get_repo_root()
83
78
  # registry_mcp.py 在 .../scripts/capability/, mcp_config.json 在宿主根(.qoder)。
84
79
  for cand in [
85
- repo / ".qoder" / "mcp_config.json", # ★ 云模式实际写入位置(switch_project)
86
- repo / "mcp_config.json", # 老形态:仓库根
80
+ repo / "mcp_config.json",
87
81
  repo / "registry_mcp_config.json",
88
82
  ]:
89
83
  if cand.is_file():
90
84
  return cand
91
- return repo / ".qoder" / "mcp_config.json"
85
+ return repo / "mcp_config.json"
92
86
 
93
87
 
94
88
  def _load_servers() -> dict[str, dict]:
@@ -0,0 +1,94 @@
1
+ {
2
+ "timestamp": "2026-06-26 21:25:55",
3
+ "servers": {
4
+ "qoder-knowledge-graph": {
5
+ "available": true,
6
+ "tools_indexed": 17,
7
+ "latency_ms": 437.0,
8
+ "sample_tools": [
9
+ "search_code",
10
+ "search_api",
11
+ "search_prd",
12
+ "get_impact",
13
+ "context_360"
14
+ ],
15
+ "spawn_pid": 38616,
16
+ "status": "PASS"
17
+ },
18
+ "qoder-zentao": {
19
+ "available": true,
20
+ "tools_indexed": 66,
21
+ "latency_ms": 3657.0,
22
+ "sample_tools": [
23
+ "check_zentao_status",
24
+ "get_zentao_url",
25
+ "list_products",
26
+ "get_product",
27
+ "create_product"
28
+ ],
29
+ "spawn_pid": 11224,
30
+ "status": "PASS"
31
+ },
32
+ "qoder-mysql": {
33
+ "available": true,
34
+ "tools_indexed": 6,
35
+ "latency_ms": 3484.0,
36
+ "sample_tools": [
37
+ "list_envs",
38
+ "query_schema",
39
+ "query_data",
40
+ "query_distinct",
41
+ "list_databases"
42
+ ],
43
+ "spawn_pid": 37868,
44
+ "status": "PASS"
45
+ },
46
+ "lanhu": {
47
+ "available": true,
48
+ "tools_indexed": 13,
49
+ "latency_ms": 3156.0,
50
+ "sample_tools": [
51
+ "lanhu_resolve_invite_link",
52
+ "lanhu_list_product_documents",
53
+ "lanhu_get_pages",
54
+ "lanhu_get_ai_analyze_page_result",
55
+ "lanhu_get_designs"
56
+ ],
57
+ "spawn_pid": 36836,
58
+ "status": "PASS"
59
+ }
60
+ },
61
+ "call_tool": {
62
+ "status": "PASS",
63
+ "latency_ms": 3953.0,
64
+ "text_preview": "Chinese: 保险 -> English: ins insurance vehlife/insurance\n\n[Carmg-H5] 12 files\n Carmg-H5/src/.../main/List.vue -> data/code/Carmg-H5/src/modules/icsAbnormalCenter/module/main/List.vue\n Carmg-H5/src/",
65
+ "is_error": false
66
+ },
67
+ "memory": {
68
+ "available": true,
69
+ "providers": [
70
+ {
71
+ "provider": "McpMemoryProvider",
72
+ "available": true,
73
+ "hit": true
74
+ },
75
+ {
76
+ "provider": "FileMemoryCap",
77
+ "available": true,
78
+ "hit": false
79
+ },
80
+ {
81
+ "provider": "NoOpMemoryCap",
82
+ "available": false,
83
+ "hit": false
84
+ }
85
+ ],
86
+ "status": "PASS"
87
+ },
88
+ "summary": {
89
+ "all_pass": true,
90
+ "active_servers": 4,
91
+ "total_servers": 4,
92
+ "total_tools": 102
93
+ }
94
+ }