@hupan56/wlkj 2.7.12 → 3.1.2
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 +1435 -1107
- package/package.json +29 -29
- package/templates/qoder/agents/insight-planning.md +1 -1
- package/templates/qoder/agents/insight-research.md +28 -15
- package/templates/qoder/commands/optional/wl-insight.md +161 -161
- package/templates/qoder/commands/optional/wl-report.md +20 -39
- package/templates/qoder/commands/optional/wl-status.md +4 -4
- package/templates/qoder/commands/wl-code.md +2 -2
- package/templates/qoder/commands/wl-design.md +6 -6
- package/templates/qoder/commands/wl-init.md +3 -3
- package/templates/qoder/commands/wl-prd.md +26 -13
- package/templates/qoder/commands/wl-req.md +43 -0
- package/templates/qoder/commands/wl-search.md +78 -47
- package/templates/qoder/commands/wl-task.md +343 -40
- package/templates/qoder/commands/wl-test.md +43 -17
- package/templates/qoder/config.yaml +52 -14
- package/templates/qoder/hooks/post-tool-use.py +181 -0
- package/templates/qoder/hooks/session-start.py +94 -23
- package/templates/qoder/hooks/stop-eval.py +207 -0
- package/templates/qoder/hooks/user-prompt-submit.py +139 -0
- package/templates/qoder/rules/wl-pipeline.md +85 -49
- package/templates/qoder/scripts/README.md +102 -0
- package/templates/qoder/scripts/capability/__init__.py +26 -0
- package/templates/qoder/scripts/capability/__main__.py +72 -0
- package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
- package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
- package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
- package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
- package/templates/qoder/scripts/capability/caps/context.py +36 -0
- package/templates/qoder/scripts/capability/caps/cron.py +50 -0
- package/templates/qoder/scripts/capability/caps/identity.py +43 -0
- package/templates/qoder/scripts/capability/caps/memory.py +71 -0
- package/templates/qoder/scripts/capability/caps/notify.py +41 -0
- package/templates/qoder/scripts/capability/caps/present.py +48 -0
- package/templates/qoder/scripts/capability/caps/repo.py +31 -0
- package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
- package/templates/qoder/scripts/capability/chain.py +92 -0
- package/templates/qoder/scripts/capability/memory_chain.py +41 -0
- package/templates/qoder/scripts/capability/registry.py +246 -0
- package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
- package/templates/qoder/scripts/capability/smoke_test.py +211 -0
- package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
- package/templates/qoder/scripts/deployment/setup/__init__.py +11 -0
- package/templates/qoder/scripts/deployment/setup/carriers.py +669 -0
- package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
- package/templates/qoder/scripts/{init_doctor.py → deployment/setup/init_doctor.py} +100 -39
- package/templates/qoder/scripts/{install_qoderwork.py → deployment/setup/install_qoderwork.py} +48 -30
- package/templates/qoder/scripts/{platform_doctor.py → deployment/setup/platform_doctor.py} +271 -259
- package/templates/qoder/scripts/{repo_root.py → deployment/setup/repo_root.py} +9 -2
- package/templates/qoder/scripts/{setup.py → deployment/setup/setup.py} +143 -14
- package/templates/qoder/scripts/{setup_lanhu.py → deployment/setup/setup_lanhu.py} +979 -963
- package/templates/qoder/scripts/domain/design/__init__.py +0 -0
- package/templates/qoder/scripts/{fill_prototype.py → domain/design/fill_prototype.py} +17 -5
- package/templates/qoder/scripts/{gen_design_doc.py → domain/design/gen_design_doc.py} +406 -394
- package/templates/qoder/scripts/domain/kg/__init__.py +11 -0
- package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
- package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +201 -0
- package/templates/qoder/scripts/domain/kg/build/build_relations.py +132 -0
- package/templates/qoder/scripts/{build_style_index.py → domain/kg/build/build_style_index.py} +50 -15
- package/templates/qoder/scripts/domain/kg/build/build_workflows.py +149 -0
- package/templates/qoder/scripts/{kg_build.py → domain/kg/build/kg_build.py} +685 -612
- package/templates/qoder/scripts/{kg_build_db.py → domain/kg/build/kg_build_db.py} +338 -327
- package/templates/qoder/scripts/{kg_incremental.py → domain/kg/build/kg_incremental.py} +425 -393
- package/templates/qoder/scripts/{learn_aggregate.py → domain/kg/build/learn_aggregate.py} +212 -201
- package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
- package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
- package/templates/qoder/scripts/domain/kg/extract/test_extract.py +115 -0
- package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
- package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
- package/templates/qoder/scripts/{kg_link_db.py → domain/kg/graph/kg_link_db.py} +235 -224
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
- package/templates/qoder/scripts/{kg.py → domain/kg/kg.py} +871 -708
- package/templates/qoder/scripts/domain/kg/kg_capabilities.py +182 -0
- package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
- package/templates/qoder/scripts/{context_pack.py → domain/kg/search/context_pack.py} +45 -17
- package/templates/qoder/scripts/{enrich_prompt.py → domain/kg/search/enrich_prompt.py} +238 -226
- package/templates/qoder/scripts/domain/kg/search/prefetch.py +384 -0
- package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
- package/templates/qoder/scripts/{search_index.py → domain/kg/search/search_index.py} +210 -88
- package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
- package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
- package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
- package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
- package/templates/qoder/scripts/{kg_duckdb.py → domain/kg/storage/kg_duckdb.py} +70 -45
- package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
- package/templates/qoder/scripts/{learn.py → domain/learning/learn.py} +157 -146
- package/templates/qoder/scripts/domain/report/__init__.py +11 -0
- package/templates/qoder/scripts/{add_session.py → domain/report/add_session.py} +256 -244
- package/templates/qoder/scripts/{export.py → domain/report/export.py} +72 -4
- package/templates/qoder/scripts/{report.py → domain/report/report.py} +292 -281
- package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
- package/templates/qoder/scripts/domain/report/role.py +39 -0
- package/templates/qoder/scripts/{status.py → domain/report/status.py} +737 -628
- package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
- package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
- package/templates/qoder/scripts/{archive_prd.py → domain/requirement/archive_prd.py} +389 -377
- package/templates/qoder/scripts/domain/requirement/req.py +228 -0
- package/templates/qoder/scripts/domain/task/__init__.py +11 -0
- package/templates/qoder/scripts/{git_sync.py → domain/task/git_sync.py} +90 -50
- package/templates/qoder/scripts/{syncgate.py → domain/task/syncgate.py} +18 -7
- package/templates/qoder/scripts/domain/task/task.py +229 -0
- package/templates/qoder/scripts/domain/task/task_lifecycle.py +606 -0
- package/templates/qoder/scripts/domain/task/task_query.py +162 -0
- package/templates/qoder/scripts/domain/task/task_relations.py +425 -0
- package/templates/qoder/scripts/{team_sync.py → domain/task/team_sync.py} +101 -23
- package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
- package/templates/qoder/scripts/foundation/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/bootstrap.py +145 -0
- package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/core/cmd_registry.py +112 -0
- package/templates/qoder/scripts/foundation/core/config.py +187 -0
- package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -400
- package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/data/contract.py +317 -0
- package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
- package/templates/qoder/scripts/foundation/data/result.py +223 -0
- package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -392
- package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/identity/check_publish.py +103 -0
- package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -238
- package/templates/qoder/scripts/foundation/identity/guard.py +159 -0
- package/templates/qoder/scripts/{common → foundation/identity}/identity.py +132 -9
- package/templates/qoder/scripts/foundation/identity/roles.py +60 -0
- package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
- package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +11 -10
- package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
- package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
- package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
- package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
- package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
- package/templates/qoder/scripts/foundation/protocol/mcp_base.py +268 -0
- package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
- package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
- package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
- package/templates/qoder/scripts/orchestration/__init__.py +0 -0
- package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
- package/templates/qoder/scripts/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/protocol/browser/README.md +207 -0
- package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
- package/templates/qoder/scripts/protocol/browser/config.env +9 -0
- package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
- package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
- package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
- package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
- package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
- package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
- package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
- package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
- package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
- package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
- package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
- package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → protocol/mcp/lanhu_stdio_wrapper.py} +131 -119
- package/templates/qoder/scripts/protocol/mcp/mcp_doctor.py +548 -0
- package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
- package/templates/qoder/scripts/protocol/mcp/mysql_mcp_server.py +461 -0
- package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
- package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
- package/templates/qoder/scripts/protocol/transports/base.py +87 -0
- package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
- package/templates/qoder/scripts/protocol/transports/http.py +141 -0
- package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
- package/templates/qoder/scripts/run_weekly_update.bat +27 -18
- package/templates/qoder/scripts/run_weekly_update.sh +22 -13
- package/templates/qoder/scripts/validation/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
- package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
- package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
- package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
- package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
- package/templates/qoder/scripts/{eval_prd.py → validation/metrics/eval_prd.py} +84 -15
- package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
- package/templates/qoder/scripts/validation/test/__init__.py +11 -0
- package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
- package/templates/qoder/scripts/{autotest.py → validation/test/autotest.py} +1234 -1751
- package/templates/qoder/scripts/validation/test/autotest_auth.py +109 -0
- package/templates/qoder/scripts/{autotest_batch.py → validation/test/autotest_batch.py} +255 -224
- package/templates/qoder/scripts/validation/test/autotest_data.py +680 -0
- package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
- package/templates/qoder/scripts/{autotest_run.py → validation/test/autotest_run.py} +323 -297
- package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
- package/templates/qoder/scripts/{benchmark.py → validation/test/benchmark.py} +17 -5
- package/templates/qoder/scripts/{kg_auto_login.py → validation/test/kg_auto_login.py} +208 -196
- package/templates/qoder/scripts/{kg_test_runner.py → validation/test/kg_test_runner.py} +13 -2
- package/templates/qoder/scripts/{page_probe.py → validation/test/page_probe.py} +470 -459
- package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
- package/templates/qoder/settings.json +23 -2
- package/templates/qoder/skills/design-import/SKILL.md +235 -226
- package/templates/qoder/skills/design-review/SKILL.md +91 -82
- package/templates/qoder/skills/prd-generator/SKILL.md +38 -24
- package/templates/qoder/skills/prd-review/SKILL.md +8 -6
- package/templates/qoder/skills/prototype-generator/SKILL.md +296 -247
- package/templates/qoder/skills/spec-coder/SKILL.md +24 -15
- package/templates/qoder/skills/spec-generator/SKILL.md +24 -12
- package/templates/qoder/skills/test-generator/SKILL.md +9 -7
- package/templates/qoder/skills/wl-code/SKILL.md +25 -13
- package/templates/qoder/skills/wl-commit/SKILL.md +10 -9
- package/templates/qoder/skills/wl-design/SKILL.md +7 -5
- package/templates/qoder/skills/wl-init/SKILL.md +8 -8
- package/templates/qoder/skills/wl-insight/SKILL.md +25 -12
- package/templates/qoder/skills/wl-prd-full/SKILL.md +59 -8
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +7 -7
- package/templates/qoder/skills/wl-prd-review/SKILL.md +18 -6
- package/templates/qoder/skills/wl-report/SKILL.md +27 -25
- package/templates/qoder/skills/wl-search/SKILL.md +151 -80
- package/templates/qoder/skills/wl-spec/SKILL.md +9 -7
- package/templates/qoder/skills/wl-status/SKILL.md +50 -18
- package/templates/qoder/skills/wl-task/SKILL.md +94 -12
- package/templates/qoder/skills/wl-test/SKILL.md +154 -45
- package/templates/qoder/templates/prd-full-template.md +7 -0
- package/templates/root/AGENTS.md +275 -237
- package/templates/root/requirements.txt +3 -0
- package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
- package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
- package/templates/qoder/hooks/inject-workflow-state.py +0 -169
- package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/check_carriers.py +0 -238
- package/templates/qoder/scripts/check_mcp.py +0 -298
- package/templates/qoder/scripts/check_mcp_launch.py +0 -183
- package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
- package/templates/qoder/scripts/collect_prds.py +0 -31
- package/templates/qoder/scripts/common/mentions.py +0 -134
- package/templates/qoder/scripts/common/utf8.py +0 -38
- package/templates/qoder/scripts/extract_api_params.py +0 -246
- package/templates/qoder/scripts/extract_routes.py +0 -54
- package/templates/qoder/scripts/extract_routes_tree.py +0 -78
- package/templates/qoder/scripts/handoff.py +0 -22
- package/templates/qoder/scripts/init_developer.py +0 -76
- package/templates/qoder/scripts/kg_mcp_server.py +0 -801
- package/templates/qoder/scripts/kg_semantic.py +0 -150
- package/templates/qoder/scripts/mcp_launcher.py +0 -414
- package/templates/qoder/scripts/mysql_mcp_server.py +0 -396
- package/templates/qoder/scripts/parse_prds.py +0 -33
- package/templates/qoder/scripts/role.py +0 -51
- package/templates/qoder/scripts/sync_carriers.py +0 -259
- package/templates/qoder/scripts/task.py +0 -1261
- package/templates/qoder/scripts/workspace_init.py +0 -102
- package/templates/qoder/scripts/zentao_mcp_server.py +0 -424
- package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
- /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
- /package/templates/qoder/{skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp → scripts/domain/__init__.py} +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
- /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
- /package/templates/qoder/scripts/{secure-ls.js → validation/test/secure-ls.js} +0 -0
|
@@ -1,298 +0,0 @@
|
|
|
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())
|
|
@@ -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)
|