@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.
Files changed (253) hide show
  1. package/bin/cli.js +1435 -1107
  2. package/package.json +29 -29
  3. package/templates/qoder/agents/insight-planning.md +1 -1
  4. package/templates/qoder/agents/insight-research.md +28 -15
  5. package/templates/qoder/commands/optional/wl-insight.md +161 -161
  6. package/templates/qoder/commands/optional/wl-report.md +20 -39
  7. package/templates/qoder/commands/optional/wl-status.md +4 -4
  8. package/templates/qoder/commands/wl-code.md +2 -2
  9. package/templates/qoder/commands/wl-design.md +6 -6
  10. package/templates/qoder/commands/wl-init.md +3 -3
  11. package/templates/qoder/commands/wl-prd.md +26 -13
  12. package/templates/qoder/commands/wl-req.md +43 -0
  13. package/templates/qoder/commands/wl-search.md +78 -47
  14. package/templates/qoder/commands/wl-task.md +343 -40
  15. package/templates/qoder/commands/wl-test.md +43 -17
  16. package/templates/qoder/config.yaml +52 -14
  17. package/templates/qoder/hooks/post-tool-use.py +181 -0
  18. package/templates/qoder/hooks/session-start.py +94 -23
  19. package/templates/qoder/hooks/stop-eval.py +207 -0
  20. package/templates/qoder/hooks/user-prompt-submit.py +139 -0
  21. package/templates/qoder/rules/wl-pipeline.md +85 -49
  22. package/templates/qoder/scripts/README.md +102 -0
  23. package/templates/qoder/scripts/capability/__init__.py +26 -0
  24. package/templates/qoder/scripts/capability/__main__.py +72 -0
  25. package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
  26. package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
  27. package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
  28. package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
  29. package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
  30. package/templates/qoder/scripts/capability/caps/context.py +36 -0
  31. package/templates/qoder/scripts/capability/caps/cron.py +50 -0
  32. package/templates/qoder/scripts/capability/caps/identity.py +43 -0
  33. package/templates/qoder/scripts/capability/caps/memory.py +71 -0
  34. package/templates/qoder/scripts/capability/caps/notify.py +41 -0
  35. package/templates/qoder/scripts/capability/caps/present.py +48 -0
  36. package/templates/qoder/scripts/capability/caps/repo.py +31 -0
  37. package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
  38. package/templates/qoder/scripts/capability/chain.py +92 -0
  39. package/templates/qoder/scripts/capability/memory_chain.py +41 -0
  40. package/templates/qoder/scripts/capability/registry.py +246 -0
  41. package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
  42. package/templates/qoder/scripts/capability/smoke_test.py +211 -0
  43. package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
  44. package/templates/qoder/scripts/deployment/setup/__init__.py +11 -0
  45. package/templates/qoder/scripts/deployment/setup/carriers.py +669 -0
  46. package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
  47. package/templates/qoder/scripts/{init_doctor.py → deployment/setup/init_doctor.py} +100 -39
  48. package/templates/qoder/scripts/{install_qoderwork.py → deployment/setup/install_qoderwork.py} +48 -30
  49. package/templates/qoder/scripts/{platform_doctor.py → deployment/setup/platform_doctor.py} +271 -259
  50. package/templates/qoder/scripts/{repo_root.py → deployment/setup/repo_root.py} +9 -2
  51. package/templates/qoder/scripts/{setup.py → deployment/setup/setup.py} +143 -14
  52. package/templates/qoder/scripts/{setup_lanhu.py → deployment/setup/setup_lanhu.py} +979 -963
  53. package/templates/qoder/scripts/domain/design/__init__.py +0 -0
  54. package/templates/qoder/scripts/{fill_prototype.py → domain/design/fill_prototype.py} +17 -5
  55. package/templates/qoder/scripts/{gen_design_doc.py → domain/design/gen_design_doc.py} +406 -394
  56. package/templates/qoder/scripts/domain/kg/__init__.py +11 -0
  57. package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
  58. package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +201 -0
  59. package/templates/qoder/scripts/domain/kg/build/build_relations.py +132 -0
  60. package/templates/qoder/scripts/{build_style_index.py → domain/kg/build/build_style_index.py} +50 -15
  61. package/templates/qoder/scripts/domain/kg/build/build_workflows.py +149 -0
  62. package/templates/qoder/scripts/{kg_build.py → domain/kg/build/kg_build.py} +685 -612
  63. package/templates/qoder/scripts/{kg_build_db.py → domain/kg/build/kg_build_db.py} +338 -327
  64. package/templates/qoder/scripts/{kg_incremental.py → domain/kg/build/kg_incremental.py} +425 -393
  65. package/templates/qoder/scripts/{learn_aggregate.py → domain/kg/build/learn_aggregate.py} +212 -201
  66. package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
  67. package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
  68. package/templates/qoder/scripts/domain/kg/extract/test_extract.py +115 -0
  69. package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
  70. package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
  71. package/templates/qoder/scripts/{kg_link_db.py → domain/kg/graph/kg_link_db.py} +235 -224
  72. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
  73. package/templates/qoder/scripts/{kg.py → domain/kg/kg.py} +871 -708
  74. package/templates/qoder/scripts/domain/kg/kg_capabilities.py +182 -0
  75. package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
  76. package/templates/qoder/scripts/{context_pack.py → domain/kg/search/context_pack.py} +45 -17
  77. package/templates/qoder/scripts/{enrich_prompt.py → domain/kg/search/enrich_prompt.py} +238 -226
  78. package/templates/qoder/scripts/domain/kg/search/prefetch.py +384 -0
  79. package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
  80. package/templates/qoder/scripts/{search_index.py → domain/kg/search/search_index.py} +210 -88
  81. package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
  82. package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
  83. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  84. package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
  85. package/templates/qoder/scripts/{kg_duckdb.py → domain/kg/storage/kg_duckdb.py} +70 -45
  86. package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
  87. package/templates/qoder/scripts/{learn.py → domain/learning/learn.py} +157 -146
  88. package/templates/qoder/scripts/domain/report/__init__.py +11 -0
  89. package/templates/qoder/scripts/{add_session.py → domain/report/add_session.py} +256 -244
  90. package/templates/qoder/scripts/{export.py → domain/report/export.py} +72 -4
  91. package/templates/qoder/scripts/{report.py → domain/report/report.py} +292 -281
  92. package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
  93. package/templates/qoder/scripts/domain/report/role.py +39 -0
  94. package/templates/qoder/scripts/{status.py → domain/report/status.py} +737 -628
  95. package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
  96. package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
  97. package/templates/qoder/scripts/{archive_prd.py → domain/requirement/archive_prd.py} +389 -377
  98. package/templates/qoder/scripts/domain/requirement/req.py +228 -0
  99. package/templates/qoder/scripts/domain/task/__init__.py +11 -0
  100. package/templates/qoder/scripts/{git_sync.py → domain/task/git_sync.py} +90 -50
  101. package/templates/qoder/scripts/{syncgate.py → domain/task/syncgate.py} +18 -7
  102. package/templates/qoder/scripts/domain/task/task.py +229 -0
  103. package/templates/qoder/scripts/domain/task/task_lifecycle.py +606 -0
  104. package/templates/qoder/scripts/domain/task/task_query.py +162 -0
  105. package/templates/qoder/scripts/domain/task/task_relations.py +425 -0
  106. package/templates/qoder/scripts/{team_sync.py → domain/task/team_sync.py} +101 -23
  107. package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
  108. package/templates/qoder/scripts/foundation/__init__.py +0 -0
  109. package/templates/qoder/scripts/foundation/bootstrap.py +145 -0
  110. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  111. package/templates/qoder/scripts/foundation/core/cmd_registry.py +112 -0
  112. package/templates/qoder/scripts/foundation/core/config.py +187 -0
  113. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -400
  114. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  115. package/templates/qoder/scripts/foundation/data/contract.py +317 -0
  116. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  117. package/templates/qoder/scripts/foundation/data/result.py +223 -0
  118. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -392
  119. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  120. package/templates/qoder/scripts/foundation/identity/check_publish.py +103 -0
  121. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -238
  122. package/templates/qoder/scripts/foundation/identity/guard.py +159 -0
  123. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +132 -9
  124. package/templates/qoder/scripts/foundation/identity/roles.py +60 -0
  125. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  126. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  127. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +11 -10
  128. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  129. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  130. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  131. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  132. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  133. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  134. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  135. package/templates/qoder/scripts/foundation/protocol/mcp_base.py +268 -0
  136. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  137. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  138. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  139. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  140. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  141. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  142. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  143. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  144. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  145. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  146. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  147. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  148. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  149. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  150. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  151. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  152. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  153. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  154. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  155. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  156. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  157. package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → protocol/mcp/lanhu_stdio_wrapper.py} +131 -119
  158. package/templates/qoder/scripts/protocol/mcp/mcp_doctor.py +548 -0
  159. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  160. package/templates/qoder/scripts/protocol/mcp/mysql_mcp_server.py +461 -0
  161. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  162. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  163. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  164. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  165. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  166. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  167. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  168. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  169. package/templates/qoder/scripts/validation/__init__.py +0 -0
  170. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  171. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  172. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  173. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  174. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  175. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  176. package/templates/qoder/scripts/{eval_prd.py → validation/metrics/eval_prd.py} +84 -15
  177. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  178. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  179. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  180. package/templates/qoder/scripts/{autotest.py → validation/test/autotest.py} +1234 -1751
  181. package/templates/qoder/scripts/validation/test/autotest_auth.py +109 -0
  182. package/templates/qoder/scripts/{autotest_batch.py → validation/test/autotest_batch.py} +255 -224
  183. package/templates/qoder/scripts/validation/test/autotest_data.py +680 -0
  184. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  185. package/templates/qoder/scripts/{autotest_run.py → validation/test/autotest_run.py} +323 -297
  186. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  187. package/templates/qoder/scripts/{benchmark.py → validation/test/benchmark.py} +17 -5
  188. package/templates/qoder/scripts/{kg_auto_login.py → validation/test/kg_auto_login.py} +208 -196
  189. package/templates/qoder/scripts/{kg_test_runner.py → validation/test/kg_test_runner.py} +13 -2
  190. package/templates/qoder/scripts/{page_probe.py → validation/test/page_probe.py} +470 -459
  191. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  192. package/templates/qoder/settings.json +23 -2
  193. package/templates/qoder/skills/design-import/SKILL.md +235 -226
  194. package/templates/qoder/skills/design-review/SKILL.md +91 -82
  195. package/templates/qoder/skills/prd-generator/SKILL.md +38 -24
  196. package/templates/qoder/skills/prd-review/SKILL.md +8 -6
  197. package/templates/qoder/skills/prototype-generator/SKILL.md +296 -247
  198. package/templates/qoder/skills/spec-coder/SKILL.md +24 -15
  199. package/templates/qoder/skills/spec-generator/SKILL.md +24 -12
  200. package/templates/qoder/skills/test-generator/SKILL.md +9 -7
  201. package/templates/qoder/skills/wl-code/SKILL.md +25 -13
  202. package/templates/qoder/skills/wl-commit/SKILL.md +10 -9
  203. package/templates/qoder/skills/wl-design/SKILL.md +7 -5
  204. package/templates/qoder/skills/wl-init/SKILL.md +8 -8
  205. package/templates/qoder/skills/wl-insight/SKILL.md +25 -12
  206. package/templates/qoder/skills/wl-prd-full/SKILL.md +59 -8
  207. package/templates/qoder/skills/wl-prd-quick/SKILL.md +7 -7
  208. package/templates/qoder/skills/wl-prd-review/SKILL.md +18 -6
  209. package/templates/qoder/skills/wl-report/SKILL.md +27 -25
  210. package/templates/qoder/skills/wl-search/SKILL.md +151 -80
  211. package/templates/qoder/skills/wl-spec/SKILL.md +9 -7
  212. package/templates/qoder/skills/wl-status/SKILL.md +50 -18
  213. package/templates/qoder/skills/wl-task/SKILL.md +94 -12
  214. package/templates/qoder/skills/wl-test/SKILL.md +154 -45
  215. package/templates/qoder/templates/prd-full-template.md +7 -0
  216. package/templates/root/AGENTS.md +275 -237
  217. package/templates/root/requirements.txt +3 -0
  218. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  219. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  220. package/templates/qoder/hooks/inject-workflow-state.py +0 -169
  221. package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
  222. package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
  223. package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
  224. package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
  225. package/templates/qoder/scripts/check_carriers.py +0 -238
  226. package/templates/qoder/scripts/check_mcp.py +0 -298
  227. package/templates/qoder/scripts/check_mcp_launch.py +0 -183
  228. package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
  229. package/templates/qoder/scripts/collect_prds.py +0 -31
  230. package/templates/qoder/scripts/common/mentions.py +0 -134
  231. package/templates/qoder/scripts/common/utf8.py +0 -38
  232. package/templates/qoder/scripts/extract_api_params.py +0 -246
  233. package/templates/qoder/scripts/extract_routes.py +0 -54
  234. package/templates/qoder/scripts/extract_routes_tree.py +0 -78
  235. package/templates/qoder/scripts/handoff.py +0 -22
  236. package/templates/qoder/scripts/init_developer.py +0 -76
  237. package/templates/qoder/scripts/kg_mcp_server.py +0 -801
  238. package/templates/qoder/scripts/kg_semantic.py +0 -150
  239. package/templates/qoder/scripts/mcp_launcher.py +0 -414
  240. package/templates/qoder/scripts/mysql_mcp_server.py +0 -396
  241. package/templates/qoder/scripts/parse_prds.py +0 -33
  242. package/templates/qoder/scripts/role.py +0 -51
  243. package/templates/qoder/scripts/sync_carriers.py +0 -259
  244. package/templates/qoder/scripts/task.py +0 -1261
  245. package/templates/qoder/scripts/workspace_init.py +0 -102
  246. package/templates/qoder/scripts/zentao_mcp_server.py +0 -424
  247. package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
  248. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  249. /package/templates/qoder/{skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp → scripts/domain/__init__.py} +0 -0
  250. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  251. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  252. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  253. /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
- 所有 Qoder 产品(IDE 编辑器 / Quest 模式 / QoderWork 桌面端 / CLI)都支持两种触发方式:
40
- 1. **斜杠命令**:输入 `/wl-prd-full` `/wl-search` 等(commands 和 skills 都可用 `/` 触发)
41
- 2. **自然语言**:直接说"写个需求""查一下代码",AI 按下表自动映射到对应 skill
42
-
43
- > 注意:Quest/QoderWork `/` 会同时列出 commands(如 `/wl-prd-full`)和 skills(如 `/prd-generator`)。
44
- > 两者功能等价,优先用 `/wl-*` 命令(更稳定,参数提示更全)。
45
-
46
- | 用户说什么 | 触发的 skill/command |
47
- |------------|---------------------|
48
- | "写个需求""生成PRD""做个原型""新模块""保单这块加个功能" | `/wl-prd-full` prd-generator + prototype-generator |
49
- | "加个字段""加个导出按钮""改下文案""小改动" | `/wl-prd-quick`(快档,零星需求)|
50
- | "查一下""代码在哪""搜保险""考勤的API""字段含义" | `/wl-search` 或 wl-search |
51
- | "建任务""排优先级""这个任务做完了""开始做这个" | `/wl-task` wl-task |
52
- | "项目怎么样""进度""健康度""路线图" | `/wl-status` 或 wl-status |
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
- **🧩 提问润色(可选增强,纯 Qoder IDE/CLI 用,QoderWork 有 MCP 不需要)**:
79
- 非 MCP 环境下,用户输入含业务词时,可先跑 `python .qoder/scripts/enrich_prompt.py "<用户输入>"`
80
- <10ms 的极速关键词扩展,让用户确认后再走实际命令。MCP 环境下跳过(AI 直接调 MCP 工具已含扩展)。
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/kg.py context <业务关键词> --platform <web|app> [--role pm|design|dev|test] # 一次取全(首选, 秒级)
103
- python .qoder/scripts/search_index.py <关键词> --platform <web|app> # 单项搜索
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. **索引真找不到才 grep 兜底**:只有当 kg.py/search_index 明确返回"无匹配"或缺了某个具体点时,才回退到 Grep/Glob 直搜 —— 且**只搜 context 指向的范围**,不准对 data/code/(8973+ 文件)或整个 qoderAll 仓全库扫。
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
- **为什么这条是红线**:QoderWork 自带 Agent 默认会用 Grep/Glob 直搜文件系统,它不懂"业务源码在 data/code/ 镜像里",会搜整个 qoderAll 仓然后报"无匹配",被迫凭空编字段/API。**Agent 只准用来**:①读 context 列出的具体文件(Read)②**在 /wl-insight 里**做 web 调研(对标/竞品)和多视角探索脑暴。**绝不用 Agent 去"发现/搜索"代码——那是 kg.py 的活。** 注意:探索类 Agent 调研已集中到 `/wl-insight`,`/wl-prd-full` 不再派 Agent 做 web 搜索/脑暴。
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/eval_prd.py <prd> [原型]` 必须 PASS (≥80%)
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/team_sync.py push`
163
- - 拉取团队最新:`python .qoder/scripts/team_sync.py pull`
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/init_doctor.py`(加 --fix 自动修复,幂等增量,不会重头构建)
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"