@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,238 +0,0 @@
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())
@@ -1,183 +0,0 @@
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()
@@ -1,166 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- check_qoderwork_consistency.py — 防退化检查:命令/skills 文件不得用裸相对路径调脚本。
5
-
6
- 背景:
7
- QoderWork 桌面端工作目录是临时会话目录(workspace/xxx), 不是仓库根。
8
- 命令/技能文件里写 `python .qoder/scripts/xxx.py` → 在 QoderWork 下找不到脚本 →
9
- Shell 直接报错, Python 根本没启动。这就是"ZCode 行、QoderWork 不行"的根因。
10
-
11
- 统一规约: 所有脚本调用必须用仓库根定位, 二选一:
12
- ① python "$R/.qoder/scripts/xxx.py" ... (R = repo_root.py 输出)
13
- ② cd "$R" && python .qoder/scripts/xxx.py ... (cd 到仓库根后相对路径)
14
-
15
- 本脚本扫 .qoder/commands/**/*.md 和 .qoder/skills/**/SKILL.md,
16
- 找出违反规约的裸相对路径调用, 退出码非 0 表示有退化。
17
- 可挂 pre-commit / CI。
18
-
19
- Usage:
20
- python .qoder/scripts/check_qoderwork_consistency.py
21
- python .qoder/scripts/check_qoderwork_consistency.py --strict # 警告也算失败
22
- Exit:
23
- 0 = 通过(无裸相对路径调用, 或仅有 --strict 才算失败的警告)
24
- 1 = 发现裸相对路径调用(退化)
25
- """
26
- import argparse
27
- import os
28
- import re
29
- import sys
30
-
31
- if sys.platform == 'win32':
32
- try:
33
- sys.stdout.reconfigure(encoding='utf-8')
34
- except (AttributeError, IOError):
35
- pass
36
-
37
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
38
- from common.paths import get_repo_root # noqa: E402
39
-
40
- # 扫描目标: commands 全部 .md + skills 的 SKILL.md
41
- SCAN_GLOBS = [
42
- ('commands', '.qoder/commands', '*.md'),
43
- ('skills', '.qoder/skills', 'SKILL.md'),
44
- ]
45
-
46
- # 裸相对路径调用: `python .qoder/scripts/xxx` 但前面没有 cd "$R" 或 "$R/ 前缀
47
- # 匹配 `python .qoder/scripts/` 或 `python3 .qoder/scripts/`, 不带 $R 引号包裹
48
- BARE_PAT = re.compile(r'(?<![\"\$])\bpython3?\s+\.qoder/scripts/([A-Za-z0-9_./-]+)')
49
-
50
- # 已安全模式: cd "$R" && python .qoder/scripts/...
51
- SAFE_CD_PAT = re.compile(r'cd\s+["\']?\$R["\']?\s*&&\s*python3?\s+\.qoder/scripts/')
52
-
53
- # 仓库根定位块存在的标志
54
- ROOT_LOCATOR_PAT = re.compile(r'repo_root\.py|R=\$\(python.*repo_root')
55
-
56
- # 在 install_qoderwork.py 提示行里的相对路径(说明性文字, 非真实调用), 豁免
57
- HINT_CTX = 'install_qoderwork.py'
58
-
59
-
60
- def scan_file(path, strict):
61
- """扫一个文件, 返回 (errors, warnings, root_block_present)。
62
- errors = 裸相对路径真实调用(必修); warnings = 无 R 块等(建议修)。
63
- """
64
- errors = []
65
- warnings = []
66
- try:
67
- with open(path, encoding='utf-8') as f:
68
- lines = f.readlines()
69
- except (OSError, IOError):
70
- return errors, warnings, True
71
-
72
- root_block = bool(ROOT_LOCATOR_PAT.search(''.join(lines)))
73
-
74
- for i, line in enumerate(lines, 1):
75
- # 豁免: 说明性文字里提到 install_qoderwork.py 的相对路径(这是"找不到时跑这个"的指引)
76
- if HINT_CTX in line:
77
- continue
78
- # 豁免: 已安全的 cd "$R" && python 模式
79
- if SAFE_CD_PAT.search(line):
80
- continue
81
- # 找裸相对路径
82
- for m in BARE_PAT.finditer(line):
83
- script = m.group(1)
84
- # 过滤掉明显是文字描述(行里既无 bash 代码块上下文也无命令感)
85
- # 简化: 只要匹配到就算(宁可误报让 reviewer 看一眼)
86
- errors.append((i, line.rstrip(), script))
87
-
88
- # 无 R 块但文件里有 $R 引用 → 警告
89
- if not root_block and '$R' in ''.join(lines):
90
- warnings.append((0, '(无 R 定位块, 但文件里用了 $R)', ''))
91
-
92
- return errors, warnings, root_block
93
-
94
-
95
- def main():
96
- ap = argparse.ArgumentParser(description='检查命令/skills 是否含裸相对路径(防 ZCode/QoderWork 割裂)')
97
- ap.add_argument('--strict', action='store_true', help='警告也算失败')
98
- args = ap.parse_args()
99
-
100
- repo = get_repo_root()
101
- total_errors = 0
102
- total_warnings = 0
103
- files_with_errors = []
104
- files_with_warnings = []
105
-
106
- for label, rel_dir, pattern in SCAN_GLOBS:
107
- scan_dir = repo / rel_dir
108
- if not scan_dir.is_dir():
109
- continue
110
- for dirpath, dirnames, filenames in os.walk(str(scan_dir)):
111
- for fn in filenames:
112
- import fnmatch
113
- if not fnmatch.fnmatch(fn, pattern):
114
- continue
115
- path = os.path.join(dirpath, fn)
116
- errors, warnings, root_block = scan_file(path, args.strict)
117
- rel = os.path.relpath(path, str(repo))
118
- if errors:
119
- files_with_errors.append((rel, errors))
120
- total_errors += len(errors)
121
- if warnings:
122
- files_with_warnings.append((rel, warnings))
123
- total_warnings += len(warnings)
124
-
125
- # 输出
126
- print('=' * 64)
127
- print('QoderWork 一致性检查 (防 ZCode 行 / QoderWork 挂 的割裂)')
128
- print('=' * 64)
129
-
130
- if files_with_errors:
131
- print()
132
- print('🔴 裸相对路径调用 (QoderWork 必挂, 必修):')
133
- for rel, errs in files_with_errors:
134
- print(' ' + rel)
135
- for lineno, line, script in errs:
136
- snippet = line.strip()[:80]
137
- print(' L%d: %s' % (lineno, snippet))
138
-
139
- if files_with_warnings:
140
- print()
141
- print('🟡 警告 (建议修, --strict 才算失败):')
142
- for rel, ws in files_with_warnings:
143
- print(' ' + rel)
144
- for lineno, line, script in ws:
145
- print(' %s' % line)
146
-
147
- if not files_with_errors and not files_with_warnings:
148
- print('✅ 全部通过: 命令/skills 无裸相对路径调用, ZCode ≡ QoderWork 一致。')
149
-
150
- print('-' * 64)
151
- print('统计: 错误 %d (文件 %d) / 警告 %d (文件 %d)'
152
- % (total_errors, len(files_with_errors), total_warnings, len(files_with_warnings)))
153
- print()
154
- print('规约: 脚本调用必须二选一:')
155
- print(' ① python "$R/.qoder/scripts/xxx.py" ... (R = repo_root.py 输出)')
156
- print(' ② cd "$R" && python .qoder/scripts/xxx.py ...')
157
-
158
- if total_errors > 0:
159
- return 1
160
- if args.strict and total_warnings > 0:
161
- return 1
162
- return 0
163
-
164
-
165
- if __name__ == '__main__':
166
- sys.exit(main())
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Collect PRDs from all user workspaces to data/docs/prd/
5
-
6
- THIN WRAPPER: the actual logic lives in git_sync.py (single source of
7
- truth). This entry point is kept for backwards compatibility.
8
-
9
- Usage:
10
- python collect_prds.py # same as the collect step of: python git_sync.py --prd-only
11
- """
12
-
13
- import os
14
- import sys
15
-
16
- # UTF-8 stdio (防御性: stdout 被捕获时不崩溃)
17
- try:
18
- sys.stdout.reconfigure(encoding='utf-8', errors='replace')
19
- except (AttributeError, TypeError, OSError, IOError):
20
- try:
21
- sys.stdout.reconfigure(encoding='utf-8')
22
- except Exception:
23
- pass
24
-
25
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
26
-
27
- from git_sync import collect_prds, FAILURES
28
-
29
- if __name__ == '__main__':
30
- collect_prds()
31
- sys.exit(1 if FAILURES else 0)