@hupan56/wlkj 2.6.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 (125) hide show
  1. package/bin/cli.js +261 -11
  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-reference.md +8 -2
  6. package/templates/qoder/commands/optional/wl-insight.md +275 -0
  7. package/templates/qoder/commands/{wl-report.md → optional/wl-report.md} +13 -5
  8. package/templates/qoder/commands/{wl-spec.md → optional/wl-spec.md} +1 -1
  9. package/templates/qoder/commands/{wl-status.md → optional/wl-status.md} +28 -2
  10. package/templates/qoder/commands/wl-code.md +10 -2
  11. package/templates/qoder/commands/wl-commit.md +1 -1
  12. package/templates/qoder/commands/wl-design-draw.md +78 -0
  13. package/templates/qoder/commands/wl-design-scan.md +108 -0
  14. package/templates/qoder/commands/wl-design-spec.md +154 -0
  15. package/templates/qoder/commands/wl-design.md +32 -0
  16. package/templates/qoder/commands/wl-init.md +24 -3
  17. package/templates/qoder/commands/wl-prd-full.md +226 -0
  18. package/templates/qoder/commands/wl-prd-quick.md +134 -0
  19. package/templates/qoder/commands/wl-prd-review.md +104 -0
  20. package/templates/qoder/commands/wl-prd.md +17 -311
  21. package/templates/qoder/commands/wl-search.md +66 -30
  22. package/templates/qoder/commands/wl-task.md +290 -59
  23. package/templates/qoder/commands/wl-test.md +92 -24
  24. package/templates/qoder/config.yaml +51 -15
  25. package/templates/qoder/hooks/inject-workflow-state.py +2 -2
  26. package/templates/qoder/hooks/session-start.py +82 -51
  27. package/templates/qoder/rules/wl-pipeline.md +216 -105
  28. package/templates/qoder/scripts/__pycache__/search_index.cpython-39.pyc +0 -0
  29. package/templates/qoder/scripts/archive_prd.py +377 -0
  30. package/templates/qoder/scripts/autotest.py +1715 -0
  31. package/templates/qoder/scripts/autotest_batch.py +224 -0
  32. package/templates/qoder/scripts/autotest_run.py +297 -0
  33. package/templates/qoder/scripts/benchmark.py +210 -209
  34. package/templates/qoder/scripts/build_style_index.py +444 -4
  35. package/templates/qoder/scripts/check_carriers.py +238 -0
  36. package/templates/qoder/scripts/check_mcp.py +298 -0
  37. package/templates/qoder/scripts/check_qoderwork_consistency.py +166 -0
  38. package/templates/qoder/scripts/common/events.py +46 -0
  39. package/templates/qoder/scripts/common/extract.py +419 -0
  40. package/templates/qoder/scripts/common/graph_traverse.py +533 -0
  41. package/templates/qoder/scripts/common/paths.py +89 -0
  42. package/templates/qoder/scripts/common/pip_install.py +144 -0
  43. package/templates/qoder/scripts/common/platform_guard.py +61 -0
  44. package/templates/qoder/scripts/common/search_engine.py +205 -205
  45. package/templates/qoder/scripts/common/terms.py +57 -0
  46. package/templates/qoder/scripts/common/ts_extract.py +536 -0
  47. package/templates/qoder/scripts/context_pack.py +73 -13
  48. package/templates/qoder/scripts/enrich_prompt.py +226 -0
  49. package/templates/qoder/scripts/eval_prd.py +318 -231
  50. package/templates/qoder/scripts/export.py +487 -487
  51. package/templates/qoder/scripts/extract_api_params.py +246 -0
  52. package/templates/qoder/scripts/extract_routes.py +54 -0
  53. package/templates/qoder/scripts/extract_routes_tree.py +78 -0
  54. package/templates/qoder/scripts/fill_prototype.py +707 -0
  55. package/templates/qoder/scripts/gen_design_doc.py +394 -0
  56. package/templates/qoder/scripts/git_sync.py +27 -15
  57. package/templates/qoder/scripts/init_doctor.py +292 -40
  58. package/templates/qoder/scripts/install_qoderwork.py +366 -9
  59. package/templates/qoder/scripts/kg.py +708 -0
  60. package/templates/qoder/scripts/kg_auto_login.py +196 -0
  61. package/templates/qoder/scripts/kg_build.py +612 -0
  62. package/templates/qoder/scripts/kg_build_db.py +327 -0
  63. package/templates/qoder/scripts/kg_duckdb.py +549 -0
  64. package/templates/qoder/scripts/kg_incremental.py +393 -0
  65. package/templates/qoder/scripts/kg_link_db.py +224 -0
  66. package/templates/qoder/scripts/kg_mcp_server.py +801 -0
  67. package/templates/qoder/scripts/kg_semantic.py +150 -0
  68. package/templates/qoder/scripts/kg_test_runner.py +241 -0
  69. package/templates/qoder/scripts/lanhu_stdio_wrapper.py +119 -0
  70. package/templates/qoder/scripts/learn.py +118 -39
  71. package/templates/qoder/scripts/learn_aggregate.py +201 -0
  72. package/templates/qoder/scripts/mcp_launcher.py +359 -0
  73. package/templates/qoder/scripts/mysql_mcp_server.py +396 -0
  74. package/templates/qoder/scripts/repo_root.py +106 -0
  75. package/templates/qoder/scripts/role.py +12 -0
  76. package/templates/qoder/scripts/run_weekly_update.bat +5 -0
  77. package/templates/qoder/scripts/run_weekly_update.sh +5 -0
  78. package/templates/qoder/scripts/search_index.py +307 -60
  79. package/templates/qoder/scripts/secure-ls.js +5640 -0
  80. package/templates/qoder/scripts/setup.py +706 -641
  81. package/templates/qoder/scripts/setup_lanhu.py +963 -0
  82. package/templates/qoder/scripts/status.py +250 -11
  83. package/templates/qoder/scripts/sync_carriers.py +259 -0
  84. package/templates/qoder/scripts/syncgate.py +2 -2
  85. package/templates/qoder/scripts/task.py +75 -0
  86. package/templates/qoder/scripts/team_sync.py +60 -4
  87. package/templates/qoder/scripts/workspace_init.py +1 -1
  88. package/templates/qoder/skills/design-import/SKILL.md +226 -0
  89. package/templates/qoder/skills/design-import/figma-workflow.md +81 -0
  90. package/templates/qoder/skills/design-review/SKILL.md +82 -25
  91. package/templates/qoder/skills/prd-generator/SKILL.md +184 -60
  92. package/templates/qoder/skills/prd-review/SKILL.md +18 -1
  93. package/templates/qoder/skills/prompt-enrich/SKILL.md +90 -0
  94. package/templates/qoder/skills/prototype-generator/SKILL.md +256 -141
  95. package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
  96. package/templates/qoder/skills/spec-coder/SKILL.md +18 -1
  97. package/templates/qoder/skills/spec-generator/SKILL.md +18 -1
  98. package/templates/qoder/skills/test-generator/SKILL.md +15 -2
  99. package/templates/qoder/skills/wl-code/SKILL.md +55 -36
  100. package/templates/qoder/skills/wl-commit/SKILL.md +89 -76
  101. package/templates/qoder/skills/wl-design/SKILL.md +55 -0
  102. package/templates/qoder/skills/wl-init/SKILL.md +76 -67
  103. package/templates/qoder/skills/wl-insight/SKILL.md +201 -81
  104. package/templates/qoder/skills/wl-prd-full/SKILL.md +69 -0
  105. package/templates/qoder/skills/wl-prd-quick/SKILL.md +49 -0
  106. package/templates/qoder/skills/wl-prd-review/SKILL.md +34 -0
  107. package/templates/qoder/skills/wl-report/SKILL.md +131 -107
  108. package/templates/qoder/skills/wl-search/SKILL.md +141 -75
  109. package/templates/qoder/skills/wl-spec/SKILL.md +49 -39
  110. package/templates/qoder/skills/wl-status/SKILL.md +83 -61
  111. package/templates/qoder/skills/wl-task/SKILL.md +132 -58
  112. package/templates/qoder/skills/wl-test/SKILL.md +406 -40
  113. package/templates/qoder/templates/prototype-app.html +13 -8
  114. package/templates/qoder/templates/prototype-web.html +376 -93
  115. package/templates/root/AGENTS.md +89 -34
  116. package/templates/root/requirements.txt +21 -0
  117. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +259 -259
  118. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -186
  119. package/templates/qoder/agents/prd-planning.md +0 -57
  120. package/templates/qoder/agents/prd-research.md +0 -34
  121. package/templates/qoder/commands/wl-insight.md +0 -51
  122. package/templates/qoder/scripts/__pycache__/init_doctor.cpython-39.pyc +0 -0
  123. package/templates/qoder/scripts/__pycache__/setup.cpython-39.pyc +0 -0
  124. package/templates/qoder/scripts/common/__pycache__/developer.cpython-39.pyc +0 -0
  125. package/templates/qoder/skills/wl-prd/SKILL.md +0 -92
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ check_carriers.py - 四载体一致性守卫 (防 IDE/Quest/CLI/QoderWork 漂移)
5
+
6
+ 背景:
7
+ AGENTS.md 自己写了"改规则要同步更新它和 .qoder/rules/wl-pipeline.md"。
8
+ 四个载体 (IDE commands / Quest skills / CLI AGENTS.md / QoderWork) 描述同一个
9
+ 命令, 如果只改一个就漂移了 —— 比如 IDE 里 /wl-test 说要跑单测, 但 QoderWork 的
10
+ skill 还停在旧版。目前没有任何机制发现, git status 里的 sync_templates.py M 状态
11
+ 就是手工同步的证据。
12
+
13
+ check_qoderwork_consistency.py 只查"裸相对路径"这一种退化。
14
+ 本脚本查"内容一致性": 同名命令的描述是否对得上、AGENTS.md 命令表是否齐全。
15
+
16
+ Usage:
17
+ python .qoder/scripts/check_carriers.py
18
+ python .qoder/scripts/check_carriers.py --strict # 警告也算失败
19
+ Exit:
20
+ 0 = 四载体一致
21
+ 1 = 发现漂移 (errors)
22
+ """
23
+ import argparse
24
+ import os
25
+ import re
26
+ import sys
27
+
28
+ if sys.platform == 'win32':
29
+ try:
30
+ sys.stdout.reconfigure(encoding='utf-8')
31
+ except (AttributeError, IOError):
32
+ pass
33
+
34
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
35
+ from common.paths import get_repo_root
36
+
37
+ BASE = get_repo_root()
38
+ COMMANDS_DIR = BASE / '.qoder' / 'commands'
39
+ SKILLS_DIR = BASE / '.qoder' / 'skills'
40
+ AGENTS_MD = BASE / 'AGENTS.md'
41
+
42
+ # description 相似度阈值 (改了标点/措辞不算漂移, 实质改变才算)
43
+ DESC_SIMILAR_THRESHOLD = 0.6
44
+
45
+
46
+ def _read_yaml_frontmatter(path):
47
+ """读 .md 的 YAML frontmatter, 返回 dict (无则空)。"""
48
+ try:
49
+ text = path.read_text(encoding='utf-8', errors='ignore')
50
+ except OSError:
51
+ return {}
52
+ if not text.startswith('---'):
53
+ return {}
54
+ parts = text.split('---', 2)
55
+ if len(parts) < 3:
56
+ return {}
57
+ fm = {}
58
+ for line in parts[1].splitlines():
59
+ line = line.strip()
60
+ if ':' in line:
61
+ k, _, v = line.partition(':')
62
+ fm[k.strip()] = v.strip().strip('"\'')
63
+ return fm
64
+
65
+
66
+ def _similarity(a, b):
67
+ """两字符串的相似度 (0~1), 用SequenceRatio。None/空 = 0。"""
68
+ if not a or not b:
69
+ return 0.0
70
+ from difflib import SequenceMatcher
71
+ return SequenceMatcher(None, a.lower(), b.lower()).ratio()
72
+
73
+
74
+ def _prefix_consistent(cmd_desc, skill_desc):
75
+ """判断 command description 是否是 skill description 的"精简前缀版本"。
76
+
77
+ 设计意图: sync_carriers 把 skill 的 description 截断到触发词前, 写进 command。
78
+ 所以 command = skill 的核心前缀, skill 后面挂触发词。这不是漂移, 是设计如此。
79
+
80
+ 判定: command 是 skill 的前缀 (忽略大小写), 或 command 覆盖了 skill 的核心部分。
81
+ 返回 True = 一致 (设计如此), False = 需进一步用相似度判断。
82
+ """
83
+ if not cmd_desc or not skill_desc:
84
+ return False
85
+ c, s = cmd_desc.lower().strip(), skill_desc.lower().strip()
86
+ # 1. command 是 skill 的前缀 (最常见: sync 后 command = skill 截断版)
87
+ if s.startswith(c):
88
+ return True
89
+ # 2. command 覆盖了 skill 的核心 (skill 去掉触发词后 == command)
90
+ # 触发词标记 (和 sync_carriers._condense_description 对齐)
91
+ for marker in ['。用户说', ';用户说', ' 用户说', '。触发:', ' 触发:', ';触发', '时触发']:
92
+ idx = s.find(marker)
93
+ if idx > 0:
94
+ skill_core = s[:idx + 1] # 含句号/分号
95
+ if skill_core.startswith(c) or c.startswith(skill_core):
96
+ return True
97
+ # 核心部分相似度高也算 (措辞微调)
98
+ if _similarity(c, skill_core) >= 0.85:
99
+ return True
100
+ return False
101
+
102
+
103
+ def check_command_skill_pairing():
104
+ """校验 commands/wl-X.md 和 skills/wl-X/SKILL.md 的 description 是否一致。
105
+ 返回 (errors, warnings)。
106
+ """
107
+ errors = []
108
+ warnings = []
109
+
110
+ # 收集所有 command (含 optional/ 子目录)
111
+ commands = {}
112
+ if COMMANDS_DIR.is_dir():
113
+ for path in COMMANDS_DIR.rglob('*.md'):
114
+ name = path.stem # wl-test
115
+ commands[name] = path
116
+
117
+ # 收集所有 skill
118
+ skills = {}
119
+ if SKILLS_DIR.is_dir():
120
+ for d in SKILLS_DIR.iterdir():
121
+ if d.is_dir() and (d / 'SKILL.md').is_file():
122
+ skills[d.name] = d / 'SKILL.md'
123
+
124
+ # 配对校验 (command 和 skill 同名才校验, 不同名是设计如此)
125
+ common_names = set(commands) & set(skills)
126
+ for name in sorted(common_names):
127
+ cmd_fm = _read_yaml_frontmatter(commands[name])
128
+ skill_fm = _read_yaml_frontmatter(skills[name])
129
+ cmd_desc = cmd_fm.get('description', '')
130
+ skill_desc = skill_fm.get('description', '')
131
+ if not cmd_desc or not skill_desc:
132
+ continue # 没写的跳过
133
+ # 一致性判断: 不只用字符相似度, 还判断"command 是否是 skill 的精简前缀版本"
134
+ # (sync_carriers 产生的就是这个关系: command = skill 去掉触发词后的核心描述)
135
+ # 这样度量才符合设计意图, 不会把"故意精简"误报成漂移。
136
+ if _prefix_consistent(cmd_desc, skill_desc):
137
+ continue # command 是 skill 的精简版 = 一致, 通过
138
+ sim = _similarity(cmd_desc, skill_desc)
139
+ if sim < DESC_SIMILAR_THRESHOLD:
140
+ errors.append((
141
+ name,
142
+ 'description 严重不一致 (相似度 %.0f%%, 阈值 %d%%)' % (sim * 100, DESC_SIMILAR_THRESHOLD * 100),
143
+ 'command: ' + cmd_desc[:60],
144
+ 'skill: ' + skill_desc[:60],
145
+ ))
146
+ elif sim < 0.85:
147
+ warnings.append((
148
+ name,
149
+ 'description 有差异 (相似度 %.0f%%), 建议核对' % (sim * 100),
150
+ ))
151
+
152
+ return errors, warnings
153
+
154
+
155
+ def check_agents_md_command_table():
156
+ """校验 AGENTS.md 的 7 核心命令表与实际文件是否对得上。
157
+ 表里列了但文件不存在 = 漂移 (改了命令没改文档, 或反之)。
158
+ """
159
+ errors = []
160
+ if not AGENTS_MD.is_file():
161
+ return [('AGENTS.md', 'AGENTS.md 不存在 (CLI 载体缺失)', '', '')]
162
+ text = AGENTS_MD.read_text(encoding='utf-8', errors='ignore')
163
+
164
+ # 提取"7 Core Commands"表里的命令名 (形如 `/wl-init`)
165
+ # 表格行格式: | `/wl-init` | ... |
166
+ table_cmds = set()
167
+ in_core_table = False
168
+ for line in text.splitlines():
169
+ s = line.strip()
170
+ if '7 Core' in line or '核心命令' in line:
171
+ in_core_table = True
172
+ if in_core_table:
173
+ for m in re.finditer(r'`(/wl-[a-z-]+)`', s):
174
+ table_cmds.add(m.group(1)[1:]) # 去掉 / 前缀
175
+ if in_core_table and s.startswith('|') and '工序站' in text and s.count('|') >= 2:
176
+ # 表还在继续
177
+ pass
178
+
179
+ # 校验每个表里的命令文件存在
180
+ for cmd in sorted(table_cmds):
181
+ cmd_file = COMMANDS_DIR / (cmd + '.md')
182
+ skill_file = SKILLS_DIR / cmd / 'SKILL.md'
183
+ if not cmd_file.is_file() and not skill_file.is_file():
184
+ errors.append((
185
+ cmd,
186
+ 'AGENTS.md 命令表列了 /%s 但 commands/ 和 skills/ 都没文件' % cmd,
187
+ '',
188
+ '',
189
+ ))
190
+
191
+ return errors
192
+
193
+
194
+ def main():
195
+ ap = argparse.ArgumentParser(description='四载体一致性守卫 (防漂移)')
196
+ ap.add_argument('--strict', action='store_true', help='警告也算失败')
197
+ args = ap.parse_args()
198
+
199
+ print('=' * 64)
200
+ print('四载体一致性检查 (IDE commands / Quest skills / CLI AGENTS.md / QoderWork)')
201
+ print('=' * 64)
202
+
203
+ pair_errors, pair_warnings = check_command_skill_pairing()
204
+ table_errors = check_agents_md_command_table()
205
+
206
+ all_errors = pair_errors + table_errors
207
+
208
+ if pair_warnings:
209
+ print('\n🟡 描述差异 (建议核对, --strict 才算失败):')
210
+ for name, msg, *_ in pair_warnings:
211
+ print(' %s: %s' % (name, msg))
212
+
213
+ if all_errors:
214
+ print('\n🔴 载体漂移 (必修):')
215
+ for item in all_errors:
216
+ name = item[0]
217
+ print(' %s' % name)
218
+ for detail in item[1:]:
219
+ if detail:
220
+ print(' %s' % detail)
221
+
222
+ if not all_errors and not pair_warnings:
223
+ print('✅ 四载体一致: 同名命令描述对得上, AGENTS.md 命令表齐全。')
224
+
225
+ print('-' * 64)
226
+ print('统计: 漂移 %d / 差异 %d' % (len(all_errors), len(pair_warnings)))
227
+ print('提示: 与 check_qoderwork_consistency.py 互补 ——')
228
+ print(' 那个查裸相对路径, 这个查内容一致性。')
229
+
230
+ if all_errors:
231
+ return 1
232
+ if args.strict and pair_warnings:
233
+ return 1
234
+ return 0
235
+
236
+
237
+ if __name__ == '__main__':
238
+ sys.exit(main())
@@ -0,0 +1,298 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ check_mcp.py - MCP 启动健康检查 (验证 init/download 后全系列可用)
5
+
6
+ 为什么需要它:
7
+ install_qoderwork.py --mcp-only 会写 mcp.json + launcher, 但"写了配置"不等于
8
+ "MCP 能起来"。50 人里有人 pip 没装齐、有人 .repo-root 写错、有人 lanhu venv 没建,
9
+ 这些都要等用的时候才发现 MCP 挂了。本脚本主动验证每个 MCP 能否真正启动。
10
+
11
+ 对应需求: "确保 MCP 等 init/download 后, 所有人 qoder 全系列正常运行"。
12
+
13
+ 检查什么 (不真启动 server, 只验证启动条件):
14
+ 1. mcp.json 存在 + 各 server 注册了 launcher 模式
15
+ 2. launcher 能定位仓库根 (.repo-root 锚点 / 环境变量 / 向上找)
16
+ 3. 各 MCP 的依赖就绪:
17
+ - kg: 装了 duckdb 的 Python + kg_mcp_server.py 存在
18
+ - mysql: 装了 pymysql 的 Python + config.yaml 有 mysql 段
19
+ - lanhu: venv 建了 + 当前角色 cookie 配了
20
+ - playwright: node 在 + cli.js / npx 可用
21
+
22
+ Usage:
23
+ python .qoder/scripts/check_mcp.py # 全量检查
24
+ python .qoder/scripts/check_mcp.py --fix # 检查 + 尝试修复 (.repo-root 等)
25
+ Exit: 0 = 全部就绪, 1 = 有 MCP 未就绪
26
+ """
27
+ import json
28
+ import os
29
+ import subprocess
30
+ import sys
31
+ from pathlib import Path
32
+
33
+ if sys.platform == 'win32':
34
+ try:
35
+ sys.stdout.reconfigure(encoding='utf-8')
36
+ except (AttributeError, IOError):
37
+ pass
38
+
39
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
40
+ from common.paths import get_repo_root
41
+
42
+ BASE = get_repo_root()
43
+ HOME = Path(os.environ.get('USERPROFILE') or os.path.expanduser('~'))
44
+ QODERWORK_DIR = HOME / '.qoderwork'
45
+ MCP_FILE = QODERWORK_DIR / 'mcp.json'
46
+ REPO_ROOT_ANCHOR = QODERWORK_DIR / '.repo-root'
47
+
48
+ OK_TAG = '[OK] '
49
+ WARN_TAG = '[WARN]'
50
+ MISS_TAG = '[MISS]'
51
+ BAD_TAG = '[BAD] '
52
+
53
+ issues = []
54
+
55
+
56
+ def say(tag, msg):
57
+ print('{} {}'.format(tag, msg))
58
+
59
+
60
+ def problem(msg, hint=None):
61
+ issues.append(msg)
62
+ say(BAD_TAG, msg)
63
+ if hint:
64
+ print(' -> ' + hint)
65
+
66
+
67
+ def _run(cmd, timeout=10):
68
+ """跑命令, 返回 (returncode, output)。"""
69
+ try:
70
+ r = subprocess.run(cmd, capture_output=True, text=True,
71
+ encoding='utf-8', errors='replace', timeout=timeout)
72
+ return r.returncode, (r.stdout or '') + (r.stderr or '')
73
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
74
+ return 1, ''
75
+
76
+
77
+ def _find_python_with(mod):
78
+ """找一个装了指定模块的 Python。返回路径或 None。"""
79
+ # 当前 Python
80
+ try:
81
+ __import__(mod)
82
+ return sys.executable
83
+ except ImportError:
84
+ pass
85
+ # 其他 Python
86
+ import shutil
87
+ for name in ('python', 'python3', 'py'):
88
+ p = shutil.which(name)
89
+ if p and p != sys.executable:
90
+ rc, _ = _run([p, '-c', 'import %s' % mod], timeout=8)
91
+ if rc == 0:
92
+ return p
93
+ return None
94
+
95
+
96
+ # ============================================================
97
+ # 检查项
98
+ # ============================================================
99
+
100
+ def check_mcp_json(fix=False):
101
+ """1. mcp.json 存在 + launcher 模式。"""
102
+ print('\n--- 1. mcp.json 配置 ---')
103
+ if not MCP_FILE.is_file():
104
+ problem('mcp.json 不存在 (%s)' % MCP_FILE,
105
+ '跑: python .qoder/scripts/install_qoderwork.py --mcp-only')
106
+ return False
107
+ try:
108
+ cfg = json.loads(MCP_FILE.read_text(encoding='utf-8'))
109
+ except (ValueError, OSError):
110
+ problem('mcp.json 解析失败', '文件损坏, 重跑 install_qoderwork.py --mcp-only')
111
+ return False
112
+ servers = cfg.get('mcpServers', {})
113
+ if not servers:
114
+ problem('mcp.json 无 mcpServers', '重跑 install_qoderwork.py --mcp-only')
115
+ return False
116
+ say(OK_TAG, 'mcp.json 存在, 注册了 %d 个 server: %s' % (
117
+ len(servers), ', '.join(servers.keys())))
118
+ # 检查是否 launcher 模式 (command 用 mcp_launcher.py)
119
+ launcher_count = 0
120
+ for name, entry in servers.items():
121
+ args = entry.get('args', []) if isinstance(entry, dict) else []
122
+ if any('mcp_launcher.py' in str(a) for a in args):
123
+ launcher_count += 1
124
+ if launcher_count > 0:
125
+ say(OK_TAG, '%d 个 server 用 launcher 模式 (跨机器通用)' % launcher_count)
126
+ else:
127
+ say(WARN_TAG, '无 launcher 模式 server (可能写死路径), 升级: install_qoderwork.py --mcp-only')
128
+ return True
129
+
130
+
131
+ def check_repo_root(fix=False):
132
+ """2. launcher 能定位仓库根 (.repo-root 锚点)。"""
133
+ print('\n--- 2. 仓库根定位 ---')
134
+ if REPO_ROOT_ANCHOR.is_file():
135
+ try:
136
+ root_val = REPO_ROOT_ANCHOR.read_text(encoding='utf-8').strip()
137
+ if (Path(root_val) / '.qoder' / 'scripts').is_dir():
138
+ say(OK_TAG, '.repo-root 锚点正确: %s' % root_val)
139
+ return True
140
+ else:
141
+ problem('.repo-root 指向的目录不是仓库根: %s' % root_val,
142
+ '重跑 install_qoderwork.py --mcp-only 会用 PROJECT_ROOT 修正')
143
+ except OSError:
144
+ pass
145
+ # 锚点缺失或错误
146
+ if fix:
147
+ try:
148
+ REPO_ROOT_ANCHOR.parent.mkdir(parents=True, exist_ok=True)
149
+ REPO_ROOT_ANCHOR.write_text(str(BASE), encoding='utf-8')
150
+ say(OK_TAG, '.repo-root 锚点已修复: %s -> %s' % (REPO_ROOT_ANCHOR, BASE))
151
+ return True
152
+ except OSError as e:
153
+ problem('修复 .repo-root 失败: %s' % e)
154
+ else:
155
+ problem('.repo-root 锚点缺失或错误',
156
+ '加 --fix 自动修复, 或重跑 install_qoderwork.py --mcp-only')
157
+ return False
158
+
159
+
160
+ def check_launcher_file(fix=False):
161
+ """3. launcher 脚本存在于 ~/.qoderwork/。"""
162
+ print('\n--- 3. MCP launcher ---')
163
+ launcher = QODERWORK_DIR / 'mcp_launcher.py'
164
+ if launcher.is_file():
165
+ say(OK_TAG, 'launcher 存在: %s' % launcher)
166
+ return True
167
+ problem('launcher 不存在: %s' % launcher,
168
+ '重跑 install_qoderwork.py --mcp-only (会复制 launcher)')
169
+ return False
170
+
171
+
172
+ def check_kg():
173
+ """4. kg MCP: duckdb + kg_mcp_server.py。"""
174
+ print('\n--- 4. 知识图谱 MCP (kg) ---')
175
+ server = BASE / '.qoder' / 'scripts' / 'kg_mcp_server.py'
176
+ if not server.is_file():
177
+ problem('kg_mcp_server.py 不存在', '引擎不完整, 重跑 npx wlkj update')
178
+ return
179
+ py = _find_python_with('duckdb')
180
+ if py:
181
+ say(OK_TAG, 'kg 就绪 (python=%s, 装了 duckdb)' % py)
182
+ else:
183
+ problem('找不到装了 duckdb 的 Python',
184
+ 'pip install duckdb 或 npx @hupan56/wlkj install-env')
185
+
186
+
187
+ def check_mysql():
188
+ """5. mysql MCP: pymysql + config.yaml mysql 段。"""
189
+ print('\n--- 5. MySQL MCP ---')
190
+ server = BASE / '.qoder' / 'scripts' / 'mysql_mcp_server.py'
191
+ if not server.is_file():
192
+ problem('mysql_mcp_server.py 不存在')
193
+ return
194
+ py = _find_python_with('pymysql')
195
+ if not py:
196
+ problem('找不到装了 pymysql 的 Python', 'pip install pymysql')
197
+ return
198
+ # config.yaml mysql 段
199
+ cfg_path = BASE / '.qoder' / 'config.yaml'
200
+ has_mysql = False
201
+ if cfg_path.is_file():
202
+ try:
203
+ import yaml
204
+ d = yaml.safe_load(cfg_path.read_text(encoding='utf-8')) or {}
205
+ has_mysql = bool(d.get('mysql', {}).get('host'))
206
+ except Exception:
207
+ pass
208
+ if has_mysql:
209
+ say(OK_TAG, 'mysql 就绪 (config.yaml 已配连接信息)')
210
+ else:
211
+ say(WARN_TAG, 'mysql 依赖就绪, 但 config.yaml 无 mysql 段 (按需配)')
212
+
213
+
214
+ def check_lanhu():
215
+ """6. lanhu MCP: venv + cookie。三态 (就绪/未装/未配)。"""
216
+ print('\n--- 6. 蓝湖 MCP (lanhu, 可选) ---')
217
+ venv = BASE / 'external' / 'lanhu-mcp' / '.venv'
218
+ server = BASE / 'external' / 'lanhu-mcp' / 'lanhu_mcp_server.py'
219
+ if not venv.is_dir() or not server.is_file():
220
+ say(WARN_TAG, 'lanhu 未安装 (团队不用蓝湖可忽略; 用的话: setup_lanhu.py)')
221
+ return
222
+ # venv python
223
+ venv_py = venv / ('Scripts' if sys.platform == 'win32' else 'bin') / ('python.exe' if sys.platform == 'win32' else 'python')
224
+ if not venv_py.is_file():
225
+ problem('lanhu venv python 不存在', '重装: setup_lanhu.py')
226
+ return
227
+ # 当前角色 cookie
228
+ try:
229
+ from common.paths import get_developer
230
+ dev = get_developer(BASE)
231
+ except Exception:
232
+ dev = None
233
+ if not dev:
234
+ say(WARN_TAG, 'lanhu venv 就绪, 但未初始化开发者 (跑 /wl-init)')
235
+ return
236
+ cookie_file = BASE / 'workspace' / 'members' / dev / '.secrets' / 'lanhu.env'
237
+ if cookie_file.is_file():
238
+ say(OK_TAG, 'lanhu 就绪 (角色=%s, cookie 已配)' % dev)
239
+ else:
240
+ say(WARN_TAG, 'lanhu venv 就绪, 但角色 %s 的 cookie 未配 (%s)' % (dev, cookie_file))
241
+
242
+
243
+ def check_playwright():
244
+ """7. playwright MCP: node + cli.js/npx。"""
245
+ print('\n--- 7. Playwright MCP (浏览器测试) ---')
246
+ import shutil
247
+ node = shutil.which('node')
248
+ if not node:
249
+ problem('node 未安装 (playwright MCP 需要)', '装 Node.js: npx wlkj install-env')
250
+ return
251
+ # 优先 cli.js (快), 回退 npx (慢)
252
+ try:
253
+ out = subprocess.run('npm root -g', shell=True, capture_output=True,
254
+ text=True, timeout=10)
255
+ root = out.stdout.strip()
256
+ cli = os.path.join(root, '@playwright', 'mcp', 'cli.js')
257
+ if os.path.isfile(cli):
258
+ say(OK_TAG, 'playwright 就绪 (node + cli.js, 快启动)')
259
+ return
260
+ except Exception:
261
+ pass
262
+ if shutil.which('npx'):
263
+ say(WARN_TAG, 'playwright 可用但走 npx (冷启动慢, 可能被 QoderWork 超时杀)')
264
+ say(WARN_TAG, ' 建议全局装: npm i -g @playwright/mcp')
265
+ else:
266
+ problem('npx 不可用', '装 Node.js')
267
+
268
+
269
+ def main():
270
+ fix = '--fix' in sys.argv
271
+ print('=' * 56)
272
+ print('MCP 健康检查 (验证 init/download 后全系列可用)')
273
+ print('=' * 56)
274
+
275
+ check_mcp_json(fix)
276
+ check_repo_root(fix)
277
+ check_launcher_file(fix)
278
+ check_kg()
279
+ check_mysql()
280
+ check_lanhu()
281
+ check_playwright()
282
+
283
+ print('\n' + '=' * 56)
284
+ if issues:
285
+ print('未就绪 (%d):' % len(issues))
286
+ for i in issues:
287
+ print(' - ' + i)
288
+ print('=' * 56)
289
+ print('一键修复: python .qoder/scripts/check_mcp.py --fix')
290
+ print('重配 MCP: python .qoder/scripts/install_qoderwork.py --mcp-only')
291
+ return 1
292
+ print('所有 MCP 就绪, Qoder 全系列可正常调用。')
293
+ print('=' * 56)
294
+ return 0
295
+
296
+
297
+ if __name__ == '__main__':
298
+ sys.exit(main())