@hupan56/wlkj 2.7.7 → 2.7.9

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.
package/bin/cli.js CHANGED
@@ -557,6 +557,9 @@ function doUpdate() {
557
557
  console.log(` 根文档 + requirements.txt: 已更新`);
558
558
 
559
559
  // === 2.5. git pull 拉最新团队数据 (kg.duckdb/kg_db.duckdb/PRD等在git里) ===
560
+ // git pull 失败会导致 kg.duckdb 拿不到 → kg MCP 无数据(但仍能连)。
561
+ // 失败原因常见: 未配 git 身份 / 远程仓库需要认证 / 网络问题。
562
+ // 这里给出清晰诊断, 不让"git pull 失败"变成无声的静默跳过。
560
563
  try {
561
564
  const { execSync: _es } = require("child_process");
562
565
  const pullR = _es("git pull --no-rebase 2>&1", { cwd, encoding: "utf-8", timeout: 30000 });
@@ -568,7 +571,22 @@ function doUpdate() {
568
571
  console.log(` git: 已拉取最新`);
569
572
  }
570
573
  } catch (e) {
571
- console.log(` git: 跳过(${(e.message || "").slice(0, 50)})`);
574
+ // git pull 失败诊断: 区分"没配git身份"/"远程要认证"/"网络问题"
575
+ const errMsg = String(e.message || "");
576
+ const stdout = String(e.stdout || "");
577
+ const combined = errMsg + " " + stdout;
578
+ if (combined.includes("Authentication failed") || combined.includes("Permission denied") ||
579
+ combined.includes("could not read Username") || combined.includes("不支持密码")) {
580
+ console.log(` git: ⚠️ 认证失败 — kg.duckdb 等团队数据拉不到`);
581
+ console.log(` 原因: git 身份/凭证未配。MCP 能连但无数据。`);
582
+ console.log(` 修复: npx @hupan56/wlkj init <你的名字> (交互式配 git 账号)`);
583
+ } else if (combined.includes("not a git repository") || combined.includes("not a git dir")) {
584
+ console.log(` git: 非git仓库 — 用 team_sync 拉团队数据`);
585
+ console.log(` 修复: npx @hupan56/wlkj init <你的名字>`);
586
+ } else {
587
+ console.log(` git: 跳过 — ${(errMsg).slice(0, 60)}`);
588
+ console.log(` (kg.duckdb 拉不到, MCP 能连但搜索无结果)`);
589
+ }
572
590
  }
573
591
 
574
592
  // === 3. 补装 pip 依赖 (必须在刷 mcp.json 之前!)
@@ -718,6 +736,19 @@ function doUpdate() {
718
736
  }
719
737
  } catch (e) { /* 刷新失败不阻塞升级 */ }
720
738
 
739
+ // === 6.5. MCP 启动实测 (发 initialize 看哪个起不来) ===
740
+ // 这是"根治黄色"的关键一步: 配完 mcp.json 后立即模拟 QoderWork 发 initialize,
741
+ // 看每个 MCP 是 OK / DISABLED(绿) / CRASHED(黄)。有问题当场报, 不让用户重启后才发现。
742
+ console.log(`\n--- MCP 启动实测 (模拟 QoderWork 连接) ---`);
743
+ const diagScript = path.join(cwd, ".qoder", "scripts", "check_mcp_launch.py");
744
+ if (pyCmd && fs.existsSync(diagScript)) {
745
+ try {
746
+ execSync(`${pyCmd} "${diagScript}"`, { cwd, stdio: "inherit", timeout: 60000 });
747
+ } catch (e) {
748
+ console.log(` [WARN] MCP 实测脚本异常 (不阻塞): ${(e.message || "").slice(0, 80)}`);
749
+ }
750
+ }
751
+
721
752
  // === 7. 自检: 关键文件到底齐不齐 ===
722
753
  console.log(`\n--- 自检 (关键文件验证) ---`);
723
754
  const checks = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hupan56/wlkj",
3
- "version": "2.7.7",
3
+ "version": "2.7.9",
4
4
  "description": "AI Product R&D Workflow - PRD/Prototype/Search/Task/Report",
5
5
  "bin": {
6
6
  "wlkj": "bin/cli.js"
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ check_mcp_launch.py - 诊断 MCP 为什么黄色
5
+
6
+ 逐个模拟 mcp_launcher.py 启动每个 MCP, 发送 initialize 请求,
7
+ 看哪个 MCP 启动失败/崩溃/不响应。输出明确的诊断结论。
8
+
9
+ 用法: python .qoder/scripts/check_mcp_launch.py
10
+ """
11
+ import json
12
+ import os
13
+ import subprocess
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ SCRIPTS_DIR = Path(__file__).resolve().parent
18
+ BASE = SCRIPTS_DIR.parent.parent # repo root
19
+
20
+ MCP_NAMES = ['kg', 'mysql', 'zentao', 'lanhu']
21
+
22
+
23
+ def find_python_with(mod):
24
+ """找一个能 import mod 的 python."""
25
+ for py in [sys.executable, 'python', 'python3']:
26
+ try:
27
+ r = subprocess.run([py, '-c', f'import {mod}'],
28
+ capture_output=True, timeout=8)
29
+ if r.returncode == 0:
30
+ return py
31
+ except Exception:
32
+ continue
33
+ return None
34
+
35
+
36
+ def test_mcp(name):
37
+ """测试一个 MCP 能否正常响应 initialize."""
38
+ launcher = SCRIPTS_DIR / 'mcp_launcher.py'
39
+ if not launcher.is_file():
40
+ return {'name': name, 'status': 'ERROR',
41
+ 'detail': f'launcher 不存在: {launcher}'}
42
+
43
+ init_req = json.dumps({
44
+ 'jsonrpc': '2.0', 'id': 1, 'method': 'initialize',
45
+ 'params': {'capabilities': {}, 'protocolVersion': '2024-11-05',
46
+ 'clientInfo': {'name': 'diag', 'version': '1.0'}}
47
+ }) + '\n'
48
+
49
+ try:
50
+ r = subprocess.run(
51
+ [sys.executable, str(launcher), name],
52
+ input=init_req,
53
+ capture_output=True, text=True, timeout=10,
54
+ cwd=str(BASE), encoding='utf-8', errors='replace'
55
+ )
56
+ except subprocess.TimeoutExpired:
57
+ return {'name': name, 'status': 'TIMEOUT',
58
+ 'detail': '启动后 10s 无响应 (可能卡在等 stdin)'}
59
+ except Exception as e:
60
+ return {'name': name, 'status': 'ERROR',
61
+ 'detail': f'启动异常: {e}'}
62
+
63
+ stdout = r.stdout or ''
64
+ stderr = r.stderr or ''
65
+
66
+ # 解析 stdout 找 initialize 响应
67
+ init_ok = False
68
+ disabled = False
69
+ reason = ''
70
+ for line in stdout.split('\n'):
71
+ line = line.strip()
72
+ if not line:
73
+ continue
74
+ try:
75
+ resp = json.loads(line)
76
+ if resp.get('id') == 1 and 'result' in resp:
77
+ init_ok = True
78
+ si = resp['result'].get('serverInfo', {})
79
+ ver = si.get('version', '')
80
+ if 'disabled' in ver:
81
+ disabled = True
82
+ reason = si.get('reason', '')
83
+ break
84
+ except json.JSONDecodeError:
85
+ continue
86
+
87
+ if init_ok and not disabled:
88
+ return {'name': name, 'status': 'OK',
89
+ 'detail': '正常启动, 工具可用'}
90
+ elif init_ok and disabled:
91
+ return {'name': name, 'status': 'DISABLED',
92
+ 'detail': f'以"未启用"模式启动 (绿但0工具): {reason}'}
93
+ elif r.returncode != 0:
94
+ # 进程崩溃了
95
+ return {'name': name, 'status': 'CRASHED',
96
+ 'detail': f'进程退出(rc={r.returncode})。stderr:\n{stderr[-500:]}'}
97
+ else:
98
+ return {'name': name, 'status': 'NO_RESPONSE',
99
+ 'detail': f'没崩溃但没响应 initialize。stdout:\n{stdout[-300:]}\nstderr:\n{stderr[-300:]}'}
100
+
101
+
102
+ def main():
103
+ print('=' * 56)
104
+ print('MCP 启动诊断')
105
+ print(f'仓库: {BASE}')
106
+ print(f'Python: {sys.executable} ({sys.version.split()[0]})')
107
+ print('=' * 56)
108
+
109
+ # 基础检查
110
+ print('\n--- 环境检查 ---')
111
+ for mod, label in [('duckdb', 'kg 依赖'), ('pymysql', 'mysql 依赖'),
112
+ ('requests', 'zentao 依赖'), ('yaml', '配置解析')]:
113
+ try:
114
+ __import__(mod)
115
+ print(f' [OK] {mod} ({label})')
116
+ except ImportError:
117
+ py = find_python_with(mod)
118
+ if py:
119
+ print(f' [WARN] {mod} 在当前Python没有, 但 {py} 有')
120
+ else:
121
+ print(f' [MISS] {mod} ({label}) — pip install {mod}')
122
+
123
+ kg_path = BASE / 'data' / 'index' / 'kg.duckdb'
124
+ print(f'\n kg.duckdb: {"存在" if kg_path.is_file() else "缺失 (git pull 失败? 非管理员跑 team_sync pull)"}')
125
+
126
+ # mcp.json 检查
127
+ home = Path(os.environ.get('USERPROFILE') or os.path.expanduser('~'))
128
+ mcp_file = home / '.qoderwork' / 'mcp.json'
129
+ print(f'\n mcp.json: {mcp_file}')
130
+ if mcp_file.is_file():
131
+ try:
132
+ cfg = json.loads(mcp_file.read_text(encoding='utf-8'))
133
+ servers = cfg.get('mcpServers', {})
134
+ for srv in ['qoder-knowledge-graph', 'qoder-mysql', 'qoder-zentao', 'lanhu']:
135
+ entry = servers.get(srv)
136
+ if entry:
137
+ cmd = entry.get('command', '?')
138
+ args = entry.get('args', [])
139
+ has_launcher = any('mcp_launcher.py' in str(a) for a in args)
140
+ print(f' {srv}: command={cmd!r} launcher={"是" if has_launcher else "否(旧模式)"}')
141
+ else:
142
+ print(f' {srv}: 未注册')
143
+ except Exception as e:
144
+ print(f' [ERR] mcp.json 解析失败: {e}')
145
+ else:
146
+ print(f' [MISS] mcp.json 不存在 (跑 npx @hupan56/wlkj update)')
147
+
148
+ # 逐个测试 MCP
149
+ print('\n--- MCP 启动测试 (模拟 QoderWork 发 initialize) ---')
150
+ results = []
151
+ for name in MCP_NAMES:
152
+ print(f'\n 测试 {name}...')
153
+ r = test_mcp(name)
154
+ results.append(r)
155
+ icon = {'OK': '🟢', 'DISABLED': '🟢(无工具)', 'CRASHED': '🔴',
156
+ 'TIMEOUT': '🟡', 'NO_RESPONSE': '🟡', 'ERROR': '🔴'}
157
+ print(f' {icon.get(r["status"], "?")} {r["status"]}: {r["name"]}')
158
+ if r['status'] not in ('OK', 'DISABLED'):
159
+ print(f' 详情: {r["detail"]}')
160
+
161
+ # 总结
162
+ print('\n' + '=' * 56)
163
+ print('诊断结论:')
164
+ ok = sum(1 for r in results if r['status'] in ('OK', 'DISABLED'))
165
+ bad = sum(1 for r in results if r['status'] not in ('OK', 'DISABLED'))
166
+ if bad == 0:
167
+ print(f' 全部 MCP 启动正常 ({ok}/{len(results)})。')
168
+ print(' 如果 QoderWork 仍显示黄色: 重启 QoderWork 或新建对话。')
169
+ print(' (mcp.json 改动需要重启才生效)')
170
+ else:
171
+ print(f' {bad} 个 MCP 启动有问题:')
172
+ for r in results:
173
+ if r['status'] not in ('OK', 'DISABLED'):
174
+ print(f' - {r["name"]}: {r["status"]}')
175
+ print('\n 常见原因:')
176
+ print(' CRASHED: Python 版本不兼容(如3.14) 或 脚本语法错误 → 看详情里的 stderr')
177
+ print(' TIMEOUT: 脚本卡住了 → 查 DuckDB 文件锁 或 网络等待')
178
+ print(' NO_RESPONSE: stdout 被 print 污染 → 看 stdout 里的非 JSON 内容')
179
+ print('=' * 56)
180
+
181
+
182
+ if __name__ == '__main__':
183
+ main()
@@ -236,7 +236,9 @@ def launch_mysql(repo):
236
236
  server = repo / '.qoder' / 'scripts' / 'mysql_mcp_server.py'
237
237
  if not server.is_file():
238
238
  _log(f'mysql server 不存在: {server}')
239
- sys.exit(1)
239
+ _launch_disabled_mcp('qoder-mysql',
240
+ 'mysql_mcp_server.py 缺失。跑 npx @hupan56/wlkj update')
241
+ return
240
242
  # 优先: 从 config.yaml 读 (团队共享, clone 就有)
241
243
  cfg_path = repo / '.qoder' / 'config.yaml'
242
244
  mysql_cfg = {}