@hupan56/wlkj 3.0.0 → 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 (237) hide show
  1. package/bin/cli.js +1435 -1373
  2. package/package.json +1 -1
  3. package/templates/qoder/commands/optional/wl-insight.md +6 -4
  4. package/templates/qoder/commands/optional/wl-report.md +20 -39
  5. package/templates/qoder/commands/optional/wl-status.md +4 -4
  6. package/templates/qoder/commands/wl-code.md +2 -2
  7. package/templates/qoder/commands/wl-design.md +5 -5
  8. package/templates/qoder/commands/wl-init.md +3 -3
  9. package/templates/qoder/commands/wl-prd.md +26 -13
  10. package/templates/qoder/commands/wl-req.md +3 -3
  11. package/templates/qoder/commands/wl-search.md +78 -47
  12. package/templates/qoder/commands/wl-task.md +343 -40
  13. package/templates/qoder/commands/wl-test.md +10 -10
  14. package/templates/qoder/config.yaml +35 -13
  15. package/templates/qoder/hooks/post-tool-use.py +181 -0
  16. package/templates/qoder/hooks/session-start.py +82 -1
  17. package/templates/qoder/hooks/stop-eval.py +207 -0
  18. package/templates/qoder/hooks/user-prompt-submit.py +139 -0
  19. package/templates/qoder/rules/wl-pipeline.md +70 -8
  20. package/templates/qoder/scripts/README.md +78 -115
  21. package/templates/qoder/scripts/capability/__init__.py +26 -0
  22. package/templates/qoder/scripts/capability/__main__.py +72 -0
  23. package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
  24. package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
  25. package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
  26. package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
  27. package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
  28. package/templates/qoder/scripts/capability/caps/context.py +36 -0
  29. package/templates/qoder/scripts/capability/caps/cron.py +50 -0
  30. package/templates/qoder/scripts/capability/caps/identity.py +43 -0
  31. package/templates/qoder/scripts/capability/caps/memory.py +71 -0
  32. package/templates/qoder/scripts/capability/caps/notify.py +41 -0
  33. package/templates/qoder/scripts/capability/caps/present.py +48 -0
  34. package/templates/qoder/scripts/capability/caps/repo.py +31 -0
  35. package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
  36. package/templates/qoder/scripts/capability/chain.py +92 -0
  37. package/templates/qoder/scripts/capability/memory_chain.py +41 -0
  38. package/templates/qoder/scripts/capability/registry.py +246 -0
  39. package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
  40. package/templates/qoder/scripts/capability/smoke_test.py +211 -0
  41. package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
  42. package/templates/qoder/scripts/{report → deployment/setup}/__init__.py +11 -11
  43. package/templates/qoder/scripts/{setup → deployment/setup}/carriers.py +33 -26
  44. package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
  45. package/templates/qoder/scripts/{setup → deployment/setup}/init_doctor.py +40 -16
  46. package/templates/qoder/scripts/{setup → deployment/setup}/install_qoderwork.py +20 -12
  47. package/templates/qoder/scripts/{setup → deployment/setup}/platform_doctor.py +8 -2
  48. package/templates/qoder/scripts/{setup → deployment/setup}/repo_root.py +113 -112
  49. package/templates/qoder/scripts/{setup → deployment/setup}/setup.py +31 -11
  50. package/templates/qoder/scripts/{setup → deployment/setup}/setup_lanhu.py +13 -7
  51. package/templates/qoder/scripts/domain/__init__.py +0 -0
  52. package/templates/qoder/scripts/domain/design/__init__.py +0 -0
  53. package/templates/qoder/scripts/{report → domain/design}/fill_prototype.py +12 -6
  54. package/templates/qoder/scripts/{report → domain/design}/gen_design_doc.py +8 -2
  55. package/templates/qoder/scripts/{setup → domain/kg}/__init__.py +11 -11
  56. package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
  57. package/templates/qoder/scripts/{kg → domain/kg/build}/build_entity_registry.py +7 -2
  58. package/templates/qoder/scripts/{kg → domain/kg/build}/build_relations.py +7 -2
  59. package/templates/qoder/scripts/{kg → domain/kg/build}/build_style_index.py +13 -7
  60. package/templates/qoder/scripts/{kg → domain/kg/build}/build_workflows.py +7 -2
  61. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build.py +76 -9
  62. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build_db.py +7 -2
  63. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_incremental.py +11 -6
  64. package/templates/qoder/scripts/{report → domain/kg/build}/learn_aggregate.py +9 -4
  65. package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
  66. package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
  67. package/templates/qoder/scripts/{common → domain/kg/extract}/test_extract.py +115 -115
  68. package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
  69. package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
  70. package/templates/qoder/scripts/{kg → domain/kg/graph}/kg_link_db.py +7 -2
  71. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
  72. package/templates/qoder/scripts/{kg → domain/kg}/kg.py +263 -30
  73. package/templates/qoder/scripts/{common → domain/kg}/kg_capabilities.py +182 -182
  74. package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
  75. package/templates/qoder/scripts/{kg → domain/kg/search}/context_pack.py +30 -9
  76. package/templates/qoder/scripts/{kg → domain/kg/search}/enrich_prompt.py +10 -4
  77. package/templates/qoder/scripts/{kg → domain/kg/search}/prefetch.py +33 -8
  78. package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
  79. package/templates/qoder/scripts/{kg → domain/kg/search}/search_index.py +143 -77
  80. package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
  81. package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
  82. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  83. package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
  84. package/templates/qoder/scripts/{kg → domain/kg/storage}/kg_duckdb.py +38 -14
  85. package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
  86. package/templates/qoder/scripts/{report → domain/learning}/learn.py +8 -3
  87. package/templates/qoder/scripts/{mcp → domain/report}/__init__.py +11 -11
  88. package/templates/qoder/scripts/{report → domain/report}/add_session.py +9 -3
  89. package/templates/qoder/scripts/{report → domain/report}/export.py +10 -5
  90. package/templates/qoder/scripts/{report → domain/report}/report.py +9 -4
  91. package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
  92. package/templates/qoder/scripts/{report → domain/report}/role.py +10 -4
  93. package/templates/qoder/scripts/{report → domain/report}/status.py +112 -9
  94. package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
  95. package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
  96. package/templates/qoder/scripts/{report → domain/requirement}/archive_prd.py +8 -2
  97. package/templates/qoder/scripts/{report → domain/requirement}/req.py +228 -222
  98. package/templates/qoder/scripts/{kg → domain/task}/__init__.py +11 -11
  99. package/templates/qoder/scripts/{task → domain/task}/git_sync.py +40 -21
  100. package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
  101. package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
  102. package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
  103. package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
  104. package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
  105. package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
  106. package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
  107. package/templates/qoder/scripts/foundation/__init__.py +0 -0
  108. package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
  109. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  110. package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
  111. package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
  112. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
  113. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  114. package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
  115. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  116. package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
  117. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
  118. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  119. package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
  120. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
  121. package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
  122. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
  123. package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
  124. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  125. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  126. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +2 -2
  127. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  128. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  129. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  130. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  131. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  132. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  133. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  134. package/templates/qoder/scripts/{common → foundation/protocol}/mcp_base.py +1 -1
  135. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  136. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  137. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  138. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  139. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  140. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  141. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  142. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  143. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  144. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  145. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  146. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  147. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  148. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  149. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  150. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  151. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  152. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  153. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  154. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  155. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  156. package/templates/qoder/scripts/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
  157. package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
  158. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  159. package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
  160. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  161. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  162. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  163. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  164. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  165. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  166. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  167. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  168. package/templates/qoder/scripts/validation/__init__.py +0 -0
  169. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  170. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  171. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  172. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  173. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  174. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  175. package/templates/qoder/scripts/{report → validation/metrics}/eval_prd.py +17 -10
  176. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  177. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  178. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  179. package/templates/qoder/scripts/{test → validation/test}/autotest.py +64 -4
  180. package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
  181. package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
  182. package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
  183. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  184. package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
  185. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  186. package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
  187. package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
  188. package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
  189. package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
  190. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  191. package/templates/qoder/settings.json +30 -0
  192. package/templates/qoder/skills/design-import/SKILL.md +22 -13
  193. package/templates/qoder/skills/design-review/SKILL.md +19 -10
  194. package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
  195. package/templates/qoder/skills/prd-review/SKILL.md +6 -4
  196. package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
  197. package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
  198. package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
  199. package/templates/qoder/skills/test-generator/SKILL.md +7 -5
  200. package/templates/qoder/skills/wl-code/SKILL.md +23 -11
  201. package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
  202. package/templates/qoder/skills/wl-design/SKILL.md +5 -3
  203. package/templates/qoder/skills/wl-init/SKILL.md +6 -6
  204. package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
  205. package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
  206. package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
  207. package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
  208. package/templates/qoder/skills/wl-report/SKILL.md +25 -23
  209. package/templates/qoder/skills/wl-search/SKILL.md +149 -78
  210. package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
  211. package/templates/qoder/skills/wl-status/SKILL.md +48 -16
  212. package/templates/qoder/skills/wl-task/SKILL.md +92 -10
  213. package/templates/qoder/skills/wl-test/SKILL.md +75 -29
  214. package/templates/qoder/templates/prd-full-template.md +7 -0
  215. package/templates/root/AGENTS.md +275 -236
  216. package/templates/root/requirements.txt +3 -0
  217. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  218. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  219. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
  220. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
  221. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
  222. package/templates/.qoder/learning/eval-history.jsonl +0 -28
  223. package/templates/data/index/wiki-index.json +0 -8
  224. package/templates/qoder/nul +0 -4
  225. package/templates/qoder/scripts/kg/extract.py +0 -398
  226. package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
  227. package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
  228. package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
  229. package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
  230. package/templates/qoder/scripts/task/__init__.py +0 -11
  231. package/templates/qoder/scripts/test/__init__.py +0 -11
  232. package/templates/qoder/scripts/wlkj.py +0 -116
  233. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  234. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  235. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  236. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  237. /package/templates/qoder/scripts/{test → validation/test}/secure-ls.js +0 -0
@@ -11,7 +11,7 @@ roles:
11
11
  pm:
12
12
  name: 产品经理
13
13
  permissions: [create_task, write_prd, approve_prd, review_spec]
14
- commands: [wl-init, wl-insight, wl-prd-full, wl-prd-quick, wl-prd-review, wl-design, wl-task, wl-search, wl-status, wl-report]
14
+ commands: [wl-init, wl-insight, wl-prd, wl-design, wl-task, wl-search, wl-status, wl-report]
15
15
  can_produce: [prd, prototype]
16
16
  can_consume: ["*"] # PM 需读代码做现状分析
17
17
  design:
@@ -48,20 +48,42 @@ pipeline:
48
48
  auto_spec_on_prd_push: true
49
49
  req_id_start: 1
50
50
 
51
- # ── MySQL 只读 MCP (QAS 测试环境, 团队共享) ──
51
+ # ── MySQL 只读 MCP (团队共享, 多环境) ──
52
+ # AI 查数据库时会先问"查哪个环境",用这里的标签让用户选。
53
+ # 标签名自定义 (test/pre/prod 或 qa/staging...),对应 environments 下的 key。
54
+ # 没配 environments (旧扁平结构) 时兼容:当作只有 default_env 一个环境。
52
55
  mysql:
53
- host: 10.54.6.9
54
- port: 3306
55
- user: fywl_ics_test
56
- password: Fywl_ics_test56476
57
- env_name: "QAS 测试环境"
56
+ default_env: test # AI 不指定 / 用户不选时的默认环境
57
+ environments:
58
+ test: # 测试环境
59
+ host: 10.54.6.9
60
+ port: 3306
61
+ user: fywl_ics_test
62
+ password: Fywl_ics_test56476
63
+ env_name: "QAS 测试环境" # 显示标签,会随查询结果返回提醒 AI 当前在哪个环境
64
+ pre: # 预生产环境 (按需填, 不用可整段删)
65
+ host: 10.54.6.10
66
+ port: 3306
67
+ user: fywl_ics_pre_ro
68
+ password: ""
69
+ env_name: "预生产环境"
70
+ prod: # 生产环境 (⚠️ 只读账号! 配了才能查, 查时 AI 会标红提醒)
71
+ host: fywl-mysql.rwlb.rds.aliyuncs.com
72
+ port: 3306
73
+ user: queryuser
74
+ password: "query2025_ics@infore"
75
+ env_name: "生产环境"
58
76
 
59
- # ── 禅道 MCP (自建内网, 团队共享) ──
60
- # /wl-task 对接禅道: 创建需求/查任务/跟踪状态
77
+ # ── 禅道 MCP (自建内网) ──
78
+ # v3.1.4: 禅道是【每人一个账号】(非团队共享)。
79
+ # - url (服务地址): 团队共享, 留这里。
80
+ # - user/password (个人凭据): 不放这里 (进 git 会泄露)。
81
+ # 每人各自填 workspace/members/{我}/.private/secrets/zentao.env:
82
+ # ZENTAO_USER=<你的禅道账号>
83
+ # ZENTAO_PASSWORD=<你的禅道密码>
84
+ # - 账号名映射: member.json 的 zentao_account (进 git, 用于归属过滤)。
61
85
  zentao:
62
86
  url: http://10.89.2.91/zentao
63
- user: hupan
64
- password: Aa123456
65
87
 
66
88
  # ── Platforms (知识图谱/搜索的平台映射, 换项目改这里即可) ──
67
89
  # search_index.py --platform <key或alias> 会过滤到对应 project 目录
@@ -105,14 +127,14 @@ team_remote:
105
127
 
106
128
  # ── Git Sync (Weekly) ──
107
129
  # 每个项目在 data/code/ 下有自己的 git 仓库
108
- # 运行: python .qoder/scripts/git_sync.py
130
+ # 运行: python .qoder/scripts/orchestration/wlkj.py git-sync
109
131
  # 或设 cron: 每周五 18:00
110
132
  #
111
133
  # ★ 源码仓库配置 (重要) ★
112
134
  # 把下面的 url 改成你团队的 git 地址 (Gitea/GitLab/GitHub 均可)。
113
135
  # branch 改成你的主分支。
114
136
  # 不知道填什么? 注释掉整个 projects 段, 手动把代码放到 data/code/<项目名>/ 即可。
115
- # 填完后跑: python .qoder/scripts/setup.py 会自动克隆+建索引。
137
+ # 填完后跑: python .qoder/scripts/orchestration/wlkj.py doctor --fix 会自动克隆+建索引。
116
138
  git_sync:
117
139
  schedule: "0 18 * * 5"
118
140
  projects:
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """post-tool-use.py - PostToolUse hook: 工具执行后自动埋点 (确定性, 治漏斗断链)
4
+
5
+ 吸收宿主确定性 hook 能力: 把"埋点"从"靠 AI 手动调 learn record"(老忘 → F2 漏斗数据残缺)
6
+ 升级为"宿主在工具执行后自动埋点"。AI 想忘都忘不了。
7
+
8
+ 触发场景 (只埋这几类, 不滥用):
9
+ - Write/Edit 落到 **/prd*.md 或 **/PRD*.md → 记 prd_written
10
+ - Write/Edit/MultiEdit 落到 **/task.json → 记 task_created
11
+ - Bash 跑了 git commit / team_sync push → 记 commit_done (兜底, 主路径在 team_sync.py)
12
+
13
+ 铁律:
14
+ - 永远 exit 0 (埋点绝不阻断工作流)
15
+ - 失败 try/except 静默 (hook 崩溃绝不阻塞会话)
16
+ - 幂等: 同一文件短时间内重复埋点靠 feedback.jsonl append 即可 (统计层去重)
17
+
18
+ 阻断契约: exit 0 放行 (本 hook 不阻断)。
19
+ stdin 字段名官方未公开, 兼容 tool_name/tool_input 等多种写法。
20
+ """
21
+ import json
22
+ import os
23
+ import re
24
+ import sys
25
+ from datetime import datetime
26
+
27
+ if sys.platform == 'win32':
28
+ try:
29
+ sys.stdout.reconfigure(encoding='utf-8')
30
+ sys.stderr.reconfigure(encoding='utf-8')
31
+ except Exception:
32
+ pass
33
+
34
+ BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
35
+ HOOK_ERRORS_LOG = os.path.join(BASE, '.qoder', '.runtime', 'hook-errors.log')
36
+
37
+ # 工具名: 宿主原生工具(大写) + 兼容别名
38
+ WRITE_TOOLS = {'write', 'edit', 'multiedit', 'str_replace_editor', 'create_file'}
39
+ COMMIT_RE = re.compile(r'git\s+commit|team_sync.*push|wlkj\.py\s+sync\s+push', re.IGNORECASE)
40
+
41
+
42
+ def _read_tool_info():
43
+ """从 stdin 读 {tool_name, tool_input}。兼容多种字段名。取不到返回 ({}, None)。"""
44
+ try:
45
+ if sys.stdin.isatty():
46
+ return {}, None
47
+ raw = sys.stdin.read().strip()
48
+ except Exception:
49
+ return {}, None
50
+ if not raw:
51
+ return {}, None
52
+ try:
53
+ data = json.loads(raw)
54
+ except (ValueError, TypeError):
55
+ return {}, None
56
+ if not isinstance(data, dict):
57
+ return {}, None
58
+ # tool_name 兼容
59
+ tool_name = None
60
+ for k in ('tool_name', 'name', 'tool'):
61
+ v = data.get(k)
62
+ if isinstance(v, str) and v.strip():
63
+ tool_name = v.strip()
64
+ break
65
+ # tool_input 兼容
66
+ tool_input = data.get('tool_input') or data.get('input') or data.get('args') or data.get('arguments')
67
+ if not isinstance(tool_input, dict):
68
+ tool_input = {}
69
+ return tool_input, tool_name
70
+
71
+
72
+ def _file_paths_from_input(tool_input, tool_name):
73
+ """从 tool_input 提取涉及的文件路径列表。兼容 Write/Edit/Bash 的不同参数名。"""
74
+ paths = []
75
+ for k in ('file_path', 'path', 'filename', 'filePath'):
76
+ v = tool_input.get(k)
77
+ if isinstance(v, str) and v:
78
+ paths.append(v)
79
+ # MultiEdit 可能多文件
80
+ for k in ('paths', 'files'):
81
+ v = tool_input.get(k)
82
+ if isinstance(v, list):
83
+ paths.extend(x for x in v if isinstance(x, str))
84
+ return paths
85
+
86
+
87
+ def _cmd_from_input(tool_input):
88
+ """Bash 工具的命令文本。"""
89
+ for k in ('command', 'cmd'):
90
+ v = tool_input.get(k)
91
+ if isinstance(v, str):
92
+ return v
93
+ return ''
94
+
95
+
96
+ def _dev():
97
+ """读当前开发者。失败返回 None。"""
98
+ df = os.path.join(BASE, '.qoder', '.developer')
99
+ try:
100
+ if not os.path.isfile(df):
101
+ return None
102
+ for line in open(df, encoding='utf-8'):
103
+ if 'name=' in line:
104
+ return line.split('name=', 1)[1].strip().split()[0]
105
+ except Exception:
106
+ pass
107
+ return None
108
+
109
+
110
+ def _append_event(event, data):
111
+ """best-effort append 一条事件到当前 dev 的 feedback.jsonl。避开 learn import 断链, 直接写文件。"""
112
+ try:
113
+ dev = _dev()
114
+ if not dev:
115
+ return
116
+ fb = os.path.join(BASE, 'workspace', 'members', dev, 'journal', 'feedback.jsonl')
117
+ os.makedirs(os.path.dirname(fb), exist_ok=True)
118
+ entry = {
119
+ 'ts': datetime.now().isoformat(timespec='seconds'),
120
+ 'dev': dev,
121
+ 'event': event,
122
+ 'data': data,
123
+ '_source': 'post-tool-use-hook', # 标记来源, 区别于 AI 主动埋点
124
+ }
125
+ with open(fb, 'a', encoding='utf-8') as f:
126
+ f.write(json.dumps(entry, ensure_ascii=False) + '\n')
127
+ except Exception:
128
+ pass # 铁律: 埋点失败绝不阻塞
129
+
130
+
131
+ def main():
132
+ try:
133
+ tool_input, tool_name = _read_tool_info()
134
+ tn = (tool_name or '').lower()
135
+
136
+ # 1. Write/Edit 落到 PRD / task.json → 埋点
137
+ if tn in WRITE_TOOLS or tn == 'bash':
138
+ paths = _file_paths_from_input(tool_input, tool_name)
139
+ for p in paths:
140
+ plow = p.lower().replace('\\', '/')
141
+ if 'prd' in os.path.basename(plow).lower() and plow.endswith('.md'):
142
+ _append_event('prd_written', {'prd': os.path.basename(p), 'tool': tool_name})
143
+ if plow.endswith('task.json'):
144
+ _append_event('task_touched', {'task_json': os.path.basename(os.path.dirname(p)), 'tool': tool_name})
145
+ # v3.1 软提示: prototype HTML 写完但缺来源锚定 → stderr 提醒(不阻断)
146
+ if 'prototype' in os.path.basename(plow) and plow.endswith('.html'):
147
+ try:
148
+ if os.path.isfile(p):
149
+ with open(p, encoding='utf-8', errors='ignore') as _f:
150
+ _ht = _f.read()[:8192]
151
+ import re as _re
152
+ if not _re.search(r'@prototype-source:|x-source-pages', _ht):
153
+ sys.stderr.write(
154
+ '[prototype 提醒] %s 缺来源锚定注释。\n'
155
+ ' → 在 <head> 加一行: <!-- @prototype-source: fywl-ui/.../index.vue -->\n'
156
+ ' → 没做这一步 = 没读真实代码就手抄模板, eval A2 会扣 5 分。\n' % os.path.basename(p))
157
+ except Exception:
158
+ pass # 软提示, 失败静默
159
+
160
+ # 2. Bash 跑了 commit/push → 兜底埋点 commit_done
161
+ if tn == 'bash':
162
+ cmd = _cmd_from_input(tool_input)
163
+ if cmd and COMMIT_RE.search(cmd):
164
+ _append_event('commit_done', {'source': 'post-tool-use-hook', 'cmd': cmd[:120]})
165
+
166
+ sys.exit(0) # 永远放行
167
+ except SystemExit:
168
+ raise
169
+ except Exception as e:
170
+ try:
171
+ os.makedirs(os.path.dirname(HOOK_ERRORS_LOG), exist_ok=True)
172
+ with open(HOOK_ERRORS_LOG, 'a', encoding='utf-8') as f:
173
+ f.write('[{}] post-tool-use: {}\n'.format(
174
+ datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str(e)[:200]))
175
+ except Exception:
176
+ pass
177
+ sys.exit(0)
178
+
179
+
180
+ if __name__ == '__main__':
181
+ main()
@@ -198,7 +198,8 @@ def get_index_info():
198
198
  # 检查是否有未标注的模块 (提示AI做语义标注)
199
199
  try:
200
200
  import subprocess as _sp
201
- r = _sp.run([sys.executable, os.path.join(BASE, '.qoder', 'scripts', 'kg_semantic.py'), '--check'],
201
+ # v3.x: kg_semantic.py 已迁移到 domain/kg/graph/ (旧扁平路径不存在)
202
+ r = _sp.run([sys.executable, os.path.join(BASE, '.qoder', 'scripts', 'domain', 'kg', 'graph', 'kg_semantic.py'), '--check'],
202
203
  capture_output=True, text=True, encoding='utf-8', timeout=5)
203
204
  if r.stdout and 'semantic_labeling_needed' in r.stdout:
204
205
  import json as _json
@@ -245,6 +246,81 @@ def get_style_info():
245
246
  return NL.join(lines)
246
247
 
247
248
 
249
+ def get_usability_summary():
250
+ """读工作流可用性指标, 把"不达标"项注入会话上下文。
251
+
252
+ 吸收宿主 SessionStart hook 能力: 不用等用户跑 /wl-status, 每次开会话主动
253
+ 暴露"工作流用没用起来"的红灯项。只展示红/黄 (绿的不打扰), 无数据则不显示。
254
+ (与 usability_score.py 同源逻辑, 但 hook 内联精简版, 避免 import 验证脚本拖慢启动)
255
+ """
256
+ lines = []
257
+ try:
258
+ # U2 一次过审率 (从 eval-history 取首条)
259
+ eval_path = os.path.join(BASE, 'data', 'learning', 'eval-history.jsonl')
260
+ records = []
261
+ if os.path.isfile(eval_path):
262
+ with open(eval_path, encoding='utf-8', errors='replace') as f:
263
+ for ln in f:
264
+ ln = ln.strip()
265
+ if ln:
266
+ try:
267
+ records.append(json.loads(ln))
268
+ except Exception:
269
+ continue
270
+ # 取最近 7 天
271
+ from datetime import datetime, timedelta
272
+ cutoff = datetime.now() - timedelta(days=7)
273
+ recent = []
274
+ for r in records:
275
+ t = r.get('time', '')
276
+ try:
277
+ if datetime.fromisoformat(str(t).split('.')[0]) >= cutoff:
278
+ recent.append(r)
279
+ except Exception:
280
+ continue
281
+
282
+ issues = [] # 只收红/黄
283
+ if recent:
284
+ passed = sum(1 for r in recent if r.get('passed'))
285
+ rate = 100.0 * passed / len(recent)
286
+ if rate < 60: # 黄线 30, 红线 60
287
+ tag = '🔴' if rate < 30 else '🟡'
288
+ issues.append('%s 一次过审率 %.0f%% (<60%% 目标) — PRD 质量需提升' % (tag, rate))
289
+
290
+ # U4 规则违反率 (从 feedback)
291
+ fb_dir = os.path.join(BASE, 'workspace', 'members')
292
+ violations = 0
293
+ prds_gen = 0
294
+ if os.path.isdir(fb_dir):
295
+ for dev in os.listdir(fb_dir):
296
+ fb = os.path.join(fb_dir, dev, 'journal', 'feedback.jsonl')
297
+ if not os.path.isfile(fb):
298
+ continue
299
+ try:
300
+ with open(fb, encoding='utf-8', errors='replace') as f:
301
+ for ln in f:
302
+ if '"rule_violation"' in ln:
303
+ violations += 1
304
+ elif '"prd_accepted"' in ln:
305
+ prds_gen += 1
306
+ except Exception:
307
+ continue
308
+ if prds_gen and violations:
309
+ vrate = 100.0 * violations / prds_gen
310
+ if vrate > 10:
311
+ tag = '🔴' if vrate > 25 else '🟡'
312
+ issues.append('%s 规则违反率 %.0f%% (>10%% 目标) — 关键规则没拦住' % (tag, vrate))
313
+
314
+ if issues:
315
+ lines.append('## Workflow Usability (近7天)')
316
+ lines.append('工作流"用不用得起来"红灯 (详情: wlkj.py usability):')
317
+ for it in issues:
318
+ lines.append(' ' + it)
319
+ except Exception:
320
+ pass # hook 失败绝不阻塞会话
321
+ return NL.join(lines) if lines else ''
322
+
323
+
248
324
  def main():
249
325
  parts = []
250
326
  parts.append('<qoder-context>')
@@ -273,6 +349,11 @@ def main():
273
349
  for t in tasks:
274
350
  parts.append(' [' + t['status'] + '] ' + t['name'] + ' - ' + t['title'])
275
351
 
352
+ usability = get_usability_summary()
353
+ if usability:
354
+ parts.append('')
355
+ parts.append(usability)
356
+
276
357
  parts.append('')
277
358
 
278
359
  idx_info = get_index_info()
@@ -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()