@hupan56/wlkj 2.7.12 → 3.0.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 (152) hide show
  1. package/bin/cli.js +344 -78
  2. package/package.json +29 -29
  3. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +23 -0
  4. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +23 -0
  5. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +1 -0
  6. package/templates/.qoder/learning/eval-history.jsonl +28 -0
  7. package/templates/data/index/wiki-index.json +8 -0
  8. package/templates/qoder/agents/insight-planning.md +1 -1
  9. package/templates/qoder/agents/insight-research.md +28 -15
  10. package/templates/qoder/commands/optional/wl-insight.md +159 -161
  11. package/templates/qoder/commands/optional/wl-report.md +4 -4
  12. package/templates/qoder/commands/optional/wl-status.md +2 -2
  13. package/templates/qoder/commands/wl-code.md +2 -2
  14. package/templates/qoder/commands/wl-design.md +2 -2
  15. package/templates/qoder/commands/wl-init.md +3 -3
  16. package/templates/qoder/commands/wl-prd.md +2 -2
  17. package/templates/qoder/commands/wl-req.md +43 -0
  18. package/templates/qoder/commands/wl-search.md +8 -8
  19. package/templates/qoder/commands/wl-task.md +17 -17
  20. package/templates/qoder/commands/wl-test.md +41 -15
  21. package/templates/qoder/config.yaml +17 -1
  22. package/templates/qoder/hooks/session-start.py +12 -22
  23. package/templates/qoder/nul +4 -0
  24. package/templates/qoder/rules/wl-pipeline.md +22 -48
  25. package/templates/qoder/scripts/README.md +139 -0
  26. package/templates/qoder/scripts/common/autotest_auth.py +109 -0
  27. package/templates/qoder/scripts/common/bootstrap.py +145 -0
  28. package/templates/qoder/scripts/common/check_publish.py +98 -0
  29. package/templates/qoder/scripts/common/cmd_registry.py +112 -0
  30. package/templates/qoder/scripts/common/config.py +187 -0
  31. package/templates/qoder/scripts/common/contract.py +317 -0
  32. package/templates/qoder/scripts/common/developer.py +2 -1
  33. package/templates/qoder/scripts/common/feishu.py +10 -9
  34. package/templates/qoder/scripts/common/guard.py +159 -0
  35. package/templates/qoder/scripts/common/identity.py +121 -2
  36. package/templates/qoder/scripts/common/kg_capabilities.py +182 -0
  37. package/templates/qoder/scripts/common/mcp_base.py +268 -0
  38. package/templates/qoder/scripts/common/paths.py +187 -1
  39. package/templates/qoder/scripts/common/result.py +223 -0
  40. package/templates/qoder/scripts/common/roles.py +60 -0
  41. package/templates/qoder/scripts/common/task_utils.py +21 -9
  42. package/templates/qoder/scripts/common/test_extract.py +115 -0
  43. package/templates/qoder/scripts/kg/__init__.py +11 -0
  44. package/templates/qoder/scripts/kg/build_entity_registry.py +196 -0
  45. package/templates/qoder/scripts/kg/build_relations.py +127 -0
  46. package/templates/qoder/scripts/{build_style_index.py → kg/build_style_index.py} +39 -10
  47. package/templates/qoder/scripts/kg/build_workflows.py +144 -0
  48. package/templates/qoder/scripts/{context_pack.py → kg/context_pack.py} +18 -11
  49. package/templates/qoder/scripts/{enrich_prompt.py → kg/enrich_prompt.py} +232 -226
  50. package/templates/qoder/scripts/{extract_api_params.py → kg/extract.py} +398 -246
  51. package/templates/qoder/scripts/{kg.py → kg/kg.py} +638 -708
  52. package/templates/qoder/scripts/{kg_build.py → kg/kg_build.py} +618 -612
  53. package/templates/qoder/scripts/{kg_build_db.py → kg/kg_build_db.py} +333 -327
  54. package/templates/qoder/scripts/{kg_duckdb.py → kg/kg_duckdb.py} +38 -37
  55. package/templates/qoder/scripts/{kg_incremental.py → kg/kg_incremental.py} +420 -393
  56. package/templates/qoder/scripts/{kg_link_db.py → kg/kg_link_db.py} +230 -224
  57. package/templates/qoder/scripts/{kg_semantic.py → kg/kg_semantic.py} +156 -150
  58. package/templates/qoder/scripts/kg/prefetch.py +359 -0
  59. package/templates/qoder/scripts/{search_index.py → kg/search_index.py} +70 -14
  60. package/templates/qoder/scripts/mcp/__init__.py +11 -0
  61. package/templates/qoder/scripts/{kg_mcp_server.py → mcp/kg_mcp_server.py} +77 -272
  62. package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → mcp/lanhu_stdio_wrapper.py} +125 -119
  63. package/templates/qoder/scripts/{check_mcp.py → mcp/mcp_doctor.py} +515 -298
  64. package/templates/qoder/scripts/{mcp_launcher.py → mcp/mcp_launcher.py} +442 -414
  65. package/templates/qoder/scripts/{mysql_mcp_server.py → mcp/mysql_mcp_server.py} +347 -396
  66. package/templates/qoder/scripts/{zentao_mcp_server.py → mcp/zentao_mcp_server.py} +384 -424
  67. package/templates/qoder/scripts/report/__init__.py +11 -0
  68. package/templates/qoder/scripts/{add_session.py → report/add_session.py} +250 -244
  69. package/templates/qoder/scripts/{archive_prd.py → report/archive_prd.py} +383 -377
  70. package/templates/qoder/scripts/{eval_prd.py → report/eval_prd.py} +73 -11
  71. package/templates/qoder/scripts/{export.py → report/export.py} +63 -0
  72. package/templates/qoder/scripts/{fill_prototype.py → report/fill_prototype.py} +6 -0
  73. package/templates/qoder/scripts/{gen_design_doc.py → report/gen_design_doc.py} +400 -394
  74. package/templates/qoder/scripts/{learn.py → report/learn.py} +152 -146
  75. package/templates/qoder/scripts/{learn_aggregate.py → report/learn_aggregate.py} +207 -201
  76. package/templates/qoder/scripts/{report.py → report/report.py} +287 -281
  77. package/templates/qoder/scripts/report/req.py +222 -0
  78. package/templates/qoder/scripts/report/role.py +33 -0
  79. package/templates/qoder/scripts/{status.py → report/status.py} +634 -628
  80. package/templates/qoder/scripts/setup/__init__.py +11 -0
  81. package/templates/qoder/scripts/setup/carriers.py +662 -0
  82. package/templates/qoder/scripts/{init_doctor.py → setup/init_doctor.py} +63 -26
  83. package/templates/qoder/scripts/{install_qoderwork.py → setup/install_qoderwork.py} +36 -26
  84. package/templates/qoder/scripts/{platform_doctor.py → setup/platform_doctor.py} +265 -259
  85. package/templates/qoder/scripts/{repo_root.py → setup/repo_root.py} +112 -106
  86. package/templates/qoder/scripts/{setup.py → setup/setup.py} +113 -4
  87. package/templates/qoder/scripts/{setup_lanhu.py → setup/setup_lanhu.py} +973 -963
  88. package/templates/qoder/scripts/task/__init__.py +11 -0
  89. package/templates/qoder/scripts/{git_sync.py → task/git_sync.py} +52 -31
  90. package/templates/qoder/scripts/{syncgate.py → task/syncgate.py} +6 -0
  91. package/templates/qoder/scripts/task/task.py +221 -0
  92. package/templates/qoder/scripts/task/task_lifecycle.py +596 -0
  93. package/templates/qoder/scripts/task/task_query.py +161 -0
  94. package/templates/qoder/scripts/task/task_relations.py +424 -0
  95. package/templates/qoder/scripts/{team_sync.py → task/team_sync.py} +93 -20
  96. package/templates/qoder/scripts/test/__init__.py +11 -0
  97. package/templates/qoder/scripts/{autotest.py → test/autotest.py} +1174 -1751
  98. package/templates/qoder/scripts/{autotest_batch.py → test/autotest_batch.py} +242 -224
  99. package/templates/qoder/scripts/test/autotest_data.py +675 -0
  100. package/templates/qoder/scripts/{autotest_run.py → test/autotest_run.py} +309 -297
  101. package/templates/qoder/scripts/{benchmark.py → test/benchmark.py} +6 -0
  102. package/templates/qoder/scripts/{kg_auto_login.py → test/kg_auto_login.py} +202 -196
  103. package/templates/qoder/scripts/{kg_test_runner.py → test/kg_test_runner.py} +7 -1
  104. package/templates/qoder/scripts/{page_probe.py → test/page_probe.py} +465 -459
  105. package/templates/qoder/scripts/wlkj.py +116 -0
  106. package/templates/qoder/settings.json +1 -10
  107. package/templates/qoder/skills/design-import/SKILL.md +226 -226
  108. package/templates/qoder/skills/design-review/SKILL.md +82 -82
  109. package/templates/qoder/skills/prd-generator/SKILL.md +26 -16
  110. package/templates/qoder/skills/prd-review/SKILL.md +5 -5
  111. package/templates/qoder/skills/prototype-generator/SKILL.md +256 -256
  112. package/templates/qoder/skills/spec-coder/SKILL.md +4 -4
  113. package/templates/qoder/skills/spec-generator/SKILL.md +4 -4
  114. package/templates/qoder/skills/test-generator/SKILL.md +5 -5
  115. package/templates/qoder/skills/wl-code/SKILL.md +4 -4
  116. package/templates/qoder/skills/wl-commit/SKILL.md +4 -4
  117. package/templates/qoder/skills/wl-design/SKILL.md +3 -3
  118. package/templates/qoder/skills/wl-init/SKILL.md +8 -8
  119. package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
  120. package/templates/qoder/skills/wl-prd-full/SKILL.md +6 -6
  121. package/templates/qoder/skills/wl-prd-quick/SKILL.md +6 -6
  122. package/templates/qoder/skills/wl-prd-review/SKILL.md +4 -4
  123. package/templates/qoder/skills/wl-report/SKILL.md +7 -7
  124. package/templates/qoder/skills/wl-search/SKILL.md +13 -13
  125. package/templates/qoder/skills/wl-spec/SKILL.md +5 -5
  126. package/templates/qoder/skills/wl-status/SKILL.md +5 -5
  127. package/templates/qoder/skills/wl-task/SKILL.md +6 -6
  128. package/templates/qoder/skills/wl-test/SKILL.md +102 -39
  129. package/templates/root/AGENTS.md +39 -40
  130. package/templates/qoder/hooks/inject-workflow-state.py +0 -169
  131. package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
  132. package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
  133. package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
  134. package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
  135. package/templates/qoder/scripts/check_carriers.py +0 -238
  136. package/templates/qoder/scripts/check_mcp_launch.py +0 -183
  137. package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
  138. package/templates/qoder/scripts/collect_prds.py +0 -31
  139. package/templates/qoder/scripts/common/mentions.py +0 -134
  140. package/templates/qoder/scripts/common/utf8.py +0 -38
  141. package/templates/qoder/scripts/extract_routes.py +0 -54
  142. package/templates/qoder/scripts/extract_routes_tree.py +0 -78
  143. package/templates/qoder/scripts/handoff.py +0 -22
  144. package/templates/qoder/scripts/init_developer.py +0 -76
  145. package/templates/qoder/scripts/parse_prds.py +0 -33
  146. package/templates/qoder/scripts/role.py +0 -51
  147. package/templates/qoder/scripts/sync_carriers.py +0 -259
  148. package/templates/qoder/scripts/task.py +0 -1261
  149. package/templates/qoder/scripts/workspace_init.py +0 -102
  150. package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
  151. package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
  152. /package/templates/qoder/scripts/{secure-ls.js → test/secure-ls.js} +0 -0
@@ -1,259 +1,265 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- platform_doctor.py - 跨平台环境自诊断 (任何 OS 跑, 输出环境真相)
5
-
6
- 为什么需要它:
7
- MAC-VERIFY.md 是人工清单, mac 同事跑完如果失败, 反馈信息往往不够定位。
8
- 本脚本自动收集所有跨平台关键信息, 一次跑完输出结构化报告,
9
- 同事直接把输出贴回来即可 —— 无需懂代码。
10
-
11
- 它在任何 OS (Windows/macOS/Linux) 上行为一致, 显式报告:
12
- - 平台 + 架构
13
- - python/python3/node/git 是否可用 + 真实路径
14
- - 工作流引擎是否完整 (关键脚本在不在)
15
- - MCP 各组件就绪状态
16
- - 角色配置
17
- - 发现的问题 + 修复建议
18
-
19
- Usage:
20
- python3 .qoder/scripts/platform_doctor.py
21
- # mac 上: python3 platform_doctor.py (从包目录跑也行)
22
- Exit: 0 = 健康, 1 = 有问题
23
- """
24
- import json
25
- import os
26
- import platform
27
- import shutil
28
- import subprocess
29
- import sys
30
- from pathlib import Path
31
-
32
- if sys.platform == 'win32':
33
- try:
34
- sys.stdout.reconfigure(encoding='utf-8')
35
- except (AttributeError, IOError):
36
- pass
37
-
38
- # 定位仓库根 (和 mcp_launcher 同样的多级回退)
39
- HERE = Path(__file__).resolve().parent
40
- BASE = None
41
- for candidate in [HERE.parent.parent, HERE.parent, HERE]:
42
- if (candidate / '.qoder' / 'scripts').is_dir():
43
- BASE = candidate
44
- break
45
- if BASE is None:
46
- BASE = HERE.parent.parent # 最佳猜测
47
-
48
- report = {'platform': {}, 'tools': {}, 'engine': {}, 'mcp': {},
49
- 'role': {}, 'issues': [], 'verdict': 'UNKNOWN'}
50
-
51
-
52
- def add_issue(level, msg, fix=None):
53
- report['issues'].append({'level': level, 'msg': msg, 'fix': fix or ''})
54
-
55
-
56
- def which(cmd):
57
- """找命令路径, 返回字符串或 None。"""
58
- return shutil.which(cmd)
59
-
60
-
61
- def run(cmd, timeout=8):
62
- """跑命令, 返回 (rc, output)。"""
63
- try:
64
- r = subprocess.run(cmd, capture_output=True, text=True,
65
- encoding='utf-8', errors='replace', timeout=timeout)
66
- return r.returncode, (r.stdout or '').strip()
67
- except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
68
- return 1, ''
69
-
70
-
71
- # ============================================================
72
- # 1. 平台信息
73
- # ============================================================
74
- report['platform'] = {
75
- 'sys_platform': sys.platform,
76
- 'platform_system': platform.system(),
77
- 'machine': platform.machine(),
78
- 'python_version': sys.version.split()[0],
79
- 'python_executable': sys.executable,
80
- }
81
- plat_name = platform.system()
82
- is_mac = sys.platform == 'darwin'
83
- is_win = sys.platform == 'win32'
84
- print('【平台】 %s %s (sys.platform=%s)' % (
85
- plat_name, platform.machine(), sys.platform))
86
- print(' Python: %s @ %s' % (sys.version.split()[0], sys.executable))
87
-
88
- # ============================================================
89
- # 2. 工具探测 (python/python3/node/git/brew)
90
- # ============================================================
91
- print('\n【工具探测】')
92
- for tool in ['python', 'python3', 'node', 'npm', 'npx', 'git']:
93
- if is_mac and tool == 'python':
94
- # mac 上 python 通常不存在, 这是正常的
95
- pass
96
- p = which(tool)
97
- ver = ''
98
- if p:
99
- if tool in ('python', 'python3'):
100
- rc, v = run([tool, '--version'])
101
- ver = v or '(版本未知)'
102
- elif tool in ('node',):
103
- rc, v = run([tool, '--version'])
104
- ver = v
105
- elif tool == 'git':
106
- rc, v = run([tool, '--version'])
107
- ver = v
108
- report['tools'][tool] = {'path': p, 'version': ver}
109
- status = '✓ %s' % ver if p else '✗ 未找到'
110
- print(' %-8s %s' % (tool, status))
111
-
112
- # 关键: python/python3 关系 (mac 验证核心)
113
- py = which('python')
114
- py3 = which('python3')
115
- if is_mac and not py and py3:
116
- print(' [mac 正常] python 不存在, python3 可用 (cli.js detectPyCmd 会选 python3)')
117
- elif not py and not py3:
118
- add_issue('CRITICAL', 'python python3 都没找到',
119
- 'mac: brew install python@3.12; win: 装 Python 勾 Add to PATH')
120
- elif not py3 and py:
121
- add_issue('WARN', '只有 python 没有 python3 (罕见, 一般不影响)')
122
-
123
- if is_mac:
124
- brew = which('brew')
125
- print(' brew: %s' % (brew or '✗ (装包用: https://brew.sh)'))
126
-
127
- # ============================================================
128
- # 3. 引擎完整性
129
- # ============================================================
130
- print('\n【工作流引擎】')
131
- scripts_dir = BASE / '.qoder' / 'scripts'
132
- key_scripts = [
133
- 'setup.py', 'kg.py', 'context_pack.py', 'search_index.py',
134
- 'mcp_launcher.py', 'install_qoderwork.py', 'role.py',
135
- 'common/pip_install.py', 'common/events.py', 'common/platform_guard.py',
136
- 'check_mcp.py', 'check_carriers.py',
137
- ]
138
- missing_scripts = []
139
- for s in key_scripts:
140
- exists = (scripts_dir / s).is_file()
141
- if not exists:
142
- missing_scripts.append(s)
143
- report['engine'][s] = exists
144
-
145
- if missing_scripts:
146
- add_issue('CRITICAL', '引擎脚本缺失: %s' % ', '.join(missing_scripts),
147
- '跑: npx @hupan56/wlkj update')
148
- print(' ✗ 缺失: %s' % ', '.join(missing_scripts))
149
- else:
150
- print(' ✓ %d 个关键脚本齐全' % len(key_scripts))
151
-
152
- # 引擎版本
153
- ver_file = BASE / '.qoder' / '.engine-version'
154
- if ver_file.is_file():
155
- ver = ver_file.read_text(encoding='utf-8').strip()
156
- report['engine']['version'] = ver
157
- print(' 引擎版本: %s' % ver)
158
-
159
- # ============================================================
160
- # 4. MCP 组件
161
- # ============================================================
162
- print('\n【MCP 组件】')
163
- # 4.1 mcp.json
164
- HOME = Path(os.environ.get('USERPROFILE') or os.path.expanduser('~'))
165
- mcp_file = HOME / '.qoderwork' / 'mcp.json'
166
- if mcp_file.is_file():
167
- try:
168
- cfg = json.loads(mcp_file.read_text(encoding='utf-8'))
169
- servers = list(cfg.get('mcpServers', {}).keys())
170
- report['mcp']['servers'] = servers
171
- print(' mcp.json: %d server (%s)' % (len(servers), ', '.join(servers)))
172
- except (ValueError, OSError):
173
- add_issue('WARN', 'mcp.json 解析失败', '重跑 install_qoderwork.py --mcp-only')
174
- print(' mcp.json: 解析失败')
175
- else:
176
- add_issue('WARN', 'mcp.json 不存在 (QoderWork 未配 MCP)',
177
- '跑: python .qoder/scripts/install_qoderwork.py --mcp-only')
178
- print(' mcp.json: 不存在')
179
-
180
- # 4.2 .repo-root 锚点
181
- anchor = HOME / '.qoderwork' / '.repo-root'
182
- if anchor.is_file():
183
- root_val = anchor.read_text(encoding='utf-8').strip()
184
- is_correct = (Path(root_val) / '.qoder' / 'scripts').is_dir()
185
- report['mcp']['repo_root'] = root_val
186
- if is_correct:
187
- print(' .repo-root: ✓ 指向 %s' % root_val)
188
- else:
189
- add_issue('CRITICAL', '.repo-root 指向错误目录: %s' % root_val,
190
- '跑: python install_qoderwork.py --mcp-only (会用 PROJECT_ROOT 修复)')
191
- print(' .repo-root: ✗ 指向错误 %s' % root_val)
192
- else:
193
- add_issue('WARN', '.repo-root 锚点不存在',
194
- '跑: install_qoderwork.py --mcp-only')
195
- print(' .repo-root: 不存在')
196
-
197
- # 4.3 duckdb (kg MCP 依赖)
198
- try:
199
- import duckdb
200
- print(' duckdb: 已装 (kg MCP 可用)')
201
- report['mcp']['duckdb'] = True
202
- except ImportError:
203
- add_issue('CRITICAL', 'duckdb 未装 (kg MCP 会崩)',
204
- 'pip install duckdb 或 npx wlkj install-env')
205
- print(' duckdb: ✗ 未装')
206
-
207
- # ============================================================
208
- # 5. 角色配置
209
- # ============================================================
210
- print('\n【角色配置】')
211
- dev_file = BASE / '.qoder' / '.developer'
212
- if dev_file.is_file():
213
- lines = dev_file.read_text(encoding='utf-8').split('\n')
214
- name_l = next((l for l in lines if l.startswith('name=')), '')
215
- role_l = next((l for l in lines if l.startswith('role=')), '')
216
- name = name_l.split('=', 1)[1].strip() if name_l else '?'
217
- role = role_l.split('=', 1)[1].strip() if role_l else 'pm(默认)'
218
- report['role'] = {'name': name, 'role': role}
219
- print(' 开发者: %s' % name)
220
- print(' 角色: %s' % role)
221
- if not role_l:
222
- add_issue('INFO', '角色未显式设置 (默认 pm)',
223
- '重跑 init 选角色: npx wlkj init %s <pm|design|dev|test|admin>' % name)
224
- else:
225
- add_issue('CRITICAL', '.developer 不存在 (未初始化)',
226
- '跑: npx @hupan56/wlkj init <名字> <角色>')
227
- print(' 未初始化!')
228
-
229
- # ============================================================
230
- # 6. 总结
231
- # ============================================================
232
- criticals = [i for i in report['issues'] if i['level'] == 'CRITICAL']
233
- warns = [i for i in report['issues'] if i['level'] == 'WARN']
234
-
235
- print('\n' + '=' * 56)
236
- if criticals:
237
- print('发现 %d 个严重问题:' % len(criticals))
238
- for i in criticals:
239
- print(' 🔴 %s' % i['msg'])
240
- if i['fix']:
241
- print(' 修复: %s' % i['fix'])
242
- report['verdict'] = 'BROKEN'
243
- elif warns:
244
- print('发现 %d 个警告 (非阻塞, 但建议处理):' % len(warns))
245
- for i in warns:
246
- print(' 🟡 %s' % i['msg'])
247
- if i['fix']:
248
- print(' 建议: %s' % i['fix'])
249
- report['verdict'] = 'OK_WITH_WARNINGS'
250
- else:
251
- print('✓ 环境健康, 工作流可用。')
252
- report['verdict'] = 'HEALTHY'
253
- print('=' * 56)
254
-
255
- # 机器可读报告 (便于同事贴回来)
256
- print('\n--- 可粘贴报告 (JSON) ---')
257
- print(json.dumps(report, ensure_ascii=False, indent=2))
258
-
259
- sys.exit(1 if criticals else 0)
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
4
+ import os as _o, sys as _s
5
+ _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
6
+ if _cp not in _s.path: _s.path.insert(0, _cp)
7
+ from bootstrap import setup; setup()
8
+
9
+ """
10
+ platform_doctor.py - 跨平台环境自诊断 (任何 OS 跑, 输出环境真相)
11
+
12
+ 为什么需要它:
13
+ MAC-VERIFY.md 是人工清单, mac 同事跑完如果失败, 反馈信息往往不够定位。
14
+ 本脚本自动收集所有跨平台关键信息, 一次跑完输出结构化报告,
15
+ 同事直接把输出贴回来即可 —— 无需懂代码。
16
+
17
+ 它在任何 OS (Windows/macOS/Linux) 上行为一致, 显式报告:
18
+ - 平台 + 架构
19
+ - python/python3/node/git 是否可用 + 真实路径
20
+ - 工作流引擎是否完整 (关键脚本在不在)
21
+ - MCP 各组件就绪状态
22
+ - 角色配置
23
+ - 发现的问题 + 修复建议
24
+
25
+ Usage:
26
+ python3 .qoder/scripts/platform_doctor.py
27
+ # 或 mac 上: python3 platform_doctor.py (从包目录跑也行)
28
+ Exit: 0 = 健康, 1 = 有问题
29
+ """
30
+ import json
31
+ import os
32
+ import platform
33
+ import shutil
34
+ import subprocess
35
+ import sys
36
+ from pathlib import Path
37
+
38
+ if sys.platform == 'win32':
39
+ try:
40
+ sys.stdout.reconfigure(encoding='utf-8')
41
+ except (AttributeError, IOError):
42
+ pass
43
+
44
+ # 定位仓库根 (和 mcp_launcher 同样的多级回退)
45
+ HERE = Path(__file__).resolve().parent
46
+ BASE = None
47
+ for candidate in [HERE.parent.parent, HERE.parent, HERE]:
48
+ if (candidate / '.qoder' / 'scripts').is_dir():
49
+ BASE = candidate
50
+ break
51
+ if BASE is None:
52
+ BASE = HERE.parent.parent # 最佳猜测
53
+
54
+ report = {'platform': {}, 'tools': {}, 'engine': {}, 'mcp': {},
55
+ 'role': {}, 'issues': [], 'verdict': 'UNKNOWN'}
56
+
57
+
58
+ def add_issue(level, msg, fix=None):
59
+ report['issues'].append({'level': level, 'msg': msg, 'fix': fix or ''})
60
+
61
+
62
+ def which(cmd):
63
+ """找命令路径, 返回字符串或 None。"""
64
+ return shutil.which(cmd)
65
+
66
+
67
+ def run(cmd, timeout=8):
68
+ """跑命令, 返回 (rc, 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 '').strip()
73
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
74
+ return 1, ''
75
+
76
+
77
+ # ============================================================
78
+ # 1. 平台信息
79
+ # ============================================================
80
+ report['platform'] = {
81
+ 'sys_platform': sys.platform,
82
+ 'platform_system': platform.system(),
83
+ 'machine': platform.machine(),
84
+ 'python_version': sys.version.split()[0],
85
+ 'python_executable': sys.executable,
86
+ }
87
+ plat_name = platform.system()
88
+ is_mac = sys.platform == 'darwin'
89
+ is_win = sys.platform == 'win32'
90
+ print('【平台】 %s %s (sys.platform=%s)' % (
91
+ plat_name, platform.machine(), sys.platform))
92
+ print(' Python: %s @ %s' % (sys.version.split()[0], sys.executable))
93
+
94
+ # ============================================================
95
+ # 2. 工具探测 (python/python3/node/git/brew)
96
+ # ============================================================
97
+ print('\n【工具探测】')
98
+ for tool in ['python', 'python3', 'node', 'npm', 'npx', 'git']:
99
+ if is_mac and tool == 'python':
100
+ # mac python 通常不存在, 这是正常的
101
+ pass
102
+ p = which(tool)
103
+ ver = ''
104
+ if p:
105
+ if tool in ('python', 'python3'):
106
+ rc, v = run([tool, '--version'])
107
+ ver = v or '(版本未知)'
108
+ elif tool in ('node',):
109
+ rc, v = run([tool, '--version'])
110
+ ver = v
111
+ elif tool == 'git':
112
+ rc, v = run([tool, '--version'])
113
+ ver = v
114
+ report['tools'][tool] = {'path': p, 'version': ver}
115
+ status = '✓ %s' % ver if p else '✗ 未找到'
116
+ print(' %-8s %s' % (tool, status))
117
+
118
+ # 关键: python/python3 关系 (mac 验证核心)
119
+ py = which('python')
120
+ py3 = which('python3')
121
+ if is_mac and not py and py3:
122
+ print(' [mac 正常] python 不存在, python3 可用 (cli.js detectPyCmd 会选 python3)')
123
+ elif not py and not py3:
124
+ add_issue('CRITICAL', 'python 和 python3 都没找到',
125
+ 'mac: brew install python@3.12; win: Python Add to PATH')
126
+ elif not py3 and py:
127
+ add_issue('WARN', '只有 python 没有 python3 (罕见, 一般不影响)')
128
+
129
+ if is_mac:
130
+ brew = which('brew')
131
+ print(' brew: %s' % (brew or ' (装包用: https://brew.sh)'))
132
+
133
+ # ============================================================
134
+ # 3. 引擎完整性
135
+ # ============================================================
136
+ print('\n【工作流引擎】')
137
+ scripts_dir = BASE / '.qoder' / 'scripts'
138
+ key_scripts = [
139
+ 'setup.py', 'kg.py', 'context_pack.py', 'search_index.py',
140
+ 'mcp_launcher.py', 'install_qoderwork.py', 'role.py',
141
+ 'common/pip_install.py', 'common/events.py', 'common/platform_guard.py',
142
+ 'check_mcp.py', 'check_carriers.py',
143
+ ]
144
+ missing_scripts = []
145
+ for s in key_scripts:
146
+ exists = (scripts_dir / s).is_file()
147
+ if not exists:
148
+ missing_scripts.append(s)
149
+ report['engine'][s] = exists
150
+
151
+ if missing_scripts:
152
+ add_issue('CRITICAL', '引擎脚本缺失: %s' % ', '.join(missing_scripts),
153
+ '跑: npx @hupan56/wlkj update')
154
+ print(' ✗ 缺失: %s' % ', '.join(missing_scripts))
155
+ else:
156
+ print(' ✓ %d 个关键脚本齐全' % len(key_scripts))
157
+
158
+ # 引擎版本
159
+ ver_file = BASE / '.qoder' / '.engine-version'
160
+ if ver_file.is_file():
161
+ ver = ver_file.read_text(encoding='utf-8').strip()
162
+ report['engine']['version'] = ver
163
+ print(' 引擎版本: %s' % ver)
164
+
165
+ # ============================================================
166
+ # 4. MCP 组件
167
+ # ============================================================
168
+ print('\n【MCP 组件】')
169
+ # 4.1 mcp.json
170
+ HOME = Path(os.environ.get('USERPROFILE') or os.path.expanduser('~'))
171
+ mcp_file = HOME / '.qoderwork' / 'mcp.json'
172
+ if mcp_file.is_file():
173
+ try:
174
+ cfg = json.loads(mcp_file.read_text(encoding='utf-8'))
175
+ servers = list(cfg.get('mcpServers', {}).keys())
176
+ report['mcp']['servers'] = servers
177
+ print(' mcp.json: %d server (%s)' % (len(servers), ', '.join(servers)))
178
+ except (ValueError, OSError):
179
+ add_issue('WARN', 'mcp.json 解析失败', '重跑 install_qoderwork.py --mcp-only')
180
+ print(' mcp.json: 解析失败')
181
+ else:
182
+ add_issue('WARN', 'mcp.json 不存在 (QoderWork 未配 MCP)',
183
+ '跑: python .qoder/scripts/install_qoderwork.py --mcp-only')
184
+ print(' mcp.json: 不存在')
185
+
186
+ # 4.2 .repo-root 锚点
187
+ anchor = HOME / '.qoderwork' / '.repo-root'
188
+ if anchor.is_file():
189
+ root_val = anchor.read_text(encoding='utf-8').strip()
190
+ is_correct = (Path(root_val) / '.qoder' / 'scripts').is_dir()
191
+ report['mcp']['repo_root'] = root_val
192
+ if is_correct:
193
+ print(' .repo-root: ✓ 指向 %s' % root_val)
194
+ else:
195
+ add_issue('CRITICAL', '.repo-root 指向错误目录: %s' % root_val,
196
+ '跑: python install_qoderwork.py --mcp-only (会用 PROJECT_ROOT 修复)')
197
+ print(' .repo-root: 指向错误 %s' % root_val)
198
+ else:
199
+ add_issue('WARN', '.repo-root 锚点不存在',
200
+ '跑: install_qoderwork.py --mcp-only')
201
+ print(' .repo-root: 不存在')
202
+
203
+ # 4.3 duckdb (kg MCP 依赖)
204
+ try:
205
+ import duckdb
206
+ print(' duckdb: ✓ 已装 (kg MCP 可用)')
207
+ report['mcp']['duckdb'] = True
208
+ except ImportError:
209
+ add_issue('CRITICAL', 'duckdb 未装 (kg MCP 会崩)',
210
+ 'pip install duckdb 或 npx wlkj install-env')
211
+ print(' duckdb: 未装')
212
+
213
+ # ============================================================
214
+ # 5. 角色配置
215
+ # ============================================================
216
+ print('\n【角色配置】')
217
+ dev_file = BASE / '.qoder' / '.developer'
218
+ if dev_file.is_file():
219
+ lines = dev_file.read_text(encoding='utf-8').split('\n')
220
+ name_l = next((l for l in lines if l.startswith('name=')), '')
221
+ role_l = next((l for l in lines if l.startswith('role=')), '')
222
+ name = name_l.split('=', 1)[1].strip() if name_l else '?'
223
+ role = role_l.split('=', 1)[1].strip() if role_l else 'pm(默认)'
224
+ report['role'] = {'name': name, 'role': role}
225
+ print(' 开发者: %s' % name)
226
+ print(' 角色: %s' % role)
227
+ if not role_l:
228
+ add_issue('INFO', '角色未显式设置 (默认 pm)',
229
+ '重跑 init 选角色: npx wlkj init %s <pm|design|dev|test|admin>' % name)
230
+ else:
231
+ add_issue('CRITICAL', '.developer 不存在 (未初始化)',
232
+ '跑: npx @hupan56/wlkj init <名字> <角色>')
233
+ print(' 未初始化!')
234
+
235
+ # ============================================================
236
+ # 6. 总结
237
+ # ============================================================
238
+ criticals = [i for i in report['issues'] if i['level'] == 'CRITICAL']
239
+ warns = [i for i in report['issues'] if i['level'] == 'WARN']
240
+
241
+ print('\n' + '=' * 56)
242
+ if criticals:
243
+ print('发现 %d 个严重问题:' % len(criticals))
244
+ for i in criticals:
245
+ print(' 🔴 %s' % i['msg'])
246
+ if i['fix']:
247
+ print(' 修复: %s' % i['fix'])
248
+ report['verdict'] = 'BROKEN'
249
+ elif warns:
250
+ print('发现 %d 个警告 (非阻塞, 但建议处理):' % len(warns))
251
+ for i in warns:
252
+ print(' 🟡 %s' % i['msg'])
253
+ if i['fix']:
254
+ print(' 建议: %s' % i['fix'])
255
+ report['verdict'] = 'OK_WITH_WARNINGS'
256
+ else:
257
+ print('✓ 环境健康, 工作流可用。')
258
+ report['verdict'] = 'HEALTHY'
259
+ print('=' * 56)
260
+
261
+ # 机器可读报告 (便于同事贴回来)
262
+ print('\n--- 可粘贴报告 (JSON) ---')
263
+ print(json.dumps(report, ensure_ascii=False, indent=2))
264
+
265
+ sys.exit(1 if criticals else 0)