@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
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""stop-eval.py - Stop hook: Agent 说"完成"时强制 eval 门禁 (治"说做完了没真评测")
|
|
4
|
+
|
|
5
|
+
吸收宿主确定性 hook 能力: 把 EVA 门禁 (PRD 发布前 eval≥80%) 从"靠 AI 自觉跑"
|
|
6
|
+
升级为"Agent 准备结束回复时宿主拦住"。AI 说"PRD 做完了"但没跑 eval → 被拦回继续。
|
|
7
|
+
|
|
8
|
+
阻断契约: exit 0 放行; exit 2 阻断(stderr 反馈, 强制 Agent 续修)。
|
|
9
|
+
|
|
10
|
+
防递归 (关键!): Stop hook 被 exit 2 阻断后, Agent 续修完再次 Stop 会再次触发本 hook →
|
|
11
|
+
死循环。必须检查 stdin 的 stop_hook_active: 为 true 时直接 exit 0 放行 (Agent 已在本轮
|
|
12
|
+
被拦过一次, 不再二次拦)。
|
|
13
|
+
|
|
14
|
+
判断逻辑 (保守, 只拦明确场景):
|
|
15
|
+
- 本会话最近产出过 PRD 文件 (扫描 workspace/members/*/drafts + tasks/*/prd.md,
|
|
16
|
+
找近 10 分钟内 mtime 的 prd*.md)
|
|
17
|
+
- 且该 PRD 没有对应的 eval 记录 (eval-history.jsonl 里查不到该 prd 名)
|
|
18
|
+
→ exit 2 强制 eval
|
|
19
|
+
其它情况一律 exit 0 (绝不误拦普通对话/非 PRD 工作)。
|
|
20
|
+
|
|
21
|
+
stdin 字段名官方未公开, 兼容 stop_hook_active / hook_active 等多种写法。
|
|
22
|
+
"""
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import sys
|
|
26
|
+
import time
|
|
27
|
+
from datetime import datetime
|
|
28
|
+
|
|
29
|
+
if sys.platform == 'win32':
|
|
30
|
+
try:
|
|
31
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
32
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
37
|
+
HOOK_ERRORS_LOG = os.path.join(BASE, '.qoder', '.runtime', 'hook-errors.log')
|
|
38
|
+
EVAL_HISTORY = os.path.join(BASE, 'data', 'learning', 'eval-history.jsonl')
|
|
39
|
+
RECENT_WINDOW = 600 # 10 分钟内产出的 PRD 视为本轮产物
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _is_reentry(stdin_data):
|
|
43
|
+
"""是否二次进入 (防递归)。兼容多字段名。"""
|
|
44
|
+
if not isinstance(stdin_data, dict):
|
|
45
|
+
return False
|
|
46
|
+
for k in ('stop_hook_active', 'hook_active', 'is_reentry'):
|
|
47
|
+
v = stdin_data.get(k)
|
|
48
|
+
if v in (True, 'true', 'True', 1, '1'):
|
|
49
|
+
return True
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _read_stdin():
|
|
54
|
+
try:
|
|
55
|
+
if sys.stdin.isatty():
|
|
56
|
+
return {}
|
|
57
|
+
raw = sys.stdin.read().strip()
|
|
58
|
+
if not raw:
|
|
59
|
+
return {}
|
|
60
|
+
return json.loads(raw)
|
|
61
|
+
except Exception:
|
|
62
|
+
return {}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _is_prd_filename(fn):
|
|
66
|
+
"""判断文件名是否是 PRD 产物。
|
|
67
|
+
|
|
68
|
+
真实 PRD 有两类命名 (与 eval-history.jsonl 记录的 'prd' 字段对齐):
|
|
69
|
+
1. workspace/tasks/<task>/prd.md (任务级 PRD, 文件名含 prd)
|
|
70
|
+
2. workspace/members/<dev>/drafts/REQ-2026-XXX-<feature>.md
|
|
71
|
+
(AI 产出的 PRD 草稿, 命名 REQ-<id>-<feature>.md, 不含 'prd' 字样)
|
|
72
|
+
只匹配 'prd' 会漏掉第 2 类 (绝大多数真实产出) → 门禁形同虚设。
|
|
73
|
+
这里两类都认: 文件名含 'prd' 或 以 'REQ-' 开头 (eval-history 的真实记录格式)。
|
|
74
|
+
"""
|
|
75
|
+
low = fn.lower()
|
|
76
|
+
if not low.endswith('.md'):
|
|
77
|
+
return False
|
|
78
|
+
return 'prd' in low or low.startswith('req-')
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _recent_prd_files():
|
|
82
|
+
"""找近 RECENT_WINDOW 秒内产出的 PRD 文件 (绝对路径)。扫描 drafts + tasks。"""
|
|
83
|
+
now = time.time()
|
|
84
|
+
candidates = []
|
|
85
|
+
search_roots = [
|
|
86
|
+
os.path.join(BASE, 'workspace', 'members'),
|
|
87
|
+
os.path.join(BASE, 'workspace', 'tasks'),
|
|
88
|
+
os.path.join(BASE, 'data', 'docs', 'prd'),
|
|
89
|
+
]
|
|
90
|
+
for root in search_roots:
|
|
91
|
+
if not os.path.isdir(root):
|
|
92
|
+
continue
|
|
93
|
+
for dirpath, dirs, files in os.walk(root):
|
|
94
|
+
# 跳过无关大目录
|
|
95
|
+
dirs[:] = [d for d in dirs if d not in ('node_modules', '.git', '__pycache__', '.secrets')]
|
|
96
|
+
for fn in files:
|
|
97
|
+
if not _is_prd_filename(fn):
|
|
98
|
+
continue
|
|
99
|
+
fp = os.path.join(dirpath, fn)
|
|
100
|
+
try:
|
|
101
|
+
if now - os.path.getmtime(fp) <= RECENT_WINDOW:
|
|
102
|
+
candidates.append(fp)
|
|
103
|
+
except OSError:
|
|
104
|
+
continue
|
|
105
|
+
return candidates
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _evald_prds():
|
|
109
|
+
"""读 eval-history.jsonl, 返回【已通过门禁】的 prd 文件名集合。
|
|
110
|
+
|
|
111
|
+
门禁定义 (与消息承诺一致): 该 PRD 最近一次 eval 记录 passed=True 或 score_pct>=80。
|
|
112
|
+
只"跑过 eval 但没过线"的不算通过 → 仍需拦回去重测。
|
|
113
|
+
eval-history.jsonl 是 append-only, 同一 PRD 可能有多条 → 取最后一条为准。
|
|
114
|
+
"""
|
|
115
|
+
last = _last_record_by_prd()
|
|
116
|
+
done = set()
|
|
117
|
+
for key, r in last.items():
|
|
118
|
+
passed = r.get('passed')
|
|
119
|
+
score = r.get('score_pct')
|
|
120
|
+
is_ok = (passed is True) or (isinstance(score, (int, float)) and score >= 80)
|
|
121
|
+
if is_ok:
|
|
122
|
+
done.add(key)
|
|
123
|
+
return done
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _last_record_by_prd():
|
|
127
|
+
"""读 eval-history.jsonl, 返回 {prd_basename: 最后一条记录}。
|
|
128
|
+
|
|
129
|
+
append-only 文件, 后出现的记录覆盖前面的 → 取每个 PRD 的最新 eval。
|
|
130
|
+
被门禁诊断复用 (区分"没跑过 eval" vs "跑了但没过线")。
|
|
131
|
+
"""
|
|
132
|
+
last_by_prd = {}
|
|
133
|
+
if not os.path.isfile(EVAL_HISTORY):
|
|
134
|
+
return last_by_prd
|
|
135
|
+
try:
|
|
136
|
+
with open(EVAL_HISTORY, encoding='utf-8', errors='replace') as f:
|
|
137
|
+
for line in f:
|
|
138
|
+
line = line.strip()
|
|
139
|
+
if not line:
|
|
140
|
+
continue
|
|
141
|
+
try:
|
|
142
|
+
r = json.loads(line)
|
|
143
|
+
p = r.get('prd')
|
|
144
|
+
if p:
|
|
145
|
+
last_by_prd[os.path.basename(str(p))] = r
|
|
146
|
+
except Exception:
|
|
147
|
+
continue
|
|
148
|
+
except Exception:
|
|
149
|
+
pass
|
|
150
|
+
return last_by_prd
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def main():
|
|
154
|
+
try:
|
|
155
|
+
data = _read_stdin()
|
|
156
|
+
# 防递归: 二次进入直接放行
|
|
157
|
+
if _is_reentry(data):
|
|
158
|
+
sys.exit(0)
|
|
159
|
+
|
|
160
|
+
# 找近 10 分钟产出的 PRD, 检查是否已【通过】门禁
|
|
161
|
+
recent = _recent_prd_files()
|
|
162
|
+
if not recent:
|
|
163
|
+
sys.exit(0) # 本轮没产 PRD, 放行
|
|
164
|
+
evald = _evald_prds()
|
|
165
|
+
last_records = _last_record_by_prd()
|
|
166
|
+
# 区分两种未过门禁: 从没跑过 eval / 跑了但没过线
|
|
167
|
+
not_run = [p for p in recent if os.path.basename(p) not in last_records]
|
|
168
|
+
failed = [p for p in recent
|
|
169
|
+
if os.path.basename(p) in last_records
|
|
170
|
+
and os.path.basename(p) not in evald]
|
|
171
|
+
if not not_run and not failed:
|
|
172
|
+
sys.exit(0) # 全部通过门禁, 放行
|
|
173
|
+
|
|
174
|
+
# 有未过门禁的近期 PRD → 阻断, 给出可操作指引
|
|
175
|
+
parts = []
|
|
176
|
+
if not_run:
|
|
177
|
+
parts.append('未跑评测: ' + ', '.join(os.path.basename(p) for p in not_run[:3]))
|
|
178
|
+
if failed:
|
|
179
|
+
details = []
|
|
180
|
+
for p in failed[:3]:
|
|
181
|
+
rec = last_records.get(os.path.basename(p), {})
|
|
182
|
+
s = rec.get('score_pct')
|
|
183
|
+
details.append('%s (最近 %s)' % (os.path.basename(p),
|
|
184
|
+
('%s%%' % s) if s is not None else '未过'))
|
|
185
|
+
parts.append('评测未过线: ' + ', '.join(details))
|
|
186
|
+
sys.stderr.write(
|
|
187
|
+
'【EVA 门禁】检测到近期产出的 PRD 未通过质量门禁 (要求 eval ≥80%%):\n'
|
|
188
|
+
' %s\n'
|
|
189
|
+
'修复: python .qoder/scripts/orchestration/wlkj.py eval <PRD> [原型]\n'
|
|
190
|
+
' 针对评测报告的扣分项改 PRD, 重跑 eval 到 ≥80%% 再结束。\n'
|
|
191
|
+
% '; '.join(parts))
|
|
192
|
+
sys.exit(2)
|
|
193
|
+
except SystemExit:
|
|
194
|
+
raise
|
|
195
|
+
except Exception as e:
|
|
196
|
+
try:
|
|
197
|
+
os.makedirs(os.path.dirname(HOOK_ERRORS_LOG), exist_ok=True)
|
|
198
|
+
with open(HOOK_ERRORS_LOG, 'a', encoding='utf-8') as f:
|
|
199
|
+
f.write('[{}] stop-eval: {}\n'.format(
|
|
200
|
+
datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str(e)[:200]))
|
|
201
|
+
except Exception:
|
|
202
|
+
pass
|
|
203
|
+
sys.exit(0) # 铁律: hook 崩溃绝不阻塞
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
if __name__ == '__main__':
|
|
207
|
+
main()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""user-prompt-submit.py - UserPromptSubmit hook: /wl-prd-* 必须先问平台 (代码硬墙)
|
|
4
|
+
|
|
5
|
+
吸收宿主确定性 hook 能力: 把 CRITICAL RULE #1 "先问平台" 从"靠 LLM 自觉"
|
|
6
|
+
升级为"宿主在用户提交时拦截"。AI 想忘都忘不了。
|
|
7
|
+
|
|
8
|
+
历史教训 (REFACTOR_PROGRESS M4): 旧版 UserPromptSubmit hook 因"激进注入上下文"被删。
|
|
9
|
+
本次只做一件事 —— 命中 /wl-prd-* 且无平台词时 exit 2 阻断, 不注入任何 stdout 文本,
|
|
10
|
+
保持非侵入式。未命中 / 自然语言一律放行 (exit 0)。
|
|
11
|
+
|
|
12
|
+
阻断契约 (照搬 platform_guard.py, Qoder 镜像 Claude Code hook 体系):
|
|
13
|
+
- exit 0 = 放行
|
|
14
|
+
- exit 2 = 阻断, stderr 作为反馈展示给 Agent/用户, 停住等用户答平台
|
|
15
|
+
|
|
16
|
+
stdin 字段名: 官方未公开, 此处兼容多种可能 (prompt / user_input / message / text),
|
|
17
|
+
首字段命中即用。若都取不到 → 保守放行 (绝不误阻断)。
|
|
18
|
+
"""
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
if sys.platform == 'win32':
|
|
25
|
+
try:
|
|
26
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
27
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
NL = chr(10)
|
|
32
|
+
BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
33
|
+
HOOK_ERRORS_LOG = os.path.join(BASE, '.qoder', '.runtime', 'hook-errors.log')
|
|
34
|
+
|
|
35
|
+
# 命中 /wl-prd-* 的正则 (full/quick/review 等子命令都覆盖)
|
|
36
|
+
PRD_CMD_RE = re.compile(r'/wl-prd-', re.IGNORECASE)
|
|
37
|
+
# 平台已声明词 (任一出现即视为已问平台, 放行)
|
|
38
|
+
PLATFORM_WORDS = ('web', 'app', 'pc', 'h5', '管理端', '移动端', '两端', 'fywl-ui', 'carmg', '两端都要')
|
|
39
|
+
# 明确"正在问平台"的措辞 (避免 AI 把问平台的话本身当成已选)
|
|
40
|
+
ASKING_PHRASES = ('请选择', '哪个平台', '请选择 (1/2/3)', '选 (1/2/3)')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _read_prompt():
|
|
44
|
+
"""从 stdin 读用户输入文本。兼容多种字段名 + 纯文本兜底。取不到返回 None。"""
|
|
45
|
+
raw = None
|
|
46
|
+
try:
|
|
47
|
+
if not sys.stdin.isatty():
|
|
48
|
+
raw = sys.stdin.read()
|
|
49
|
+
except Exception:
|
|
50
|
+
return None
|
|
51
|
+
if not raw:
|
|
52
|
+
return None
|
|
53
|
+
raw = raw.strip()
|
|
54
|
+
# 1. 尝试 JSON (宿主喂的结构化输入)
|
|
55
|
+
try:
|
|
56
|
+
data = json.loads(raw)
|
|
57
|
+
if isinstance(data, dict):
|
|
58
|
+
for key in ('prompt', 'user_input', 'message', 'text', 'content', 'input'):
|
|
59
|
+
v = data.get(key)
|
|
60
|
+
if isinstance(v, str) and v.strip():
|
|
61
|
+
return v
|
|
62
|
+
# 嵌套 messages[-1].content 等
|
|
63
|
+
msgs = data.get('messages')
|
|
64
|
+
if isinstance(msgs, list) and msgs:
|
|
65
|
+
last = msgs[-1]
|
|
66
|
+
if isinstance(last, dict):
|
|
67
|
+
c = last.get('content') or last.get('text')
|
|
68
|
+
if isinstance(c, str):
|
|
69
|
+
return c
|
|
70
|
+
except (ValueError, TypeError):
|
|
71
|
+
pass
|
|
72
|
+
# 2. 纯文本兜底
|
|
73
|
+
return raw if raw else None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _should_block(prompt_text):
|
|
77
|
+
"""是否阻断: 命中 /wl-prd-* 且无平台词 且 不是在问平台本身。"""
|
|
78
|
+
if not prompt_text:
|
|
79
|
+
return False
|
|
80
|
+
if not PRD_CMD_RE.search(prompt_text):
|
|
81
|
+
return False
|
|
82
|
+
low = prompt_text.lower()
|
|
83
|
+
# AI 正在问平台 (这段 prompt 是 AI 发的提问) → 不拦, 让它继续问
|
|
84
|
+
if any(p in prompt_text for p in ASKING_PHRASES):
|
|
85
|
+
return False
|
|
86
|
+
# 已含平台词 → 放行
|
|
87
|
+
if any(w.lower() in low for w in PLATFORM_WORDS):
|
|
88
|
+
return False
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _record_violation():
|
|
93
|
+
"""best-effort 上报一次 rule_violation (不阻塞拦截本身)。注意避开 learn import 断链。"""
|
|
94
|
+
try:
|
|
95
|
+
learn_dir = os.path.join(BASE, '.qoder', 'scripts', 'domain', 'learning')
|
|
96
|
+
if learn_dir not in sys.path:
|
|
97
|
+
sys.path.insert(0, learn_dir)
|
|
98
|
+
# bootstrap foundation 路径
|
|
99
|
+
foundation_dir = os.path.join(BASE, '.qoder', 'scripts', 'foundation')
|
|
100
|
+
if foundation_dir not in sys.path:
|
|
101
|
+
sys.path.insert(0, foundation_dir)
|
|
102
|
+
from learn import record_feedback # noqa
|
|
103
|
+
record_feedback('rule_violation', {
|
|
104
|
+
'rule': 'ask_platform_first',
|
|
105
|
+
'skill': 'wl-prd (UserPromptSubmit hook)',
|
|
106
|
+
'detail': '用户提交 /wl-prd-* 未含平台词, hook 已阻断',
|
|
107
|
+
})
|
|
108
|
+
except Exception:
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def main():
|
|
113
|
+
try:
|
|
114
|
+
prompt = _read_prompt()
|
|
115
|
+
if _should_block(prompt):
|
|
116
|
+
_record_violation()
|
|
117
|
+
sys.stderr.write(
|
|
118
|
+
'【平台硬墙】/wl-prd-* 必须先问平台 (Web管理端 / APP移动端 / 两端都要)。\n'
|
|
119
|
+
'参考 AGENTS.md CRITICAL RULE #1: 先问平台, 不自动判断, 不跳过。\n'
|
|
120
|
+
'已记录一次规则违规。请先回答平台选择, 再继续。\n')
|
|
121
|
+
sys.exit(2) # 阻断
|
|
122
|
+
sys.exit(0) # 放行
|
|
123
|
+
except SystemExit:
|
|
124
|
+
raise
|
|
125
|
+
except Exception as e:
|
|
126
|
+
# 铁律: hook 崩溃绝不阻塞会话 → 保守放行 + 落盘日志
|
|
127
|
+
try:
|
|
128
|
+
os.makedirs(os.path.dirname(HOOK_ERRORS_LOG), exist_ok=True)
|
|
129
|
+
with open(HOOK_ERRORS_LOG, 'a', encoding='utf-8') as f:
|
|
130
|
+
from datetime import datetime
|
|
131
|
+
f.write('[{}] user-prompt-submit: {}\n'.format(
|
|
132
|
+
datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str(e)[:200]))
|
|
133
|
+
except Exception:
|
|
134
|
+
pass
|
|
135
|
+
sys.exit(0)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == '__main__':
|
|
139
|
+
main()
|
|
@@ -34,50 +34,44 @@ test -f .qoder/.developer && echo "已初始化" || echo "未初始化"
|
|
|
34
34
|
> "写个需求",但身份没初始化会导致命令静默失败。50 人团队里新成员第一句话
|
|
35
35
|
> 就撞这个的概率很高,必须在规则层兜住。
|
|
36
36
|
|
|
37
|
-
## 规则 0
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
| "写日报""周报""我这周干了啥""今天做了什么" | `/wl-report` 或 wl-report |
|
|
54
|
-
| "调研""竞品""对标""规划""路线图""接下来做什么""发现个机会""探讨需求" | `/wl-insight` 或 wl-insight(探索站:发现/调研/规划)|
|
|
55
|
-
| "生成规格""写spec""技术方案" | `/wl-spec` 或 spec-generator |
|
|
56
|
-
| "开始写代码""按规格实现""实现这个接口" | `/wl-code` 或 spec-coder |
|
|
57
|
-
| "跑测试用例""执行测试任务""回归测试任务" | `/wl-test`(测试工作流)或 wl-test |
|
|
58
|
-
| "写单元测试""补单测""生成test" | `/wl-test unit` 或 test-generator |
|
|
59
|
-
| "评审PRD""PRD质量怎么样""检查需求文档" | `/wl-prd-review`(评审 PRD 质量)|
|
|
60
|
-
| "提交代码""commit""推上去""保存代码" | `/wl-commit` 或 wl-commit |
|
|
61
|
-
| "初始化""换开发者""环境检查""我是谁" | `/wl-init` 或 wl-init |
|
|
62
|
-
| **"出个原型""画个界面""做个 mockup""改个页面"** | **`/wl-design-draw`(画界面)** |
|
|
63
|
-
| **"XX 有哪些页面""XX 操作流程""系统什么风格""改XX影响什么"** | **`/wl-design-scan`(审视现状)** |
|
|
64
|
-
| **"录入设计稿""评审原型""检查原型""Figma 稿""蓝湖稿""蓝湖链接""lanhu"** | **`/wl-design-spec`(设计交付,蓝湖链接优先用 `mcp__lanhu__*` 直读)** |
|
|
65
|
-
| "测试覆盖""哪些功能有测试""哪些没测试""回归盲区" | `/wl-design-scan` 或 MCP `coverage_matrix()` |
|
|
66
|
-
| "这个功能完整吗""XX功能画像" | `/wl-design-scan` 或 MCP `feature_overview(feature='XX')` |
|
|
67
|
-
| "改XX会影响什么""改这个接口影响哪些页面" | `/wl-design-scan` 或 MCP `get_impact(endpoint='XX')` |
|
|
68
|
-
| "XX函数谁调用了""XX的调用链" | MCP `context_360(symbol='XX')` |
|
|
69
|
-
|
|
70
|
-
> 知识图谱 13 个能力(`kg.py`)**全系列通用**:QoderWork 环境 AI 也可直接调
|
|
71
|
-
> MCP 工具(`mcp__qoder-knowledge-graph__*`),非 MCP 环境跑 kg.py,数据一致。
|
|
72
|
-
> 能力清单见 `/wl-search`。
|
|
73
|
-
|
|
74
|
-
**找不到明确匹配时:先问用户意图,不要乱跑**("你是想写 PRD、查代码、还是建任务?")。
|
|
37
|
+
## 规则 0:触发方式(非侵入式 — v3.0 改造)
|
|
38
|
+
|
|
39
|
+
工作流**只在显式调用时启动**,不主动干预用户的普通对话。
|
|
40
|
+
|
|
41
|
+
**两种触发方式(且仅此两种):**
|
|
42
|
+
1. **斜杠命令**:输入 `/wl-prd-full` `/wl-search` `/wl-req` 等 → 启动工作流
|
|
43
|
+
2. **npx**:`npx @hupan56/wlkj init/update` → 启动安装/同步
|
|
44
|
+
|
|
45
|
+
**自然语言不触发工作流。** 用户说"写个需求""查下代码"等,AI 正常回答,不自动拉入工作流。
|
|
46
|
+
(工作流启动后的过程内交互 — 如选平台、确认需求 — 仍可用自然语言。)
|
|
47
|
+
|
|
48
|
+
> Quest/QoderWork 里 `/` 会列出 commands 和 skills,优先用 `/wl-*` 命令。
|
|
49
|
+
> 命令一览见本文档底部"用户侧命令一览"。
|
|
50
|
+
|
|
51
|
+
> 知识图谱能力(`kg.py`)全系列通用:QoderWork 用 MCP,非 MCP 跑 kg.py。
|
|
52
|
+
|
|
75
53
|
注意:用户说"同步/保存产出"通常指 team_sync(push),不是 git commit;
|
|
76
54
|
用户说"提交/commit 代码"才是 wl-commit。
|
|
77
55
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
56
|
+
## 规则 0.5:数据库查询必须先问环境(安全红线,问了就停)
|
|
57
|
+
|
|
58
|
+
AI 检测到需要查询**真实数据库**(调用 `mcp__qoder-mysql` 的 query_schema / query_data /
|
|
59
|
+
query_distinct / list_databases / list_tables 等)时,**绝不能直接发查询**,必须:
|
|
60
|
+
|
|
61
|
+
1. **先调 `mcp__qoder-mysql__list_envs()`** 取环境列表;
|
|
62
|
+
2. **把可选环境列给用户,问"要查哪个环境"**,然后**停下等用户选**;
|
|
63
|
+
3. 用户选定后,后续查询都带 `env=<用户选的标签>`。
|
|
64
|
+
|
|
65
|
+
环境由 `.qoder/config.yaml` 的 `mysql.environments` 定义,标签可自定义
|
|
66
|
+
(test/pre/prod 或 qa/staging…)。**禁止自行假定环境**,即便用户没明说,
|
|
67
|
+
也要先问——这是为了杜绝误连生产库。
|
|
68
|
+
|
|
69
|
+
查询结果自带环境声明(由 MCP 注入):
|
|
70
|
+
- 测试环境 → 数据是样本,不代表线上分布
|
|
71
|
+
- 生产环境 → 🔴 真实数据,仅供核对字段含义/格式,**禁止作为业务结论外传**
|
|
72
|
+
|
|
73
|
+
> 同一轮对话里用户已选过环境、追问同一库,可不再重复问。
|
|
74
|
+
> 默认环境见 `mysql.default_env`(通常 test),但"首次仍问"是硬约束。
|
|
81
75
|
|
|
82
76
|
## 规则 1:PRD/原型需求必须先问平台(问了就停,不许边问边干)
|
|
83
77
|
|
|
@@ -99,8 +93,8 @@ test -f .qoder/.developer && echo "已初始化" || echo "未初始化"
|
|
|
99
93
|
## 规则 2:查代码索引优先,索引找不到才准 grep(又快又准的取数顺序)
|
|
100
94
|
|
|
101
95
|
```bash
|
|
102
|
-
python .qoder/scripts/
|
|
103
|
-
python .qoder/scripts/
|
|
96
|
+
python .qoder/scripts/orchestration/wlkj.py kg context <业务关键词> --platform <web|app> [--role pm|design|dev|test] # 一次取全(首选, 秒级)
|
|
97
|
+
python .qoder/scripts/orchestration/wlkj.py search <关键词> --platform <web|app> # 单项搜索
|
|
104
98
|
```
|
|
105
99
|
kg.py context(= context_pack)一条命令返回:相关代码文件 + 同类页面示例 + 字段规格 +
|
|
106
100
|
相关 PRD + 设计 Token + 图标参考 + 模板路径。kg.py 还有 12 个兄弟能力
|
|
@@ -109,9 +103,34 @@ kg.py context(= context_pack)一条命令返回:相关代码文件 + 同
|
|
|
109
103
|
**取数顺序(强制,违反 = 慢 + 凭空写 PRD):**
|
|
110
104
|
1. **先用索引**:任何 /wl-* 命令查代码,**第一步永远是 kg.py context**(查预建图谱,秒级,不碰文件系统)。绝大多数需求一次就够。
|
|
111
105
|
2. **索引够用就停**:context 列出了相关文件/字段/API → 只用 Read 读那 1-2 个 .vue,**不要再用 Grep/Glob 去文件系统扫**。
|
|
112
|
-
3.
|
|
106
|
+
3. **中文文案/报错溯源用 grep-text**:用户查的是中文短语(报错文案"车辆不存在"、字段标签"项目车号"、业务词),kg.py 的代码标识符索引天然查不到。**这时只能用 `kg.py grep-text <中文短语>`**(单进程 Python,限定 data/code,限定扩展名,秒回,不弹窗)。
|
|
107
|
+
4. **🚫 硬性禁令(违反 = QoderWork 卡死 + 疯狂弹 cmd 窗口)**:
|
|
108
|
+
- **禁止**用 `findstr /s`、`grep -r`、`rg` 递归扫整个 data/code(8973+ 文件)或 qoderAll 仓
|
|
109
|
+
- **禁止**用 `os.walk` / `os.system` / `subprocess` 全盘递归搜文件
|
|
110
|
+
- **禁止**在 Bash 里手写 Python `-c "import os; for ... os.walk(...)"` 全库遍历
|
|
111
|
+
- 兜底搜索**唯一合法方式** = `kg.py grep-text`(已内置单进程、范围限定、命中上限)
|
|
112
|
+
- Grep/Glob 工具**只准搜 context/grep-text 已定位到的具体文件或单一小目录**,不准全库扫
|
|
113
|
+
|
|
114
|
+
**为什么这条是红线**:QoderWork 自带 Agent 默认会用 Grep/Glob 直搜文件系统,它不懂"业务源码在 data/code/ 镜像里",会搜整个 qoderAll 仓然后报"无匹配",被迫凭空编字段/API。更糟的是 AI 兜底去跑 `findstr /s` 全盘扫描 → Windows 对 8973 文件递归 = 卡死 + 疯狂弹子进程窗口,用户只能强杀。**Agent 只准用来**:①读 context 列出的具体文件(Read)②**在 /wl-insight 里**做 web 调研(对标/竞品)和多视角探索脑暴。**绝不用 Agent 去"发现/搜索"代码——那是 kg.py 的活。** 注意:探索类 Agent 调研已集中到 `/wl-insight`,`/wl-prd-full` 不再派 Agent 做 web 搜索/脑暴。
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
## 规则 2.5:工具调用能并发就并发(性能命门,串行 = 慢)
|
|
117
|
+
|
|
118
|
+
**串行执行是工作流慢的最大元凶。** 每次工具调用都要等返回(图谱查询 2-3 秒、读文件 1 秒、
|
|
119
|
+
命令行 3-5 秒),10 个串行步骤 = 白等 30-50 秒。MCP 工具调用**支持同一回合并发**,必须利用:
|
|
120
|
+
|
|
121
|
+
**并发规则(全 /wl-* 命令强制):**
|
|
122
|
+
1. **多个独立的 search/Read → 同一 assistant 回合一起发**,不要"发一个、等返回、再发下个"。
|
|
123
|
+
确定要读 3 个文件后,一个回合同时发 3 个 Read。
|
|
124
|
+
2. **多个独立搜索可并发**:要搜 `OSS` + `upload` 两个关键词,一次发 2 个 search_code。
|
|
125
|
+
3. **只有"后步依赖前步结果"才串行**:先 search 拿路径才能 Read(必须串行);但路径已知后,
|
|
126
|
+
多个 Read 必须**并发**。
|
|
127
|
+
4. **一轮思考塞满**:能同回合发的工具调用全发出去,等它们一起回来,别"一回合一个"。
|
|
128
|
+
|
|
129
|
+
**反例(禁止)**:search A → 等 → Read X → 等 → search B → 等 → Read Y → 等 ...
|
|
130
|
+
**正例(正确)**:[search A + search B] 并发 → 等 → [Read X + Read Y + Read Z] 并发 → 答
|
|
131
|
+
|
|
132
|
+
> 这是工作流比原生慢的根因——原生 AI 本能地并发,工作流却被"逐步执行"的文档节奏带成了串行。
|
|
133
|
+
> 把串行变并发,同样工作量耗时可砍到 1/3。MCP server(kg/mysql/zentao)都支持 JSON-RPC 并发请求。
|
|
115
134
|
|
|
116
135
|
## 规则 3:原型风格必须来自真源(含图标)
|
|
117
136
|
|
|
@@ -154,19 +173,19 @@ PM /wl-prd-full → AI 出初稿 PRD + 原型
|
|
|
154
173
|
## 规则 4:PRD 必须套团队模板 + 过 EVA 门禁
|
|
155
174
|
|
|
156
175
|
- 完整需求用 .qoder/templates/prd-full-template.md,零星需求用 prd-quick-template.md
|
|
157
|
-
- 发布前:`python .qoder/scripts/
|
|
176
|
+
- 发布前:`python .qoder/scripts/orchestration/wlkj.py eval <prd> [原型]` 必须 PASS (≥80%)
|
|
158
177
|
- 发现确认采用批量方式(一个编号列表一轮确认),不要逐条问
|
|
159
178
|
|
|
160
179
|
## 规则 5:用户永不接触 git
|
|
161
180
|
|
|
162
|
-
- 产出保存/发布后自动执行 `python .qoder/scripts/
|
|
163
|
-
- 拉取团队最新:`python .qoder/scripts/
|
|
181
|
+
- 产出保存/发布后自动执行 `python .qoder/scripts/orchestration/wlkj.py sync push`
|
|
182
|
+
- 拉取团队最新:`python .qoder/scripts/orchestration/wlkj.py sync pull`
|
|
164
183
|
- 出现 SYNC_CONFLICT 由 AI 按提示自行解决,绝不让用户跑 git 命令
|
|
165
184
|
|
|
166
185
|
## 规则 6:环境问题找 doctor
|
|
167
186
|
|
|
168
187
|
任何"索引不对/搜不到/环境坏了"类问题:
|
|
169
|
-
`python .qoder/scripts/
|
|
188
|
+
`python .qoder/scripts/orchestration/wlkj.py doctor`(加 --fix 自动修复,幂等增量,不会重头构建)
|
|
170
189
|
|
|
171
190
|
## 规则 7:Repo Wiki 是补充知识源
|
|
172
191
|
|
|
@@ -214,3 +233,20 @@ PM /wl-prd-full → AI 出初稿 PRD + 原型
|
|
|
214
233
|
> 环境/域名在 config.yaml `autotest:` 配,`--url` 可临时覆盖。
|
|
215
234
|
|
|
216
235
|
用户用自然语言表达即可,AI 负责映射到命令和脚本。
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 工作流可用性自检(吸收宿主能力:会话上下文注入)
|
|
240
|
+
|
|
241
|
+
会话开头 `<qoder-context>` 里的 **Workflow Usability** 段是宿主 SessionStart hook
|
|
242
|
+
自动注入的"近7天红灯"(一次过审率/规则违反率不达标)。**见到红灯时遵守:**
|
|
243
|
+
|
|
244
|
+
1. **红灯 = 工作流某环节没跑起来**,不是忽略它继续干活。
|
|
245
|
+
2. 一次过审率低(<60%):写 PRD 前先读 `data/learning/patterns/prd-patterns.md` 避坑。
|
|
246
|
+
3. 规则违反率高(>10%):`/wl-prd-*` 启动**必须先问平台**(铁律1),不问就是新增违规。
|
|
247
|
+
4. 想看全貌:`python .qoder/scripts/orchestration/wlkj.py usability` 或 `/wl-status health`(健康分含 adoption 维度)。
|
|
248
|
+
5. 指标定义/公式/阈值:`data/docs/constitution/workflow-usability-metrics.md`。
|
|
249
|
+
|
|
250
|
+
> 这套指标评"工作流用没用起来",与 EVA(评 PRD 质量)分工不同——EVA 卡发布门禁,
|
|
251
|
+
> 可用性指标卡"工具该不该继续投入"。
|
|
252
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# .qoder/scripts/ — 引擎脚本目录 (分层架构)
|
|
2
|
+
|
|
3
|
+
> 引擎代码, 由 `npx @hupan56/wlkj init/update` 生成, **不进团队 git** (Trellis 化)。
|
|
4
|
+
> 用户不直接碰这些文件 — 通过 `/命令` 和 `npx` 间接调用。
|
|
5
|
+
|
|
6
|
+
## 🏗️ 分层架构 (7 层)
|
|
7
|
+
|
|
8
|
+
子目录化 (v3.0) 后, 脚本按职责分层, 顶层**无散落 .py**, 只有 7 个分层目录:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
scripts/
|
|
12
|
+
├── orchestration/ 编排层 — wlkj.py 统一入口 (载体收敛点)
|
|
13
|
+
├── foundation/ 基础层 — 共享库 (被所有层 import)
|
|
14
|
+
├── domain/ 领域层 — 业务逻辑 (kg/task/report/...)
|
|
15
|
+
├── protocol/ 协议层 — MCP server / 传输适配
|
|
16
|
+
├── capability/ 能力层 — 链式能力编排 / 冒烟测试
|
|
17
|
+
├── validation/ 验证层 — 测试 / 评测 / 基准
|
|
18
|
+
└── deployment/ 部署层 — 安装 / 体检 / 载体同步
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 🚪 统一入口: `orchestration/wlkj.py` (Facade)
|
|
22
|
+
|
|
23
|
+
**所有 Qoder 载体 (commands/skills/rules) 的脚本调用都走这个入口**, 由 DISPATCH 表路由到
|
|
24
|
+
分层路径。这样 scripts/ 重组目录时只改 DISPATCH 一处, 载体不受影响。
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
python .qoder/scripts/orchestration/wlkj.py <命令> [参数...]
|
|
28
|
+
# 例: wlkj.py kg search 保险 | wlkj.py task list | wlkj.py doctor --fix
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
完整命令表见 `wlkj.py` 的 DISPATCH (kg/search/context/task/sync/req/eval/learn/status/report/test/doctor 等 36 条)。
|
|
32
|
+
脚本内部互调 (Python 进程间) 直接用分层路径, 不绕 wlkj (避免 subprocess 嵌套)。
|
|
33
|
+
|
|
34
|
+
## 各层职责 + 关键脚本
|
|
35
|
+
|
|
36
|
+
### 🎼 orchestration/ — 编排
|
|
37
|
+
| 脚本 | 用途 |
|
|
38
|
+
|------|------|
|
|
39
|
+
| `wlkj.py` | **统一调度入口**, DISPATCH 透明转发到各层脚本 |
|
|
40
|
+
|
|
41
|
+
### 🧱 foundation/ — 基础 (共享库, 不含业务)
|
|
42
|
+
| 模块 | 用途 |
|
|
43
|
+
|------|------|
|
|
44
|
+
| `bootstrap.py` | sys.path 自举 (子目录化后定位 scripts/ 根) |
|
|
45
|
+
| `core/paths.py` | **路径管理** (PROJECT_ROOT / get_repo_root / get_developer) |
|
|
46
|
+
| `identity/` | 身份 (identity.py / developer.py / roles.py 角色章节) |
|
|
47
|
+
| `io/` | 原子 IO (atomicio.py) / 文件锁 (filelock.py) |
|
|
48
|
+
| `protocol/mcp_base.py` | MCP server 基类 (@tool 装饰器 + 元类注册) |
|
|
49
|
+
| `integrations/` | 术语扩展 (terms.py) / 禅道客户端 (zentao_client.py, 单一真源) |
|
|
50
|
+
| `utils/eval_api.py` | 评测 API |
|
|
51
|
+
|
|
52
|
+
### 🎯 domain/ — 领域 (业务逻辑)
|
|
53
|
+
| 子包 | 关键脚本 | 用途 |
|
|
54
|
+
|------|---------|------|
|
|
55
|
+
| `kg/` | `kg.py` (**图谱统一入口**, 13+ 能力) | 知识图谱 |
|
|
56
|
+
| `kg/search/` | search_index.py / context_pack.py / prefetch.py | 代码搜索 / 一键上下文 |
|
|
57
|
+
| `kg/build/` | kg_build.py / kg_build_db.py / kg_incremental.py / build_style_index.py | 图谱构建 |
|
|
58
|
+
| `kg/graph/` | kg_semantic.py / kg_link_db.py | 语义 / 数据库图谱链接 |
|
|
59
|
+
| `kg/storage/` | kg_duckdb.py | DuckDB 存储层 |
|
|
60
|
+
| `kg/server/` | kgd.py (常驻服务) / perf_bench.py | 语义加速服务 |
|
|
61
|
+
| `task/` | task.py (**任务系统**) / team_sync.py / git_sync.py / zentao_sync.py / syncgate.py | 任务 + 同步 |
|
|
62
|
+
| `requirement/` | req.py / archive_prd.py | 需求生命周期 |
|
|
63
|
+
| `report/` | report.py / status.py / role.py / export.py / *_snapshot.py | 报告 / 状态 |
|
|
64
|
+
| `design/` | fill_prototype.py / gen_design_doc.py | 原型填充 / 设计文档 |
|
|
65
|
+
| `learning/` | learn.py | 学习飞轮 (埋点) |
|
|
66
|
+
|
|
67
|
+
### 🔌 protocol/ — 协议
|
|
68
|
+
| 子包 | 脚本 | 用途 |
|
|
69
|
+
|------|------|------|
|
|
70
|
+
| `mcp/` | mcp_launcher.py (**统一启动器**) / kg_mcp_server.py / mysql_mcp_server.py / zentao_mcp_server.py / lanhu_stdio_wrapper.py / mcp_doctor.py | 4 个自有 MCP server |
|
|
71
|
+
| `transports/` | cli.py | CLI 传输适配 |
|
|
72
|
+
|
|
73
|
+
### 🧩 capability/ — 能力编排
|
|
74
|
+
| 脚本 | 用途 |
|
|
75
|
+
|------|------|
|
|
76
|
+
| `chain.py` / `memory_chain.py` | 链式能力编排 |
|
|
77
|
+
| `adapters/` (cli.py / qw.py / mcp.py) | 能力适配器 (CLI/QoderWork/MCP) |
|
|
78
|
+
|
|
79
|
+
### 🧪 validation/ — 验证
|
|
80
|
+
| 子包 | 关键脚本 | 用途 |
|
|
81
|
+
|------|---------|------|
|
|
82
|
+
| `test/` | autotest.py / smoke_all_commands.py / benchmark.py / kg_test_runner.py | 自动测试 / 冒烟 / 基准 |
|
|
83
|
+
| `metrics/` | eval_prd.py / usability_score.py | **PRD 质量门禁** (EVA, ≥80%) + **工作流可用性** (采纳+流程) |
|
|
84
|
+
| `eval/` | 性能评测报告 | |
|
|
85
|
+
|
|
86
|
+
### 🚀 deployment/ — 部署
|
|
87
|
+
| 脚本 | 用途 |
|
|
88
|
+
|------|------|
|
|
89
|
+
| `setup.py` | **一键就绪** (npx init 调的主体) |
|
|
90
|
+
| `init_doctor.py` | 环境体检 + 自愈 + 身份注册 |
|
|
91
|
+
| `install_qoderwork.py` | QoderWork 技能/MCP 安装 |
|
|
92
|
+
| `carriers.py` | **载体一致性** (check/check-paths/sync/all, 4 载体对齐) |
|
|
93
|
+
| `repo_root.py` | 仓库根探测 (QoderWork 桌面端用) |
|
|
94
|
+
| `setup_lanhu.py` / `platform_doctor.py` | 蓝湖配置 / 平台体检 |
|
|
95
|
+
|
|
96
|
+
## 设计原则
|
|
97
|
+
|
|
98
|
+
1. **分层即分类**: 物理目录 = 职责分层 (foundation/domain/protocol/...), 取代旧的命名前缀
|
|
99
|
+
2. **单一入口**: `orchestration/wlkj.py` 是载体层唯一收敛点; `domain/kg/kg.py` 是图谱入口; `domain/task/task.py` 是任务入口
|
|
100
|
+
3. **foundation/ 共享**: 通用逻辑抽到 foundation/, 领域无关 (路径/身份/IO/协议基类)
|
|
101
|
+
4. **载体与引擎解耦**: 载体 (commands/skills) 只认 wlkj.py 一个路径, 引擎目录重组不波及载体
|
|
102
|
+
5. **降级不崩**: 缺依赖/缺数据时优雅降级, 绝不 sys.exit 崩溃 (见 protocol/mcp/mcp_launcher.py)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
nexus —— 宿主能力抽象层(Capability Nexus)。
|
|
4
|
+
|
|
5
|
+
工作流不问宿主是谁,只问它有什么能力。
|
|
6
|
+
QoderWork / ZCode / Codex / ClaudeCode 有 memory / cron / mcp 就用,
|
|
7
|
+
没有就静默回退(Null Object),绝不报错。
|
|
8
|
+
|
|
9
|
+
主轴: 能力契约(Capability Protocol)+ MCP 优先 + 职责链(Provider Chain)
|
|
10
|
+
|
|
11
|
+
用法:
|
|
12
|
+
from capability import resolve
|
|
13
|
+
cap = resolve()
|
|
14
|
+
cap.mcp.call("list_executions", {...}) # L1 已有 MCP(禅道/知识图谱...)
|
|
15
|
+
cap.memory.remember("本周", "73/74/75") # L2 职责链(MCP→QW→文件→NoOp)
|
|
16
|
+
|
|
17
|
+
诊断:
|
|
18
|
+
python .qoder/scripts/capability # 打印当前宿主 + 各能力状态
|
|
19
|
+
|
|
20
|
+
设计文档: .qoder/data/docs/architecture/nexus-capability-layer.md
|
|
21
|
+
"""
|
|
22
|
+
from .registry import resolve, detect_host, Nexus, HOST_TABLE
|
|
23
|
+
|
|
24
|
+
__all__ = ["resolve", "detect_host", "Nexus", "HOST_TABLE"]
|
|
25
|
+
|
|
26
|
+
__version__ = "0.1.0"
|