@hupan56/wlkj 2.5.0 → 2.7.0

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 (127) hide show
  1. package/bin/cli.js +289 -12
  2. package/package.json +1 -1
  3. package/templates/qoder/agents/insight-planning.md +67 -0
  4. package/templates/qoder/agents/insight-research.md +61 -0
  5. package/templates/qoder/agents/prd-quick.md +1 -0
  6. package/templates/qoder/agents/prd-reference.md +10 -2
  7. package/templates/qoder/commands/optional/wl-insight.md +275 -0
  8. package/templates/qoder/commands/{wl-report.md → optional/wl-report.md} +13 -5
  9. package/templates/qoder/commands/{wl-spec.md → optional/wl-spec.md} +1 -1
  10. package/templates/qoder/commands/{wl-status.md → optional/wl-status.md} +28 -2
  11. package/templates/qoder/commands/wl-code.md +10 -2
  12. package/templates/qoder/commands/wl-commit.md +1 -1
  13. package/templates/qoder/commands/wl-design-draw.md +78 -0
  14. package/templates/qoder/commands/wl-design-scan.md +108 -0
  15. package/templates/qoder/commands/wl-design-spec.md +154 -0
  16. package/templates/qoder/commands/wl-design.md +32 -0
  17. package/templates/qoder/commands/wl-init.md +24 -3
  18. package/templates/qoder/commands/wl-prd-full.md +226 -0
  19. package/templates/qoder/commands/wl-prd-quick.md +134 -0
  20. package/templates/qoder/commands/wl-prd-review.md +104 -0
  21. package/templates/qoder/commands/wl-prd.md +17 -288
  22. package/templates/qoder/commands/wl-search.md +66 -30
  23. package/templates/qoder/commands/wl-task.md +290 -59
  24. package/templates/qoder/commands/wl-test.md +92 -24
  25. package/templates/qoder/config.yaml +59 -15
  26. package/templates/qoder/hooks/inject-workflow-state.py +35 -9
  27. package/templates/qoder/hooks/session-start.py +144 -62
  28. package/templates/qoder/rules/wl-pipeline.md +216 -105
  29. package/templates/qoder/scripts/__pycache__/search_index.cpython-39.pyc +0 -0
  30. package/templates/qoder/scripts/archive_prd.py +377 -0
  31. package/templates/qoder/scripts/autotest.py +1715 -0
  32. package/templates/qoder/scripts/autotest_batch.py +224 -0
  33. package/templates/qoder/scripts/autotest_run.py +297 -0
  34. package/templates/qoder/scripts/benchmark.py +210 -209
  35. package/templates/qoder/scripts/build_style_index.py +444 -4
  36. package/templates/qoder/scripts/check_carriers.py +238 -0
  37. package/templates/qoder/scripts/check_mcp.py +298 -0
  38. package/templates/qoder/scripts/check_qoderwork_consistency.py +166 -0
  39. package/templates/qoder/scripts/common/developer.py +26 -19
  40. package/templates/qoder/scripts/common/events.py +46 -0
  41. package/templates/qoder/scripts/common/extract.py +419 -0
  42. package/templates/qoder/scripts/common/graph_traverse.py +533 -0
  43. package/templates/qoder/scripts/common/identity.py +6 -1
  44. package/templates/qoder/scripts/common/paths.py +89 -0
  45. package/templates/qoder/scripts/common/pip_install.py +144 -0
  46. package/templates/qoder/scripts/common/platform_guard.py +61 -0
  47. package/templates/qoder/scripts/common/search_engine.py +205 -205
  48. package/templates/qoder/scripts/common/terms.py +57 -0
  49. package/templates/qoder/scripts/common/ts_extract.py +536 -0
  50. package/templates/qoder/scripts/context_pack.py +73 -13
  51. package/templates/qoder/scripts/enrich_prompt.py +226 -0
  52. package/templates/qoder/scripts/eval_prd.py +318 -225
  53. package/templates/qoder/scripts/export.py +487 -487
  54. package/templates/qoder/scripts/extract_api_params.py +246 -0
  55. package/templates/qoder/scripts/extract_routes.py +54 -0
  56. package/templates/qoder/scripts/extract_routes_tree.py +78 -0
  57. package/templates/qoder/scripts/fill_prototype.py +707 -0
  58. package/templates/qoder/scripts/gen_design_doc.py +394 -0
  59. package/templates/qoder/scripts/git_sync.py +27 -15
  60. package/templates/qoder/scripts/init_doctor.py +306 -41
  61. package/templates/qoder/scripts/install_qoderwork.py +366 -9
  62. package/templates/qoder/scripts/kg.py +708 -0
  63. package/templates/qoder/scripts/kg_auto_login.py +196 -0
  64. package/templates/qoder/scripts/kg_build.py +612 -0
  65. package/templates/qoder/scripts/kg_build_db.py +327 -0
  66. package/templates/qoder/scripts/kg_duckdb.py +549 -0
  67. package/templates/qoder/scripts/kg_incremental.py +393 -0
  68. package/templates/qoder/scripts/kg_link_db.py +224 -0
  69. package/templates/qoder/scripts/kg_mcp_server.py +801 -0
  70. package/templates/qoder/scripts/kg_semantic.py +150 -0
  71. package/templates/qoder/scripts/kg_test_runner.py +241 -0
  72. package/templates/qoder/scripts/lanhu_stdio_wrapper.py +119 -0
  73. package/templates/qoder/scripts/learn.py +118 -39
  74. package/templates/qoder/scripts/learn_aggregate.py +201 -0
  75. package/templates/qoder/scripts/mcp_launcher.py +359 -0
  76. package/templates/qoder/scripts/mysql_mcp_server.py +396 -0
  77. package/templates/qoder/scripts/repo_root.py +106 -0
  78. package/templates/qoder/scripts/role.py +12 -0
  79. package/templates/qoder/scripts/run_weekly_update.bat +5 -0
  80. package/templates/qoder/scripts/run_weekly_update.sh +5 -0
  81. package/templates/qoder/scripts/search_index.py +307 -60
  82. package/templates/qoder/scripts/secure-ls.js +5640 -0
  83. package/templates/qoder/scripts/setup.py +706 -453
  84. package/templates/qoder/scripts/setup_lanhu.py +963 -0
  85. package/templates/qoder/scripts/status.py +250 -11
  86. package/templates/qoder/scripts/sync_carriers.py +259 -0
  87. package/templates/qoder/scripts/syncgate.py +5 -4
  88. package/templates/qoder/scripts/task.py +75 -0
  89. package/templates/qoder/scripts/team_sync.py +60 -4
  90. package/templates/qoder/scripts/workspace_init.py +1 -1
  91. package/templates/qoder/skills/design-import/SKILL.md +226 -0
  92. package/templates/qoder/skills/design-import/figma-workflow.md +81 -0
  93. package/templates/qoder/skills/design-review/SKILL.md +82 -25
  94. package/templates/qoder/skills/prd-generator/SKILL.md +185 -58
  95. package/templates/qoder/skills/prd-review/SKILL.md +18 -1
  96. package/templates/qoder/skills/prompt-enrich/SKILL.md +90 -0
  97. package/templates/qoder/skills/prototype-generator/SKILL.md +256 -141
  98. package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
  99. package/templates/qoder/skills/spec-coder/SKILL.md +18 -1
  100. package/templates/qoder/skills/spec-generator/SKILL.md +18 -1
  101. package/templates/qoder/skills/test-generator/SKILL.md +15 -2
  102. package/templates/qoder/skills/wl-code/SKILL.md +55 -36
  103. package/templates/qoder/skills/wl-commit/SKILL.md +89 -76
  104. package/templates/qoder/skills/wl-design/SKILL.md +55 -0
  105. package/templates/qoder/skills/wl-init/SKILL.md +76 -67
  106. package/templates/qoder/skills/wl-insight/SKILL.md +201 -81
  107. package/templates/qoder/skills/wl-prd-full/SKILL.md +69 -0
  108. package/templates/qoder/skills/wl-prd-quick/SKILL.md +49 -0
  109. package/templates/qoder/skills/wl-prd-review/SKILL.md +34 -0
  110. package/templates/qoder/skills/wl-report/SKILL.md +131 -107
  111. package/templates/qoder/skills/wl-search/SKILL.md +141 -75
  112. package/templates/qoder/skills/wl-spec/SKILL.md +49 -39
  113. package/templates/qoder/skills/wl-status/SKILL.md +83 -61
  114. package/templates/qoder/skills/wl-task/SKILL.md +132 -58
  115. package/templates/qoder/skills/wl-test/SKILL.md +406 -40
  116. package/templates/qoder/templates/prd-full-template.md +2 -0
  117. package/templates/qoder/templates/prd-quick-template.md +1 -0
  118. package/templates/qoder/templates/prototype-app.html +13 -8
  119. package/templates/qoder/templates/prototype-web.html +376 -93
  120. package/templates/root/AGENTS.md +89 -34
  121. package/templates/root/requirements.txt +21 -0
  122. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +259 -259
  123. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -186
  124. package/templates/qoder/agents/prd-planning.md +0 -56
  125. package/templates/qoder/agents/prd-research.md +0 -33
  126. package/templates/qoder/commands/wl-insight.md +0 -51
  127. package/templates/qoder/skills/wl-prd/SKILL.md +0 -89
@@ -23,6 +23,7 @@ QoderWork 从 %USERPROFILE%\\.qoderwork\\skills\\ 加载技能(每个子目录
23
23
  """
24
24
 
25
25
  import argparse
26
+ import json
26
27
  import os
27
28
  import shutil
28
29
  import subprocess
@@ -52,6 +53,50 @@ else:
52
53
  _HOME = Path.home()
53
54
  QODERWORK_SKILLS_DIR = _HOME / ".qoderwork" / "skills"
54
55
  QODERWORK_COMMANDS_DIR = _HOME / ".qoderwork" / "commands"
56
+ QODERWORK_MCP_FILE = _HOME / ".qoderwork" / "mcp.json"
57
+
58
+ # Playwright MCP 配置 (让 QoderWork 的 AI 能用 Playwright 工具驱动浏览器, 快且稳)
59
+ # 可选增强: 不装 playwright 也不影响 QoderWork 自带连接器
60
+ # ⚠️ 实测: npx @playwright/mcp@latest 冷启动 11.7s, QoderWork 会超时杀掉 → 工具注册失败
61
+ # → AI 瞎猜工具名(BrowserNavigate) → 全失败。
62
+ # 解法: 优先用"全局已装的 cli.js + node 直启"(0.57s), 探测不到才回退 npx。
63
+ def _find_playwright_mcp_cli():
64
+ """探测全局已装的 @playwright/mcp/cli.js 路径, 返回 (node, cli.js) 或 None。
65
+ Windows 下 npm 是 .cmd, 必须 shell=True 才能调到。"""
66
+ import shutil, subprocess
67
+ node = shutil.which("node")
68
+ if not node:
69
+ return None
70
+ # npm root -g 拿全局 node_modules 路径 (最准, 跨自定义prefix)
71
+ try:
72
+ out = subprocess.run("npm root -g", capture_output=True, text=True,
73
+ shell=True, timeout=10)
74
+ root = out.stdout.strip()
75
+ if root:
76
+ cli = os.path.join(root, "@playwright", "mcp", "cli.js")
77
+ if os.path.isfile(cli):
78
+ return node, cli
79
+ except Exception:
80
+ pass
81
+ # 回退: 试常见全局路径
82
+ for cand in [os.path.join(os.path.expanduser("~"), "AppData", "Roaming", "npm",
83
+ "node_modules", "@playwright", "mcp", "cli.js")]:
84
+ if os.path.isfile(cand):
85
+ return node, cand
86
+ return None
87
+
88
+
89
+ def _playwright_mcp_entry():
90
+ """生成 playwright MCP 配置: 优先 node+cli.js(快), 回退 npx(慢但通用)。"""
91
+ found = _find_playwright_mcp_cli()
92
+ if found:
93
+ node, cli = found
94
+ return {"playwright": {"command": node, "args": [cli]}}
95
+ # 回退: npx (冷启动慢, 可能被 QoderWork 超时杀, 但通用)
96
+ return {"playwright": {"command": "npx", "args": ["@playwright/mcp@latest"]}}
97
+
98
+
99
+ PLAYWRIGHT_MCP_ENTRY = _playwright_mcp_entry()
55
100
 
56
101
  # 旧的错误路径 (迁移清理用)
57
102
  LEGACY_WRONG_DIR = _HOME / ".qoderworkcn" / "skills"
@@ -197,6 +242,165 @@ def uninstall_one(name: str) -> str:
197
242
  return f"error: {e}"
198
243
 
199
244
 
245
+ def setup_playwright_mcp(dry=False):
246
+ """把 Playwright MCP 写进 ~/.qoderwork/mcp.json (可选增强, 不影响自带连接器)。
247
+
248
+ 让 QoderWork 的 AI 能用 Playwright 工具(browser_navigate/click/type/screenshot)
249
+ 驱动浏览器 —— 快(10s级 vs 自带连接器 5-8分钟)、稳(不折腾 tab)、不依赖扩展。
250
+ 已有 playwright 配置则跳过(幂等); 没有则追加。
251
+ """
252
+ import json
253
+ mcp_path = QODERWORK_MCP_FILE
254
+ # 读现有配置
255
+ existing = {}
256
+ if mcp_path.is_file():
257
+ try:
258
+ existing = json.loads(mcp_path.read_text(encoding="utf-8"))
259
+ if not isinstance(existing, dict):
260
+ existing = {}
261
+ except (json.JSONDecodeError, OSError):
262
+ existing = {}
263
+ servers = existing.get("mcpServers", {})
264
+ if not isinstance(servers, dict):
265
+ servers = {}
266
+ if "playwright" in servers:
267
+ # 已有配置: 若是旧的 npx@latest(慢, 易超时失败), 升级成 node+cli.js(快)
268
+ cur = servers["playwright"]
269
+ if cur.get("command") == "npx":
270
+ new_entry = PLAYWRIGHT_MCP_ENTRY["playwright"]
271
+ if new_entry.get("command") != "npx": # 探测到了 cli.js
272
+ if dry:
273
+ return "would-upgrade"
274
+ servers["playwright"] = new_entry
275
+ existing["mcpServers"] = servers
276
+ mcp_path.write_text(json.dumps(existing, indent=2, ensure_ascii=False), encoding="utf-8")
277
+ return "upgraded"
278
+ return "ok-existing"
279
+ if dry:
280
+ return "would-add"
281
+ # 追加 playwright
282
+ servers["playwright"] = PLAYWRIGHT_MCP_ENTRY["playwright"]
283
+ existing["mcpServers"] = servers
284
+ mcp_path.parent.mkdir(parents=True, exist_ok=True)
285
+ mcp_path.write_text(
286
+ json.dumps(existing, indent=2, ensure_ascii=False) + "\n",
287
+ encoding="utf-8",
288
+ )
289
+ return "added"
290
+
291
+
292
+ def setup_lanhu_mcp(dry=False):
293
+ """注册蓝湖 MCP 到 ~/.qoderwork/mcp.json (可选增强, 设计稿直读)。
294
+
295
+ 蓝湖 MCP (dsphper/lanhu-mcp) 是第三方 HTTP server, 让设计师发蓝湖链接,
296
+ AI 直读设计稿(颜色/尺寸/字体/切图)。注册成 url 模式 (Streamable HTTP),
297
+ 跟 playwright/kg/mysql 这些 stdio server 并列。
298
+
299
+ 注意: 蓝湖是 HTTP server, 需要用户手动启动
300
+ python .qoder/scripts/setup_lanhu.py start
301
+ (启动脚本自动读当前开发者的 cookie 注入, 不用手动配 .env)
302
+ 本函数只注册 url (幂等), 不负责 clone/装依赖/启动 —— 那些由 setup_lanhu.py
303
+ 在 setup.py Step 4.5 完成。这里只做 mcp.json 条目同步, 保证团队所有人
304
+ 跑 install_qoderwork 后都能看到蓝湖 server。
305
+
306
+ 返回: 'ok-existing' / 'added' / 'would-add'
307
+ """
308
+ # 复用 setup_lanhu.py 的注册逻辑 (单一事实源)
309
+ try:
310
+ sys.path.insert(0, str(SCRIPT_DIR))
311
+ from setup_lanhu import register_lanhu_mcp
312
+ except ImportError:
313
+ return 'would-add'
314
+ return register_lanhu_mcp(developer_name='Developer', dry=dry)
315
+
316
+
317
+ def detect_python_command():
318
+ """探测本机可用的 python 命令名 (跨平台)。
319
+ Windows: py > python (无 python3); mac/linux: python3 > python。
320
+ 返回命令名字符串 (在 PATH 里的), 失败返回 None。"""
321
+ for cand in (('py', '-3'), ('python3', None), ('python', None)):
322
+ name, extra = cand
323
+ if not shutil.which(name):
324
+ continue
325
+ cmd = [name] + ([extra] if extra else []) + ['--version']
326
+ try:
327
+ r = subprocess.run(cmd, capture_output=True, text=True,
328
+ encoding='utf-8', errors='replace')
329
+ if r.returncode == 0:
330
+ # 返回完整调用前缀 (py -3 / python3 / python)
331
+ return ' '.join(cmd[:-1])
332
+ except (OSError, subprocess.SubprocessError):
333
+ continue
334
+ return None
335
+
336
+
337
+ def rewrite_mcp_json_for_launcher():
338
+ """把 mcp.json 里所有 python MCP (kg/mysql/lanhu) 改成 launcher 模式。
339
+
340
+ 旧 (写死路径): {"command": "E:\\...\\python.exe", "args": ["E:\\...\\server.py"]}
341
+ 新 (launcher): {"command": "<py cmd>", "args": ["<home>/.qoderwork/mcp_launcher.py", "<name>"]}
342
+
343
+ 幂等: 已是 launcher 模式 (args 含 mcp_launcher.py) 则跳过。
344
+ 全新机器: mcp.json 不存在时创建空骨架 + 写入 kg/mysql/lanhu 条目。
345
+ mysql env 抽到 ~/.qoderwork/.mcp-mysql-env.json (launcher 读)。
346
+ """
347
+ # 全新机器: mcp.json 不存在 → 创建空骨架 (playwright 后续会追加)
348
+ if not QODERWORK_MCP_FILE.is_file():
349
+ QODERWORK_MCP_FILE.parent.mkdir(parents=True, exist_ok=True)
350
+ QODERWORK_MCP_FILE.write_text(
351
+ '{"mcpServers": {}}\n', encoding="utf-8")
352
+ try:
353
+ cfg = json.loads(QODERWORK_MCP_FILE.read_text(encoding="utf-8"))
354
+ except (json.JSONDecodeError, OSError):
355
+ return
356
+ servers = cfg.get("mcpServers", {})
357
+ if not isinstance(servers, dict):
358
+ return
359
+
360
+ py_cmd = detect_python_command() or "python"
361
+ launcher = str(_HOME / ".qoderwork" / "mcp_launcher.py")
362
+ # MCP 名 → launcher 子命令
363
+ name_map = {
364
+ "qoder-knowledge-graph": "kg",
365
+ "qoder-mysql": "mysql",
366
+ "lanhu": "lanhu",
367
+ }
368
+ changed = False
369
+ for srv, sub in name_map.items():
370
+ entry = servers.get(srv)
371
+ # 全新机器: kg/mysql/lanhu 条目可能不存在 → 创建它 (launcher 模式)
372
+ if not entry:
373
+ servers[srv] = {"command": py_cmd, "args": [launcher, sub]}
374
+ changed = True
375
+ continue
376
+ # 已是 launcher 模式?
377
+ args = entry.get("args", []) if isinstance(entry, dict) else []
378
+ if args and any("mcp_launcher.py" in str(a) for a in args):
379
+ continue
380
+ # mysql env 抽离到独立文件
381
+ if srv == "qoder-mysql" and isinstance(entry, dict) and entry.get("env"):
382
+ env_file = _HOME / ".qoderwork" / ".mcp-mysql-env.json"
383
+ try:
384
+ env_file.write_text(json.dumps(entry["env"], indent=2, ensure_ascii=False) + "\n",
385
+ encoding="utf-8")
386
+ except OSError:
387
+ pass
388
+ # 改成 launcher 模式
389
+ servers[srv] = {"command": py_cmd, "args": [launcher, sub]}
390
+ changed = True
391
+
392
+ if changed:
393
+ cfg["mcpServers"] = servers
394
+ try:
395
+ QODERWORK_MCP_FILE.write_text(
396
+ json.dumps(cfg, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
397
+ print(f"\n--- mcp.json 重写为 launcher 模式 ---")
398
+ print(f" [UPD] kg/mysql/lanhu → command={py_cmd}, args=[mcp_launcher.py, <名>]")
399
+ print(f" 不再写死仓库路径/venv路径, 跨机器通用")
400
+ except OSError as e:
401
+ print(f" [ERR] mcp.json 重写失败: {e}")
402
+
403
+
200
404
  def main():
201
405
  parser = argparse.ArgumentParser(
202
406
  description="把 .qoder/skills/ 安装到 QoderWork 桌面端目录"
@@ -209,8 +413,49 @@ def main():
209
413
  help="用拷贝代替 junction(非 Windows 或不想软链时用)")
210
414
  parser.add_argument("--force-commands", action="store_true",
211
415
  help="强制覆盖已存在的 command 文件(升级时用;默认已存在则跳过)")
416
+ parser.add_argument("--mcp-only", action="store_true",
417
+ help="只刷新 launcher + mcp.json,不装 skill(update 时调)")
212
418
  args = parser.parse_args()
213
419
 
420
+ # --mcp-only: 轻量模式, 只刷 launcher + mcp.json, 不装 skill
421
+ if args.mcp_only:
422
+ print("=" * 56)
423
+ print("MCP 配置刷新 (--mcp-only)")
424
+ print("=" * 56)
425
+ # 1. 复制 launcher
426
+ launcher_src = SOURCE_COMMANDS_DIR.parent / "scripts" / "mcp_launcher.py"
427
+ launcher_dst = _HOME / ".qoderwork" / "mcp_launcher.py"
428
+ try:
429
+ launcher_dst.parent.mkdir(parents=True, exist_ok=True)
430
+ if launcher_src.is_file():
431
+ import shutil as _sh
432
+ _sh.copy2(str(launcher_src), str(launcher_dst))
433
+ print(f" [OK] launcher 已刷新: {launcher_dst}")
434
+ else:
435
+ print(f" [ERR] 源不存在: {launcher_src}")
436
+ except OSError as e:
437
+ print(f" [ERR] 复制失败: {e}")
438
+ # 2. 重写 mcp.json 为 launcher 模式 (创建 kg/mysql/lanhu)
439
+ rewrite_mcp_json_for_launcher()
440
+ # 3. 补 playwright (rewrite 不含它)
441
+ try:
442
+ setup_playwright_mcp(dry=False)
443
+ except Exception:
444
+ pass
445
+ # 4. 写 .repo-root 锚点 (launcher 定位仓库根用)
446
+ # 用 PROJECT_ROOT (脚本自身定位的仓库根) 而非 cwd —— 修"安装目录不一致"bug:
447
+ # 用户可能从任意目录跑 install_qoderwork (home/其他项目), cwd ≠ 仓库根,
448
+ # 写 cwd 会让 .repo-root 指向错误目录 → MCP 全找不到仓库。
449
+ # PROJECT_ROOT 由本文件位置推出 (scripts/ -> .qoder/ -> repo), 与 cwd 无关。
450
+ anchor = _HOME / ".qoderwork" / ".repo-root"
451
+ try:
452
+ anchor.parent.mkdir(parents=True, exist_ok=True)
453
+ anchor.write_text(str(PROJECT_ROOT), encoding="utf-8")
454
+ print(f" [OK] .repo-root 锚点: {anchor} -> {PROJECT_ROOT}")
455
+ except OSError:
456
+ pass
457
+ return
458
+
214
459
  print("=" * 56)
215
460
  print("QoderWork 技能安装器")
216
461
  print(f" 源: {SOURCE_SKILLS_DIR}")
@@ -308,8 +553,8 @@ def main():
308
553
  if action in ("install", "check") and SOURCE_COMMANDS_DIR.is_dir():
309
554
  print("\n--- Commands (/wl-* 斜杠命令) ---")
310
555
  cmd_count = {"ok": 0, "new": 0, "upd": 0}
311
- for cmd_file in sorted(SOURCE_COMMANDS_DIR.glob("wl-*.md")):
312
- name = cmd_file.name # e.g. wl-prd.md
556
+ for cmd_file in sorted(SOURCE_COMMANDS_DIR.rglob("*.md")):
557
+ name = cmd_file.name # e.g. wl-prd.md (子目录归类的也平铺到目标)
313
558
  target = QODERWORK_COMMANDS_DIR / name
314
559
  if action == "check":
315
560
  if target.exists():
@@ -318,19 +563,21 @@ def main():
318
563
  print(f" [MISS] {name}")
319
564
  else: # install
320
565
  need_copy = False
566
+ is_update = False
321
567
  if not target.exists():
322
568
  need_copy = True
323
- elif args.force_commands:
324
- # 强制刷新(升级场景):仅当内容不同才覆盖,避免无谓写
325
- import shutil
569
+ else:
570
+ # 始终按内容同步(幂等): 不同就刷新。
571
+ # command 是纯文本小文件, 内容不同说明源已升级, 必须覆盖,
572
+ # 否则 QoderWork 用旧 command → 走错流程(如旧 wl-test 搜代码)。
326
573
  if not file_equal(cmd_file, target):
327
574
  need_copy = True
575
+ is_update = True
328
576
  if need_copy:
329
577
  try:
330
578
  QODERWORK_COMMANDS_DIR.mkdir(parents=True, exist_ok=True)
331
- import shutil
332
579
  shutil.copy2(str(cmd_file), str(target))
333
- if target.exists() and args.force_commands:
580
+ if is_update:
334
581
  cmd_count["upd"] += 1
335
582
  else:
336
583
  cmd_count["new"] += 1
@@ -343,11 +590,121 @@ def main():
343
590
  msg += f" / {cmd_count['upd']} 刷新"
344
591
  print(msg)
345
592
 
593
+ # === 2.5. repo_root.py (QoderWork 桌面端仓库根定位器) ===
594
+ # QoderWork 桌面端工作目录是临时会话目录, 不是仓库根, 相对路径失效。
595
+ # repo_root.py 从 mcp.json 反推仓库根, 命令文件靠它定位脚本/模板。
596
+ if action in ("install", "check"):
597
+ repo_root_src = SOURCE_COMMANDS_DIR.parent / "scripts" / "repo_root.py"
598
+ repo_root_dst = _HOME / ".qoderwork" / "repo_root.py"
599
+ if repo_root_src.is_file():
600
+ if action == "check":
601
+ if repo_root_dst.exists():
602
+ print(f"\n--- repo_root.py (仓库根定位器) ---")
603
+ print(f" [OK] {repo_root_dst}")
604
+ else:
605
+ print(f"\n--- repo_root.py (仓库根定位器) ---")
606
+ print(f" [MISS] 未安装到 {repo_root_dst}")
607
+ else:
608
+ try:
609
+ repo_root_dst.parent.mkdir(parents=True, exist_ok=True)
610
+ shutil.copy2(str(repo_root_src), str(repo_root_dst))
611
+ print(f"\n--- repo_root.py (仓库根定位器) ---")
612
+ print(f" [OK] 已安装: {repo_root_dst}")
613
+ except OSError as e:
614
+ print(f"\n--- repo_root.py (仓库根定位器) ---")
615
+ print(f" [ERR] {e}")
616
+
617
+ # === 2.6. mcp_launcher.py + .repo-root (统一 MCP 启动器, 消除写死路径) ===
618
+ # mcp_launcher.py 让 mcp.json 不再写死仓库路径/venv路径, 跨 Win/Mac 通用。
619
+ # .repo-root 锚点文件让 launcher 能定位仓库根 (不依赖 mcp.json 里的路径)。
620
+ if action in ("install", "check"):
621
+ print(f"\n--- mcp_launcher.py (统一 MCP 启动器) ---")
622
+ launcher_src = SOURCE_COMMANDS_DIR.parent / "scripts" / "mcp_launcher.py"
623
+ launcher_dst = _HOME / ".qoderwork" / "mcp_launcher.py"
624
+ anchor_dst = _HOME / ".qoderwork" / ".repo-root"
625
+ if action == "check":
626
+ if launcher_dst.exists():
627
+ print(f" [OK] launcher 已装: {launcher_dst}")
628
+ else:
629
+ print(f" [MISS] launcher 未装")
630
+ if anchor_dst.exists():
631
+ print(f" [OK] .repo-root 锚点在: {anchor_dst}")
632
+ else:
633
+ print(f" [MISS] .repo-root 锚点缺失 (launcher 会回退向上找)")
634
+ else:
635
+ try:
636
+ launcher_dst.parent.mkdir(parents=True, exist_ok=True)
637
+ if launcher_src.is_file():
638
+ shutil.copy2(str(launcher_src), str(launcher_dst))
639
+ print(f" [OK] launcher 已装: {launcher_dst}")
640
+ else:
641
+ print(f" [ERR] 源不存在: {launcher_src}")
642
+ # 写 .repo-root 锚点 (记录当前仓库根, launcher 读它定位)
643
+ anchor_dst.write_text(str(PROJECT_ROOT) + "\n", encoding="utf-8")
644
+ print(f" [OK] .repo-root 锚点已写: {PROJECT_ROOT}")
645
+ except OSError as e:
646
+ print(f" [ERR] {e}")
647
+
648
+ # === 2.7. 重写 mcp.json: 所有 python MCP 改 launcher 模式 (消除写死路径) ===
649
+ if action == "install":
650
+ rewrite_mcp_json_for_launcher()
651
+
652
+ # === 3. Playwright MCP (可选增强: 让 QoderWork 能用 Playwright 驱动浏览器) ===
653
+ if action in ("install", "check"):
654
+ print("\n--- Playwright MCP (浏览器自动化增强, 可选) ---")
655
+ pw_status = setup_playwright_mcp(dry=(action == "check"))
656
+ if pw_status == "ok-existing":
657
+ print(f" [OK] playwright MCP 已配置: {QODERWORK_MCP_FILE}")
658
+ elif pw_status == "added":
659
+ print(f" [NEW] 已写入 playwright MCP: {QODERWORK_MCP_FILE}")
660
+ print(" QoderWork 重启后, AI 可用 Playwright 工具驱动浏览器(快、稳)")
661
+ elif pw_status == "upgraded":
662
+ print(f" [FIX] playwright MCP 已升级: npx(慢,易超时) → node+cli.js(0.5s启动)")
663
+ print(" ⚠️ 必须 重启 QoderWork 生效。旧 npx 配置会被 QoderWork 超时杀掉导致工具注册失败")
664
+ elif pw_status == "would-upgrade":
665
+ print(f" [FIX] 检测到旧 npx 配置(慢), 安装时会升级为 node+cli.js")
666
+ elif pw_status == "would-add":
667
+ print(f" [MISS] 未配置 playwright MCP, 安装时会写入 {QODERWORK_MCP_FILE}")
668
+ # 检查 playwright mcp 是否已装(优先看 cli.js, 决定能否走快路径)
669
+ pw_cli = _find_playwright_mcp_cli()
670
+ if pw_cli:
671
+ print(f" 启动方式: node + cli.js (0.5s, 快) ← {pw_cli[1]}")
672
+ else:
673
+ npx_ok = shutil.which("npx") is not None
674
+ print(f" 启动方式: {'npx (11s冷启动, 可能被QoderWork超时杀)' if npx_ok else '✗ 未装 Node.js'}")
675
+ if npx_ok:
676
+ print(" 提示: 装 @playwright/mcp 全局后重跑本脚本, 会自动切换到快的 node 启动: npm i -g @playwright/mcp")
677
+
678
+ # === 4. 蓝湖 MCP (可选增强: 设计师发蓝湖链接, AI 直读设计稿) ===
679
+ if action in ("install", "check"):
680
+ print("\n--- 蓝湖 MCP (设计稿直读增强, 可选) ---")
681
+ lh_status = setup_lanhu_mcp(dry=(action == "check"))
682
+ if lh_status == "ok-existing":
683
+ print(f" [OK] 蓝湖 MCP 已注册: {QODERWORK_MCP_FILE}")
684
+ elif lh_status == "added":
685
+ print(f" [NEW] 已写入蓝湖 MCP (STDIO): {QODERWORK_MCP_FILE}")
686
+ print(" QoderWork 开自动起、关自动停; 命令内授权调 mcp__lanhu__*")
687
+ elif lh_status == "upgraded":
688
+ print(f" [UPD] 蓝湖 MCP 升级 HTTP→STDIO: {QODERWORK_MCP_FILE}")
689
+ print(" 重启 QoderWork 后自动开关, 无需手动 start")
690
+ elif lh_status == "would-add":
691
+ print(f" [MISS] 未注册蓝湖 MCP, 安装时会写入 {QODERWORK_MCP_FILE}")
692
+ # STDIO 模式由 QoderWork 自动管, 不查端口; 只确认 wrapper + 源码就绪
693
+ wrapper = PROJECT_ROOT / ".qoder" / "scripts" / "lanhu_stdio_wrapper.py"
694
+ lanhu_dir = PROJECT_ROOT / "external" / "lanhu-mcp"
695
+ if wrapper.is_file():
696
+ print(f" wrapper 就绪: {wrapper.name} (按角色读 cookie)")
697
+ else:
698
+ print(f" [MISS] wrapper 缺失: {wrapper}")
699
+ if not lanhu_dir.is_dir():
700
+ print(" 蓝湖源码: 未 clone (跑 setup.py 或 setup_lanhu.py 安装)")
701
+
346
702
  if action == "install" and counts["err"] == 0:
347
703
  print("\n✓ 安装完成。重启 QoderWork(或新建对话)后技能生效。")
348
704
  print(" 在 QoderWork 里直接用自然语言即可,例如:")
349
- print(' "写个保险异常筛选的 PRD" -> prd-generator + prototype-generator')
350
- print(' "查一下考勤代码在哪" -> wl-search')
705
+ print(' "写个保险异常筛选的 PRD" -> /wl-prd-full(正经需求)')
706
+ print(' "列表加个导出按钮" -> /wl-prd-quick(小改动)')
707
+ print(' "查一下考勤代码在哪" -> /wl-search')
351
708
  elif action == "install" and counts["err"] > 0:
352
709
  print(f"\n⚠ 有 {counts['err']} 个安装失败,见上方 [ERR] 行。")
353
710
  print(" 常见原因: 目标路径被占用为非软链。手动删除该目录后重跑本脚本。")