@hupan56/wlkj 3.3.2 → 3.3.4

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 (42) hide show
  1. package/bin/cli.js +16 -1
  2. package/package.json +1 -1
  3. package/templates/qoder/scripts/capability/adapters/mcp.py +8 -1
  4. package/templates/qoder/scripts/capability/registry_mcp.py +10 -4
  5. package/templates/qoder/scripts/deployment/setup/init_doctor.py +6 -2
  6. package/templates/qoder/scripts/deployment/setup/setup.py +5 -2
  7. package/templates/qoder/scripts/domain/integration/return_to_platform.py +3 -2
  8. package/templates/qoder/scripts/domain/integration/spec_upload.py +1 -1
  9. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +41 -5
  10. package/templates/qoder/scripts/domain/kg/switch_project.py +2 -2
  11. package/templates/qoder/scripts/engine/poller.py +1 -1
  12. package/templates/qoder/scripts/capability/__pycache__/__init__.cpython-39.pyc +0 -0
  13. package/templates/qoder/scripts/capability/__pycache__/present_html.cpython-39.pyc +0 -0
  14. package/templates/qoder/scripts/capability/__pycache__/registry.cpython-39.pyc +0 -0
  15. package/templates/qoder/scripts/capability/__pycache__/registry_mcp.cpython-39.pyc +0 -0
  16. package/templates/qoder/scripts/capability/adapters/__pycache__/__init__.cpython-39.pyc +0 -0
  17. package/templates/qoder/scripts/capability/adapters/__pycache__/cli.cpython-39.pyc +0 -0
  18. package/templates/qoder/scripts/capability/adapters/__pycache__/mcp.cpython-39.pyc +0 -0
  19. package/templates/qoder/scripts/capability/adapters/__pycache__/qw.cpython-39.pyc +0 -0
  20. package/templates/qoder/scripts/capability/caps/__pycache__/__init__.cpython-39.pyc +0 -0
  21. package/templates/qoder/scripts/capability/caps/__pycache__/context.cpython-39.pyc +0 -0
  22. package/templates/qoder/scripts/capability/caps/__pycache__/cron.cpython-39.pyc +0 -0
  23. package/templates/qoder/scripts/capability/caps/__pycache__/identity.cpython-39.pyc +0 -0
  24. package/templates/qoder/scripts/capability/caps/__pycache__/memory.cpython-39.pyc +0 -0
  25. package/templates/qoder/scripts/capability/caps/__pycache__/notify.cpython-39.pyc +0 -0
  26. package/templates/qoder/scripts/capability/caps/__pycache__/present.cpython-39.pyc +0 -0
  27. package/templates/qoder/scripts/capability/caps/__pycache__/repo.cpython-39.pyc +0 -0
  28. package/templates/qoder/scripts/capability/caps/__pycache__/sandbox.cpython-39.pyc +0 -0
  29. package/templates/qoder/scripts/domain/__pycache__/__init__.cpython-39.pyc +0 -0
  30. package/templates/qoder/scripts/domain/integration/__pycache__/__init__.cpython-39.pyc +0 -0
  31. package/templates/qoder/scripts/domain/integration/__pycache__/return_to_platform.cpython-39.pyc +0 -0
  32. package/templates/qoder/scripts/domain/task/__pycache__/wlkj_panel.cpython-39.pyc +0 -0
  33. package/templates/qoder/scripts/foundation/__pycache__/__init__.cpython-39.pyc +0 -0
  34. package/templates/qoder/scripts/foundation/core/__pycache__/__init__.cpython-39.pyc +0 -0
  35. package/templates/qoder/scripts/foundation/core/__pycache__/paths.cpython-39.pyc +0 -0
  36. package/templates/qoder/scripts/protocol/__pycache__/__init__.cpython-39.pyc +0 -0
  37. package/templates/qoder/scripts/protocol/transports/__pycache__/__init__.cpython-39.pyc +0 -0
  38. package/templates/qoder/scripts/protocol/transports/__pycache__/base.cpython-39.pyc +0 -0
  39. package/templates/qoder/scripts/protocol/transports/__pycache__/cli.cpython-39.pyc +0 -0
  40. package/templates/qoder/scripts/protocol/transports/__pycache__/http.cpython-39.pyc +0 -0
  41. package/templates/qoder/scripts/protocol/transports/__pycache__/stdio.cpython-39.pyc +0 -0
  42. package/templates/qoder/scripts/validation/metrics/__pycache__/present_board.cpython-39.pyc +0 -0
package/bin/cli.js CHANGED
@@ -383,7 +383,7 @@ async function doInit(name, roleArg) {
383
383
  console.log(`\n--- 自动初始化 ---`);
384
384
  const setupPath = path.join(cwd, ".qoder", "scripts", "deployment", "setup", "setup.py");
385
385
  if (fs.existsSync(setupPath)) {
386
- const setupArgs = [name, role, "--skip-cron", "--skip-qoderwork"].filter(Boolean);
386
+ const setupArgs = [name, role, "--skip-cron", "--skip-qoderwork", "--skip-code"].filter(Boolean);
387
387
  try {
388
388
  const cmd = `python "${setupPath}" ${setupArgs.map(a => `"${a}"`).join(" ")}`;
389
389
  console.log(` 运行: setup.py ${setupArgs.join(" ")}`);
@@ -443,6 +443,21 @@ async function doInit(name, roleArg) {
443
443
  }
444
444
  }
445
445
 
446
+ // === 6.6. 连云平台 bind 项目(云模式:KG 在云平台,引擎调云 MCP)===
447
+ if (pyCmd) {
448
+ const switchScript = path.join(cwd, ".qoder", "scripts", "domain", "kg", "switch_project.py");
449
+ if (fs.existsSync(switchScript)) {
450
+ console.log(`\n--- 连云平台 bind 项目 ---`);
451
+ console.log(` 问平台邮箱/密码 → 拉项目列表 → 选项目 → 写 mcp_config(连云)`);
452
+ try {
453
+ const env = { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" };
454
+ execSync(`${pyCmd} "${switchScript}"`, { cwd, stdio: "inherit", timeout: 180000, env });
455
+ } catch (e) {
456
+ console.log(` [WARN] 云 bind 未完成,可手动: python .qoder/scripts/domain/kg/switch_project.py`);
457
+ }
458
+ }
459
+ }
460
+
446
461
  // === 7. 写版本戳 ===
447
462
  writeEngineVersion(cwd);
448
463
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hupan56/wlkj",
3
- "version": "3.3.2",
3
+ "version": "3.3.4",
4
4
  "description": "AI Product R&D Workflow - PRD/Prototype/Search/Task/Report",
5
5
  "bin": {
6
6
  "wlkj": "bin/cli.js"
@@ -132,6 +132,11 @@ class McpCap:
132
132
 
133
133
  def _cli_fallback(self, tool_name: str, args: dict) -> Optional[str]:
134
134
  """MCP 工具名 → wlkj.py 执行。无映射返回 None。"""
135
+ # ★ 递归守卫:search_code→kg search→search_index→call_remote→call→_cli_fallback 会无限套娃。
136
+ # 已在降级子进程内(_run_wlkj 设了 _WLKJ_IN_CLI_FALLBACK=1)则直接放弃降级,返回原始错误。
137
+ import os as _os
138
+ if _os.environ.get("_WLKJ_IN_CLI_FALLBACK"):
139
+ return None
135
140
  entry = self._TOOL_TO_WLKJ.get(tool_name)
136
141
  if entry is None:
137
142
  return None
@@ -183,10 +188,12 @@ class McpCap:
183
188
  repo = os.getcwd()
184
189
  wlkj = os.path.join(repo, ".qoder", "scripts", "orchestration", "wlkj.py")
185
190
  try:
191
+ # 递归守卫:子进程内 _WLKJ_IN_CLI_FALLBACK=1,其 McpCap 不再降级(否则套娃)
192
+ env = dict(os.environ, _WLKJ_IN_CLI_FALLBACK="1")
186
193
  r = subprocess.run(
187
194
  [sys.executable, wlkj, cmd] + args,
188
195
  capture_output=True, text=True, timeout=60,
189
- encoding="utf-8", errors="replace",
196
+ encoding="utf-8", errors="replace", env=env,
190
197
  )
191
198
  if r.returncode == 0:
192
199
  return r.stdout.strip()
@@ -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": "http://127.0.0.1:10010/mcp",
54
+ "url": os.environ.get("WLKJ_BASE_URL", "http://10.89.7.120") + "/mcp",
55
55
  "note": "知识图谱。已改为 http 模式,指向资产平台 MCP 服务(POST /mcp JSON-RPC)",
56
56
  },
57
57
  "qoder-zentao": {
@@ -73,16 +73,22 @@ SERVERS: dict[str, dict] = {
73
73
 
74
74
 
75
75
  def _mcp_config_path() -> Path:
76
- """定位 mcp_config.json(在仓库根/宿主下)。"""
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
+ """
77
82
  repo = _get_repo_root()
78
83
  # registry_mcp.py 在 .../scripts/capability/, mcp_config.json 在宿主根(.qoder)。
79
84
  for cand in [
80
- repo / "mcp_config.json",
85
+ repo / ".qoder" / "mcp_config.json", # ★ 云模式实际写入位置(switch_project)
86
+ repo / "mcp_config.json", # 老形态:仓库根
81
87
  repo / "registry_mcp_config.json",
82
88
  ]:
83
89
  if cand.is_file():
84
90
  return cand
85
- return repo / "mcp_config.json"
91
+ return repo / ".qoder" / "mcp_config.json"
86
92
 
87
93
 
88
94
  def _load_servers() -> dict[str, dict]:
@@ -834,6 +834,7 @@ def check_lanhu():
834
834
  def main():
835
835
  args = [a for a in sys.argv[1:]]
836
836
  fix = '--fix' in args
837
+ skip_code = '--skip-code' in args or '--cloud' in args
837
838
  pos = [a for a in args if not a.startswith('--')]
838
839
  name = pos[0] if pos else None
839
840
  role = pos[1] if len(pos) > 1 else None
@@ -848,8 +849,11 @@ def main():
848
849
  check_dependencies(fix)
849
850
  check_developer(fix, name, role)
850
851
  check_team_sync(fix)
851
- check_source_repos(fix, config)
852
- check_index(fix)
852
+ if skip_code:
853
+ print('\n--- 4. 源码/索引: 云模式跳过(KG 在云平台,引擎调云 MCP)---')
854
+ else:
855
+ check_source_repos(fix, config)
856
+ check_index(fix)
853
857
  check_style(fix)
854
858
  check_prd_templates()
855
859
  check_weekly(fix)
@@ -242,9 +242,11 @@ def detect_name(explicit=None):
242
242
  # Step 2: 调 init_doctor --fix (主体)
243
243
  # ============================================================
244
244
 
245
- def run_doctor(name, role):
245
+ def run_doctor(name, role, skip_code=False):
246
246
  print('\n--- 初始化 (init_doctor) ---')
247
247
  cmd = [sys.executable, str(THIS_DIR / 'init_doctor.py'), '--fix', name, role]
248
+ if skip_code:
249
+ cmd.append('--skip-code')
248
250
  # 透传 stdio (用户能看到实时输出) + 强制 UTF-8 环境 (解决 cmd GBK 乱码)
249
251
  env = dict(os.environ)
250
252
  env['PYTHONIOENCODING'] = 'utf-8'
@@ -911,6 +913,7 @@ def main():
911
913
  parser.add_argument('--skip-cron', action='store_true', help='跳过 cron 注册')
912
914
  parser.add_argument('--skip-qoderwork', action='store_true', help='跳过 QoderWork 安装')
913
915
  parser.add_argument('--skip-lanhu', action='store_true', help='跳过蓝湖 MCP 引导')
916
+ parser.add_argument('--skip-code', '--cloud', action='store_true', help='云模式:跳过本地源码 clone/索引(KG 在云平台)')
914
917
  args = parser.parse_args()
915
918
 
916
919
  print('=' * 56)
@@ -931,7 +934,7 @@ def main():
931
934
  configure_git_identity(name)
932
935
 
933
936
  # Step 2
934
- if not run_doctor(name, args.role):
937
+ if not run_doctor(name, args.role, args.skip_code):
935
938
  warn('init_doctor 未完全成功, 继续 setup 后续步骤...')
936
939
 
937
940
  # Step 3
@@ -20,6 +20,7 @@ integration.return_to_platform —— 引擎产出回流平台的统一通道。
20
20
  """
21
21
  from __future__ import annotations
22
22
 
23
+ import os
23
24
  from typing import Optional
24
25
 
25
26
 
@@ -121,7 +122,7 @@ def _via_http(payload: dict) -> tuple[bool, str]:
121
122
 
122
123
 
123
124
  def _platform_base() -> str:
124
- """平台 base urlhttp://127.0.0.1:10010),从 mcp_config server url 推导。"""
125
+ """平台 base url(默认 http://10.89.7.120 云),从 mcp_config server url 推导。"""
125
126
  from urllib.parse import urlparse
126
127
  cfg = _load_mcp_config()
127
128
  for srv in (cfg.get("mcpServers") or {}).values():
@@ -133,7 +134,7 @@ def _platform_base() -> str:
133
134
  if ep:
134
135
  p = urlparse(ep)
135
136
  return "%s://%s" % (p.scheme, p.netloc)
136
- return "http://127.0.0.1:10010"
137
+ return os.environ.get("WLKJ_BASE_URL", "http://10.89.7.120")
137
138
 
138
139
 
139
140
  def _engine_token() -> Optional[str]:
@@ -48,7 +48,7 @@ def _resolve_endpoint() -> Tuple[str, str, str]:
48
48
  u = srv.get("url", "")
49
49
  if "/mcp" in u:
50
50
  base = u.split("/mcp")[0]; break
51
- base = (base or "http://127.0.0.1:10010").rstrip("/")
51
+ base = (base or os.environ.get("WLKJ_BASE_URL", "http://10.89.7.120")).rstrip("/")
52
52
  token = os.environ.get("WLKJ_MCP_TOKEN", "")
53
53
  if not token:
54
54
  for srv in (cfg.get("mcpServers") or {}).values():
@@ -278,6 +278,31 @@ def build_embeddings(batch_size=64):
278
278
  con.close()
279
279
 
280
280
 
281
+ def _cloud_rag_search(query, top_k=10):
282
+ """云优先语义搜索:调工作台 MCP rag_search(本地无 KG/embedding,全迁云)。
283
+
284
+ Returns:
285
+ [(entity_id, text, score), ...] 或 None(云不可用时返回 None,让调用方退本地)。
286
+ """
287
+ try:
288
+ from domain.kg.search._remote import call_remote
289
+ data = call_remote("rag_search", {"query": query, "top_k": top_k})
290
+ except Exception:
291
+ return None
292
+ items = data.get("items") or []
293
+ out = []
294
+ for it in items[:top_k]:
295
+ eid = it.get("entity_id") or it.get("id") or ""
296
+ text = it.get("text") or it.get("name") or ""
297
+ score = it.get("score")
298
+ try:
299
+ score = float(score) if score is not None else 0.0
300
+ except Exception:
301
+ score = 0.0
302
+ out.append((eid, text, score))
303
+ return out
304
+
305
+
281
306
  def semantic_search(query, top_k=10):
282
307
  """语义搜索: query→向量→余弦相似度→top_k 实体。
283
308
 
@@ -656,13 +681,24 @@ if __name__ == '__main__':
656
681
  n = build_embeddings()
657
682
  print('Built %d embeddings' % n)
658
683
 
659
- elif len(sys.argv) > 2 and sys.argv[1] == 'search':
660
- results = semantic_search(sys.argv[2])
684
+ elif (len(sys.argv) > 2 and sys.argv[1] == 'search') or \
685
+ (len(sys.argv) > 1 and sys.argv[1] not in ('--check', '--label', 'build-embeddings', '--status', '--backfill-cn')):
686
+ # 兼容两种姿势: `search <词>` 和裸词 `<词>`(wlkj.py semantic 保险异常 透传过来)。
687
+ # ★ 云优先:本地无 KG/embedding(全迁云),先调云 rag_search;云不可用才退本地 semantic_search。
688
+ query = sys.argv[2] if sys.argv[1] == 'search' else sys.argv[1]
689
+ results = _cloud_rag_search(query)
690
+ if results is None:
691
+ # 云不可用 → 退本地(开发期有本地 embedding 时仍可用)
692
+ results = semantic_search(query)
661
693
  if results:
662
- for eid, text, score in results:
663
- print(' %.3f %s' % (score, text[:70]))
694
+ for row in results:
695
+ if len(row) == 3:
696
+ eid, text, score = row
697
+ print(' %.3f %s' % (score, text[:70]))
698
+ else:
699
+ print(' %s' % row)
664
700
  else:
665
- print('(无结果或 embedding 未构建)')
701
+ print('(无结果或云 rag_search 不可用 + 本地 embedding 未构建)')
666
702
 
667
703
  elif len(sys.argv) > 1 and sys.argv[1] == '--status':
668
704
  st = embedding_status()
@@ -6,7 +6,7 @@
6
6
  python switch_project.py --email a@b.c --pw xxx # 非交互:直接指定
7
7
  python switch_project.py --project-uuid <UUID> # 非交互:直接指定项目
8
8
 
9
- 需工作台后端在跑(默认 http://127.0.0.1:10010)。
9
+ 需工作台后端在跑(默认 http://10.89.7.120 云)。
10
10
  """
11
11
  import json
12
12
  import os
@@ -33,7 +33,7 @@ def _find_config_path():
33
33
  CONFIG_PATH = _find_config_path()
34
34
 
35
35
  # 平台地址(默认 10010,可 env 覆盖)
36
- BASE_URL = os.environ.get("WLKJ_BASE_URL", "http://127.0.0.1:10010")
36
+ BASE_URL = os.environ.get("WLKJ_BASE_URL", "http://10.89.7.120")
37
37
 
38
38
 
39
39
  def bind(email, password, base_url=BASE_URL):
@@ -51,7 +51,7 @@ def _get_base(cfg: dict) -> str:
51
51
  u = srv.get("url", "")
52
52
  if "/mcp" in u:
53
53
  return u.split("/mcp")[0]
54
- return "http://127.0.0.1:10010"
54
+ return os.environ.get("WLKJ_BASE_URL", "http://10.89.7.120")
55
55
 
56
56
 
57
57
  def _api(base, method, path, token, body=None):