@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
@@ -1,169 +0,0 @@
1
- # inject-workflow-state.py - Inject workflow state on every message
2
- import os, json, sys
3
-
4
- if sys.platform == 'win32':
5
- try:
6
- sys.stdout.reconfigure(encoding='utf-8')
7
- except Exception:
8
- pass
9
-
10
- NL = chr(10)
11
- BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
12
-
13
- # mtime 缓存 (审计 H5: 每条用户消息都跑这个 hook, 缓存避免重复读盘)
14
- # 格式: {filepath: (mtime, parsed_value)}
15
- _CACHE = {}
16
-
17
-
18
- def _append_hook_error(log_path, line):
19
- """写一行错误到 hook-errors.log, 超 100KB 截断保留最后 50KB (防无限膨胀)。
20
-
21
- best-effort: 任何 IO 失败都静默 (不能让日志写入失败导致 hook 二次崩溃)。
22
- """
23
- from datetime import datetime
24
- try:
25
- os.makedirs(os.path.dirname(log_path), exist_ok=True)
26
- # 轮转: 文件过大则截断
27
- if os.path.isfile(log_path):
28
- try:
29
- if os.path.getsize(log_path) > 100 * 1024:
30
- with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
31
- tail = f.read()[-50 * 1024:]
32
- with open(log_path, 'w', encoding='utf-8') as f:
33
- f.write(tail)
34
- except OSError:
35
- pass
36
- with open(log_path, 'a', encoding='utf-8') as f:
37
- f.write('[{}] inject-workflow-state: {}\n'.format(
38
- datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str(line)[:200]))
39
- except Exception:
40
- pass
41
-
42
-
43
- def _read_developer():
44
- """读当前开发者名 (容错: UTF-8/GBK)."""
45
- df = os.path.join(BASE, '.qoder', '.developer')
46
- if not os.path.isfile(df):
47
- return None
48
- for enc in ('utf-8', 'gbk', 'utf-8-sig'):
49
- try:
50
- with open(df, encoding=enc) as f:
51
- for line in f:
52
- line = line.strip()
53
- if line.startswith('name='):
54
- return line.split('=', 1)[1].strip()
55
- if line.startswith('name:') or ':' in line:
56
- # 容错 name: x 格式
57
- for sep in ('=', ':'):
58
- if sep in line:
59
- k, v = line.split(sep, 1)
60
- if k.strip() == 'name':
61
- return v.strip()
62
- except (OSError, UnicodeDecodeError):
63
- continue
64
- return None
65
-
66
-
67
- def _cached_read(path, parser):
68
- """带 mtime 缓存的文件读。parser: callable(content_str) -> value."""
69
- try:
70
- mtime = os.path.getmtime(path)
71
- except OSError:
72
- return None
73
- cached = _CACHE.get(path)
74
- if cached and cached[0] == mtime:
75
- return cached[1]
76
- # 重读
77
- for enc in ('utf-8', 'gbk', 'utf-8-sig'):
78
- try:
79
- with open(path, encoding=enc) as f:
80
- content = f.read()
81
- value = parser(content)
82
- _CACHE[path] = (mtime, value)
83
- return value
84
- except (OSError, UnicodeDecodeError):
85
- continue
86
- return None
87
-
88
-
89
- def get_status():
90
- # 优先读按开发者隔离的 current-task 文件
91
- dev = _read_developer()
92
- ct_candidates = []
93
- if dev:
94
- safe = ''.join(c for c in dev if c.isalnum() or c in '_-') or 'anon'
95
- ct_candidates.append(os.path.join(BASE, '.qoder', '.runtime', 'current-task.' + safe))
96
- ct_candidates.append(os.path.join(BASE, '.qoder', '.current-task')) # 旧格式回退
97
-
98
- rel = None
99
- for ct in ct_candidates:
100
- rel = _cached_read(ct, lambda c: c.strip() or None)
101
- if rel:
102
- break
103
- if not rel:
104
- return None, None
105
-
106
- tj = os.path.join(BASE, rel, 'task.json')
107
- if os.path.isfile(tj):
108
- d = _cached_read(tj, lambda c: json.loads(c) if c.strip() else None)
109
- if d:
110
- return rel, d.get('status', 'unknown')
111
- return rel, None
112
-
113
-
114
- # Statuses written by task.py: planning -> in_progress -> completed
115
- GUIDE = {
116
- None: 'No active task. Use /wl-prd-full to start, or /wl-task create.',
117
- 'planning': 'Task in planning. Use /wl-prd-full to write the PRD, then /wl-task start.',
118
- 'in_progress': 'Dev in progress. /wl-spec to generate spec, /wl-code to implement.',
119
- 'completed': 'Task completed. /wl-task archive to archive, or start the next one.',
120
- }
121
-
122
-
123
- def main():
124
- tp, st = get_status()
125
- # Hook-2: 如果任务/状态没变, 输出空 (减少 token 消耗)
126
- # 用 .runtime/.last-workflow-state 存上次输出, mtime 变了才重新注入
127
- state_file = os.path.join(BASE, '.qoder', '.runtime', '.last-wf-state.txt')
128
- current_sig = '{}|{}'.format(tp or '', st or '')
129
- try:
130
- os.makedirs(os.path.dirname(state_file), exist_ok=True)
131
- if os.path.isfile(state_file):
132
- last_sig = open(state_file, 'r', encoding='utf-8').read().strip()
133
- if last_sig == current_sig:
134
- # 状态没变, 不重复注入 (省 token), 但仍输出健康标记
135
- # QoderWork 看不到任何输出时分不清"缓存跳过"还是"hook 没跑"
136
- print('<!-- [hook-ok] inject-workflow-state: state unchanged (cached) -->')
137
- return
138
- open(state_file, 'w', encoding='utf-8').write(current_sig)
139
- except OSError:
140
- pass
141
-
142
- g = GUIDE.get(st, 'Status "{}" - check task.json, expected planning/in_progress/completed.'.format(st))
143
- parts = []
144
- parts.append('<qoder-workflow>')
145
- if tp:
146
- parts.append('Task: ' + tp)
147
- parts.append('Status: ' + str(st))
148
- parts.append('Do: ' + g)
149
- parts.append('</qoder-workflow>')
150
- # Hook-1: 健康标记 — 与 session-start 一致, QoderWork 看不到说明 hook 没生效
151
- parts.append('<!-- [hook-ok] inject-workflow-state hook ran -->')
152
- print(NL.join(parts))
153
-
154
-
155
- if __name__ == '__main__':
156
- try:
157
- main()
158
- except Exception as e:
159
- # 落盘到日志 (stdout 一次性, 追溯用)
160
- try:
161
- from pathlib import Path
162
- log_path = str(Path(__file__).resolve().parents[1] / '.runtime' / 'hook-errors.log')
163
- _append_hook_error(log_path, str(e)[:200])
164
- except Exception:
165
- pass
166
- print('<qoder-workflow>')
167
- print('hook error: ' + str(e)[:200])
168
- print(' (详情见 .qoder/.runtime/hook-errors.log)')
169
- print('</qoder-workflow>')
@@ -1,238 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- check_carriers.py - 四载体一致性守卫 (防 IDE/Quest/CLI/QoderWork 漂移)
5
-
6
- 背景:
7
- AGENTS.md 自己写了"改规则要同步更新它和 .qoder/rules/wl-pipeline.md"。
8
- 四个载体 (IDE commands / Quest skills / CLI AGENTS.md / QoderWork) 描述同一个
9
- 命令, 如果只改一个就漂移了 —— 比如 IDE 里 /wl-test 说要跑单测, 但 QoderWork 的
10
- skill 还停在旧版。目前没有任何机制发现, git status 里的 sync_templates.py M 状态
11
- 就是手工同步的证据。
12
-
13
- check_qoderwork_consistency.py 只查"裸相对路径"这一种退化。
14
- 本脚本查"内容一致性": 同名命令的描述是否对得上、AGENTS.md 命令表是否齐全。
15
-
16
- Usage:
17
- python .qoder/scripts/check_carriers.py
18
- python .qoder/scripts/check_carriers.py --strict # 警告也算失败
19
- Exit:
20
- 0 = 四载体一致
21
- 1 = 发现漂移 (errors)
22
- """
23
- import argparse
24
- import os
25
- import re
26
- import sys
27
-
28
- if sys.platform == 'win32':
29
- try:
30
- sys.stdout.reconfigure(encoding='utf-8')
31
- except (AttributeError, IOError):
32
- pass
33
-
34
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
35
- from common.paths import get_repo_root
36
-
37
- BASE = get_repo_root()
38
- COMMANDS_DIR = BASE / '.qoder' / 'commands'
39
- SKILLS_DIR = BASE / '.qoder' / 'skills'
40
- AGENTS_MD = BASE / 'AGENTS.md'
41
-
42
- # description 相似度阈值 (改了标点/措辞不算漂移, 实质改变才算)
43
- DESC_SIMILAR_THRESHOLD = 0.6
44
-
45
-
46
- def _read_yaml_frontmatter(path):
47
- """读 .md 的 YAML frontmatter, 返回 dict (无则空)。"""
48
- try:
49
- text = path.read_text(encoding='utf-8', errors='ignore')
50
- except OSError:
51
- return {}
52
- if not text.startswith('---'):
53
- return {}
54
- parts = text.split('---', 2)
55
- if len(parts) < 3:
56
- return {}
57
- fm = {}
58
- for line in parts[1].splitlines():
59
- line = line.strip()
60
- if ':' in line:
61
- k, _, v = line.partition(':')
62
- fm[k.strip()] = v.strip().strip('"\'')
63
- return fm
64
-
65
-
66
- def _similarity(a, b):
67
- """两字符串的相似度 (0~1), 用SequenceRatio。None/空 = 0。"""
68
- if not a or not b:
69
- return 0.0
70
- from difflib import SequenceMatcher
71
- return SequenceMatcher(None, a.lower(), b.lower()).ratio()
72
-
73
-
74
- def _prefix_consistent(cmd_desc, skill_desc):
75
- """判断 command description 是否是 skill description 的"精简前缀版本"。
76
-
77
- 设计意图: sync_carriers 把 skill 的 description 截断到触发词前, 写进 command。
78
- 所以 command = skill 的核心前缀, skill 后面挂触发词。这不是漂移, 是设计如此。
79
-
80
- 判定: command 是 skill 的前缀 (忽略大小写), 或 command 覆盖了 skill 的核心部分。
81
- 返回 True = 一致 (设计如此), False = 需进一步用相似度判断。
82
- """
83
- if not cmd_desc or not skill_desc:
84
- return False
85
- c, s = cmd_desc.lower().strip(), skill_desc.lower().strip()
86
- # 1. command 是 skill 的前缀 (最常见: sync 后 command = skill 截断版)
87
- if s.startswith(c):
88
- return True
89
- # 2. command 覆盖了 skill 的核心 (skill 去掉触发词后 == command)
90
- # 触发词标记 (和 sync_carriers._condense_description 对齐)
91
- for marker in ['。用户说', ';用户说', ' 用户说', '。触发:', ' 触发:', ';触发', '时触发']:
92
- idx = s.find(marker)
93
- if idx > 0:
94
- skill_core = s[:idx + 1] # 含句号/分号
95
- if skill_core.startswith(c) or c.startswith(skill_core):
96
- return True
97
- # 核心部分相似度高也算 (措辞微调)
98
- if _similarity(c, skill_core) >= 0.85:
99
- return True
100
- return False
101
-
102
-
103
- def check_command_skill_pairing():
104
- """校验 commands/wl-X.md 和 skills/wl-X/SKILL.md 的 description 是否一致。
105
- 返回 (errors, warnings)。
106
- """
107
- errors = []
108
- warnings = []
109
-
110
- # 收集所有 command (含 optional/ 子目录)
111
- commands = {}
112
- if COMMANDS_DIR.is_dir():
113
- for path in COMMANDS_DIR.rglob('*.md'):
114
- name = path.stem # wl-test
115
- commands[name] = path
116
-
117
- # 收集所有 skill
118
- skills = {}
119
- if SKILLS_DIR.is_dir():
120
- for d in SKILLS_DIR.iterdir():
121
- if d.is_dir() and (d / 'SKILL.md').is_file():
122
- skills[d.name] = d / 'SKILL.md'
123
-
124
- # 配对校验 (command 和 skill 同名才校验, 不同名是设计如此)
125
- common_names = set(commands) & set(skills)
126
- for name in sorted(common_names):
127
- cmd_fm = _read_yaml_frontmatter(commands[name])
128
- skill_fm = _read_yaml_frontmatter(skills[name])
129
- cmd_desc = cmd_fm.get('description', '')
130
- skill_desc = skill_fm.get('description', '')
131
- if not cmd_desc or not skill_desc:
132
- continue # 没写的跳过
133
- # 一致性判断: 不只用字符相似度, 还判断"command 是否是 skill 的精简前缀版本"
134
- # (sync_carriers 产生的就是这个关系: command = skill 去掉触发词后的核心描述)
135
- # 这样度量才符合设计意图, 不会把"故意精简"误报成漂移。
136
- if _prefix_consistent(cmd_desc, skill_desc):
137
- continue # command 是 skill 的精简版 = 一致, 通过
138
- sim = _similarity(cmd_desc, skill_desc)
139
- if sim < DESC_SIMILAR_THRESHOLD:
140
- errors.append((
141
- name,
142
- 'description 严重不一致 (相似度 %.0f%%, 阈值 %d%%)' % (sim * 100, DESC_SIMILAR_THRESHOLD * 100),
143
- 'command: ' + cmd_desc[:60],
144
- 'skill: ' + skill_desc[:60],
145
- ))
146
- elif sim < 0.85:
147
- warnings.append((
148
- name,
149
- 'description 有差异 (相似度 %.0f%%), 建议核对' % (sim * 100),
150
- ))
151
-
152
- return errors, warnings
153
-
154
-
155
- def check_agents_md_command_table():
156
- """校验 AGENTS.md 的 7 核心命令表与实际文件是否对得上。
157
- 表里列了但文件不存在 = 漂移 (改了命令没改文档, 或反之)。
158
- """
159
- errors = []
160
- if not AGENTS_MD.is_file():
161
- return [('AGENTS.md', 'AGENTS.md 不存在 (CLI 载体缺失)', '', '')]
162
- text = AGENTS_MD.read_text(encoding='utf-8', errors='ignore')
163
-
164
- # 提取"7 Core Commands"表里的命令名 (形如 `/wl-init`)
165
- # 表格行格式: | `/wl-init` | ... |
166
- table_cmds = set()
167
- in_core_table = False
168
- for line in text.splitlines():
169
- s = line.strip()
170
- if '7 Core' in line or '核心命令' in line:
171
- in_core_table = True
172
- if in_core_table:
173
- for m in re.finditer(r'`(/wl-[a-z-]+)`', s):
174
- table_cmds.add(m.group(1)[1:]) # 去掉 / 前缀
175
- if in_core_table and s.startswith('|') and '工序站' in text and s.count('|') >= 2:
176
- # 表还在继续
177
- pass
178
-
179
- # 校验每个表里的命令文件存在
180
- for cmd in sorted(table_cmds):
181
- cmd_file = COMMANDS_DIR / (cmd + '.md')
182
- skill_file = SKILLS_DIR / cmd / 'SKILL.md'
183
- if not cmd_file.is_file() and not skill_file.is_file():
184
- errors.append((
185
- cmd,
186
- 'AGENTS.md 命令表列了 /%s 但 commands/ 和 skills/ 都没文件' % cmd,
187
- '',
188
- '',
189
- ))
190
-
191
- return errors
192
-
193
-
194
- def main():
195
- ap = argparse.ArgumentParser(description='四载体一致性守卫 (防漂移)')
196
- ap.add_argument('--strict', action='store_true', help='警告也算失败')
197
- args = ap.parse_args()
198
-
199
- print('=' * 64)
200
- print('四载体一致性检查 (IDE commands / Quest skills / CLI AGENTS.md / QoderWork)')
201
- print('=' * 64)
202
-
203
- pair_errors, pair_warnings = check_command_skill_pairing()
204
- table_errors = check_agents_md_command_table()
205
-
206
- all_errors = pair_errors + table_errors
207
-
208
- if pair_warnings:
209
- print('\n🟡 描述差异 (建议核对, --strict 才算失败):')
210
- for name, msg, *_ in pair_warnings:
211
- print(' %s: %s' % (name, msg))
212
-
213
- if all_errors:
214
- print('\n🔴 载体漂移 (必修):')
215
- for item in all_errors:
216
- name = item[0]
217
- print(' %s' % name)
218
- for detail in item[1:]:
219
- if detail:
220
- print(' %s' % detail)
221
-
222
- if not all_errors and not pair_warnings:
223
- print('✅ 四载体一致: 同名命令描述对得上, AGENTS.md 命令表齐全。')
224
-
225
- print('-' * 64)
226
- print('统计: 漂移 %d / 差异 %d' % (len(all_errors), len(pair_warnings)))
227
- print('提示: 与 check_qoderwork_consistency.py 互补 ——')
228
- print(' 那个查裸相对路径, 这个查内容一致性。')
229
-
230
- if all_errors:
231
- return 1
232
- if args.strict and pair_warnings:
233
- return 1
234
- return 0
235
-
236
-
237
- if __name__ == '__main__':
238
- sys.exit(main())