@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,281 +1,292 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- report.py - 工作日报/周报生成 + 飞书推送 (阶段 D3)
5
-
6
- 支撑 /wl-report 命令的数据生成。AI 执行 markdown 指令, 本脚本提供数据。
7
-
8
- Usage:
9
- python report.py daily # 生成今日日报 (打印)
10
- python report.py weekly # 生成本周周报 (打印)
11
- python report.py daily --push-feishu # 生成并推送到飞书
12
- python report.py weekly --push-feishu
13
-
14
- 输出: 中文格式, 保存到 workspace/members/{dev}/journal/
15
- """
16
-
17
- import argparse
18
- import os
19
- import subprocess
20
- import sys
21
- from datetime import datetime, date, timedelta
22
- from pathlib import Path
23
-
24
- try:
25
- sys.stdout.reconfigure(encoding='utf-8', errors='replace')
26
- except (AttributeError, TypeError, OSError):
27
- pass
28
-
29
- THIS_DIR = os.path.dirname(os.path.abspath(__file__))
30
- sys.path.insert(0, THIS_DIR)
31
- from common.paths import get_repo_root, get_developer, get_tasks_dir
32
- from common.task_utils import load_task_json
33
-
34
- BASE = get_repo_root()
35
-
36
-
37
- def _ensure_developer():
38
- """必须有开发者身份。"""
39
- dev = get_developer(BASE)
40
- if not dev:
41
- print('[ERROR] 未设置开发者身份。先跑 /wl-init 注册。', file=sys.stderr)
42
- sys.exit(4)
43
- return dev
44
-
45
-
46
- def _git_commits(dev: str, since_date: str) -> list:
47
- """获取开发者指定日期后的 git 提交。"""
48
- try:
49
- r = subprocess.run(
50
- ['git', 'log', '--author', dev, '--since', since_date,
51
- '--pretty=format:%h|%s|%ar', '--no-merges'],
52
- capture_output=True, text=True, encoding='utf-8', errors='replace',
53
- cwd=str(BASE),
54
- )
55
- if r.returncode != 0:
56
- return []
57
- commits = []
58
- for line in r.stdout.strip().splitlines():
59
- parts = line.split('|', 2)
60
- if len(parts) == 3:
61
- commits.append({'hash': parts[0], 'msg': parts[1], 'when': parts[2]})
62
- return commits
63
- except Exception:
64
- return []
65
-
66
-
67
- def _my_tasks(dev: str, statuses=None) -> list:
68
- """获取我的任务。"""
69
- tasks_dir = get_tasks_dir(BASE)
70
- if not tasks_dir.is_dir():
71
- return []
72
- out = []
73
- for d in tasks_dir.iterdir():
74
- if not d.is_dir():
75
- continue
76
- data = load_task_json(d)
77
- if not data:
78
- continue
79
- if dev not in (data.get('assignee'), data.get('creator')):
80
- continue
81
- if statuses and data.get('status') not in statuses:
82
- continue
83
- out.append({'name': d.name, 'data': data})
84
- return out
85
-
86
-
87
- def _finished_today(dev: str) -> list:
88
- """今天完成的任务 (从 stage_ts.completed 判断)。"""
89
- today_str = date.today().isoformat()
90
- out = []
91
- tasks_dir = get_tasks_dir(BASE)
92
- # 也查归档 (今天 finish 的会归档)
93
- archive_dir = BASE / '.qoder' / 'archive'
94
- search_dirs = [tasks_dir, archive_dir] if archive_dir.is_dir() else [tasks_dir]
95
- for sd in search_dirs:
96
- if not sd.is_dir():
97
- continue
98
- for d in sd.iterdir():
99
- if not d.is_dir():
100
- continue
101
- data = load_task_json(d)
102
- if not data:
103
- continue
104
- if dev not in (data.get('assignee'), data.get('creator')):
105
- continue
106
- completed_ts = (data.get('stage_ts') or {}).get('completed') or data.get('updated_at', '')
107
- if completed_ts.startswith(today_str):
108
- out.append({'name': d.name, 'data': data})
109
- return out
110
-
111
-
112
- def generate_daily(dev: str) -> str:
113
- """生成日报 markdown。"""
114
- today = date.today().isoformat()
115
- commits = _git_commits(dev, today)
116
- finished = _finished_today(dev)
117
- in_progress = _my_tasks(dev, statuses=['in_progress'])
118
- planning = _my_tasks(dev, statuses=['planning'])
119
-
120
- lines = [
121
- f'# 工作日报 - {dev} - {today}',
122
- '',
123
- '## 今日完成',
124
- ]
125
- if finished:
126
- for t in finished:
127
- title = t['data'].get('title', t['name'])
128
- lines.append(f'- [{t["name"]}] {title}')
129
- elif commits:
130
- # 无 finish 任务但有提交
131
- seen = set()
132
- for c in commits:
133
- msg = c['msg']
134
- if msg not in seen:
135
- lines.append(f'- {msg} ({c["hash"]})')
136
- seen.add(msg)
137
- else:
138
- lines.append('- (今日无完成任务或提交)')
139
-
140
- lines.extend(['', '## 进行中'])
141
- if in_progress:
142
- for t in in_progress:
143
- title = t['data'].get('title', t['name'])
144
- due = t['data'].get('due_date', '')
145
- due_str = f' (截止 {due})' if due else ''
146
- lines.append(f'- [{t["name"]}] {title}{due_str}')
147
- else:
148
- lines.append('- (无)')
149
-
150
- lines.extend(['', '## 明日计划'])
151
- if planning:
152
- for t in planning[:3]:
153
- title = t['data'].get('title', t['name'])
154
- lines.append(f'- [{t["name"]}] {title}')
155
- elif in_progress:
156
- lines.append('- (继续进行中的任务)')
157
- else:
158
- lines.append('- (待规划)')
159
-
160
- lines.extend(['', f'## 提交统计', f'- 今日提交: {len(commits)} 次'])
161
-
162
- return '\n'.join(lines)
163
-
164
-
165
- def generate_weekly(dev: str) -> str:
166
- """生成周报 markdown。"""
167
- # 周一为起点
168
- today = date.today()
169
- monday = today - timedelta(days=today.weekday())
170
- week_str = f'{monday.isoformat()} ~ {today.isoformat()}'
171
- since = monday.isoformat()
172
-
173
- commits = _git_commits(dev, since)
174
- finished = _finished_this_week(dev, since)
175
- in_progress = _my_tasks(dev, statuses=['in_progress'])
176
-
177
- lines = [
178
- f'# 周报 - {dev} - {week_str}',
179
- '',
180
- '## 本周完成',
181
- ]
182
- if finished:
183
- for t in finished:
184
- title = t['data'].get('title', t['name'])
185
- lines.append(f'- [{t["name"]}] {title}')
186
- else:
187
- lines.append('- (本周无完成任务)')
188
-
189
- lines.extend([
190
- '', '## 关键产出',
191
- f'- 提交数: {len(commits)} 次',
192
- ])
193
- # 提取 REQ/PRD 相关提交
194
- prd_commits = [c for c in commits if 'REQ-' in c['msg'].upper() or 'PRD' in c['msg'].upper()]
195
- if prd_commits:
196
- lines.append(f'- PRD 相关提交: {len(prd_commits)}')
197
-
198
- lines.extend(['', '## 进行中'])
199
- if in_progress:
200
- for t in in_progress:
201
- title = t['data'].get('title', t['name'])
202
- lines.append(f'- [{t["name"]}] {title}')
203
- else:
204
- lines.append('- (无)')
205
-
206
- lines.extend(['', '## 下周计划', '- (待规划)'])
207
-
208
- return '\n'.join(lines)
209
-
210
-
211
- def _finished_this_week(dev: str, since: str) -> list:
212
- """本周完成的任务。"""
213
- out = []
214
- tasks_dir = get_tasks_dir(BASE)
215
- archive_dir = BASE / '.qoder' / 'archive'
216
- search_dirs = [tasks_dir, archive_dir] if archive_dir.is_dir() else [tasks_dir]
217
- for sd in search_dirs:
218
- if not sd.is_dir():
219
- continue
220
- for d in sd.iterdir():
221
- if not d.is_dir():
222
- continue
223
- data = load_task_json(d)
224
- if not data:
225
- continue
226
- if dev not in (data.get('assignee'), data.get('creator')):
227
- continue
228
- completed_ts = (data.get('stage_ts') or {}).get('completed') or data.get('updated_at', '')
229
- if completed_ts >= since and data.get('status') == 'completed':
230
- out.append({'name': d.name, 'data': data})
231
- return out
232
-
233
-
234
- def save_and_maybe_push(content: str, mode: str, push_feishu: bool):
235
- """保存报告到 journal 目录, 可选推送飞书。"""
236
- dev = get_developer(BASE)
237
- journal_dir = BASE / 'workspace' / 'members' / dev / 'journal'
238
- journal_dir.mkdir(parents=True, exist_ok=True)
239
-
240
- today = date.today().isoformat()
241
- filename = f'{today}-{mode}.md'
242
- out_path = journal_dir / filename
243
- out_path.write_text(content, encoding='utf-8')
244
- print(f'报告已保存: workspace/members/{dev}/journal/{filename}')
245
- print('---')
246
- print(content)
247
-
248
- if push_feishu:
249
- try:
250
- from common.feishu import send_card, is_enabled
251
- if is_enabled():
252
- # 报告作为长文本卡片推送
253
- title = f'{dev} 的{"周报" if mode == "weekly" else "日报"} - {today}'
254
- # 飞书卡片内容截断 (避免过长)
255
- content_short = content[:2000] + ('\n...(截断)' if len(content) > 2000 else '')
256
- send_card(title, [content_short], event_type=None, theme='blue')
257
- print('\n[feishu] 报告已推送到飞书群')
258
- else:
259
- print('\n[feishu] 未启用, 跳过推送 (在 config.yaml 设 feishu.enabled: true)')
260
- except Exception as e:
261
- print(f'\n[feishu] 推送失败 (不影响报告): {e}')
262
-
263
-
264
- def main():
265
- parser = argparse.ArgumentParser(description='工作日报/周报生成')
266
- parser.add_argument('mode', choices=['daily', 'weekly'], help='日报 or 周报')
267
- parser.add_argument('--push-feishu', action='store_true', help='推送到飞书群')
268
- args = parser.parse_args()
269
-
270
- dev = _ensure_developer()
271
-
272
- if args.mode == 'daily':
273
- content = generate_daily(dev)
274
- else:
275
- content = generate_weekly(dev)
276
-
277
- save_and_maybe_push(content, args.mode, args.push_feishu)
278
-
279
-
280
- if __name__ == '__main__':
281
- main()
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
4
+ import os as _o, sys as _s
5
+ _f = _o.path.abspath(__file__)
6
+ for _ in range(10):
7
+ _f = _o.path.dirname(_f)
8
+ _cp = _o.path.join(_f, 'foundation')
9
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
10
+ break
11
+ if _cp not in _s.path: _s.path.insert(0, _cp)
12
+ from bootstrap import setup; setup()
13
+
14
+ """
15
+ report.py - 工作日报/周报生成 + 飞书推送 (阶段 D3)
16
+
17
+ 支撑 /wl-report 命令的数据生成。AI 执行 markdown 指令, 本脚本提供数据。
18
+
19
+ Usage:
20
+ python report.py daily # 生成今日日报 (打印)
21
+ python report.py weekly # 生成本周周报 (打印)
22
+ python report.py daily --push-feishu # 生成并推送到飞书
23
+ python report.py weekly --push-feishu
24
+
25
+ 输出: 中文格式, 保存到 workspace/members/{dev}/journal/
26
+ """
27
+
28
+ import argparse
29
+ import os
30
+ import subprocess
31
+ import sys
32
+ from datetime import datetime, date, timedelta
33
+ from pathlib import Path
34
+
35
+ try:
36
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
37
+ except (AttributeError, TypeError, OSError):
38
+ pass
39
+
40
+ THIS_DIR = os.path.dirname(os.path.abspath(__file__))
41
+ sys.path.insert(0, THIS_DIR)
42
+ from foundation.core.paths import get_repo_root, get_developer, get_tasks_dir
43
+ from foundation.data.task_utils import load_task_json
44
+
45
+ BASE = get_repo_root()
46
+
47
+
48
+ def _ensure_developer():
49
+ """必须有开发者身份。"""
50
+ dev = get_developer(BASE)
51
+ if not dev:
52
+ print('[ERROR] 未设置开发者身份。先跑 /wl-init 注册。', file=sys.stderr)
53
+ sys.exit(4)
54
+ return dev
55
+
56
+
57
+ def _git_commits(dev: str, since_date: str) -> list:
58
+ """获取开发者指定日期后的 git 提交。"""
59
+ try:
60
+ r = subprocess.run(
61
+ ['git', 'log', '--author', dev, '--since', since_date,
62
+ '--pretty=format:%h|%s|%ar', '--no-merges'],
63
+ capture_output=True, text=True, encoding='utf-8', errors='replace',
64
+ cwd=str(BASE),
65
+ )
66
+ if r.returncode != 0:
67
+ return []
68
+ commits = []
69
+ for line in r.stdout.strip().splitlines():
70
+ parts = line.split('|', 2)
71
+ if len(parts) == 3:
72
+ commits.append({'hash': parts[0], 'msg': parts[1], 'when': parts[2]})
73
+ return commits
74
+ except Exception:
75
+ return []
76
+
77
+
78
+ def _my_tasks(dev: str, statuses=None) -> list:
79
+ """获取我的任务。"""
80
+ tasks_dir = get_tasks_dir(BASE)
81
+ if not tasks_dir.is_dir():
82
+ return []
83
+ out = []
84
+ for d in tasks_dir.iterdir():
85
+ if not d.is_dir():
86
+ continue
87
+ data = load_task_json(d)
88
+ if not data:
89
+ continue
90
+ if dev not in (data.get('assignee'), data.get('creator')):
91
+ continue
92
+ if statuses and data.get('status') not in statuses:
93
+ continue
94
+ out.append({'name': d.name, 'data': data})
95
+ return out
96
+
97
+
98
+ def _finished_today(dev: str) -> list:
99
+ """今天完成的任务 (从 stage_ts.completed 判断)。"""
100
+ today_str = date.today().isoformat()
101
+ out = []
102
+ tasks_dir = get_tasks_dir(BASE)
103
+ # 也查归档 (今天 finish 的会归档)
104
+ archive_dir = BASE / '.qoder' / 'archive'
105
+ search_dirs = [tasks_dir, archive_dir] if archive_dir.is_dir() else [tasks_dir]
106
+ for sd in search_dirs:
107
+ if not sd.is_dir():
108
+ continue
109
+ for d in sd.iterdir():
110
+ if not d.is_dir():
111
+ continue
112
+ data = load_task_json(d)
113
+ if not data:
114
+ continue
115
+ if dev not in (data.get('assignee'), data.get('creator')):
116
+ continue
117
+ completed_ts = (data.get('stage_ts') or {}).get('completed') or data.get('updated_at', '')
118
+ if completed_ts.startswith(today_str):
119
+ out.append({'name': d.name, 'data': data})
120
+ return out
121
+
122
+
123
+ def generate_daily(dev: str) -> str:
124
+ """生成日报 markdown。"""
125
+ today = date.today().isoformat()
126
+ commits = _git_commits(dev, today)
127
+ finished = _finished_today(dev)
128
+ in_progress = _my_tasks(dev, statuses=['in_progress'])
129
+ planning = _my_tasks(dev, statuses=['planning'])
130
+
131
+ lines = [
132
+ f'# 工作日报 - {dev} - {today}',
133
+ '',
134
+ '## 今日完成',
135
+ ]
136
+ if finished:
137
+ for t in finished:
138
+ title = t['data'].get('title', t['name'])
139
+ lines.append(f'- [{t["name"]}] {title}')
140
+ elif commits:
141
+ # 无 finish 任务但有提交
142
+ seen = set()
143
+ for c in commits:
144
+ msg = c['msg']
145
+ if msg not in seen:
146
+ lines.append(f'- {msg} ({c["hash"]})')
147
+ seen.add(msg)
148
+ else:
149
+ lines.append('- (今日无完成任务或提交)')
150
+
151
+ lines.extend(['', '## 进行中'])
152
+ if in_progress:
153
+ for t in in_progress:
154
+ title = t['data'].get('title', t['name'])
155
+ due = t['data'].get('due_date', '')
156
+ due_str = f' (截止 {due})' if due else ''
157
+ lines.append(f'- [{t["name"]}] {title}{due_str}')
158
+ else:
159
+ lines.append('- (无)')
160
+
161
+ lines.extend(['', '## 明日计划'])
162
+ if planning:
163
+ for t in planning[:3]:
164
+ title = t['data'].get('title', t['name'])
165
+ lines.append(f'- [{t["name"]}] {title}')
166
+ elif in_progress:
167
+ lines.append('- (继续进行中的任务)')
168
+ else:
169
+ lines.append('- (待规划)')
170
+
171
+ lines.extend(['', f'## 提交统计', f'- 今日提交: {len(commits)} 次'])
172
+
173
+ return '\n'.join(lines)
174
+
175
+
176
+ def generate_weekly(dev: str) -> str:
177
+ """生成周报 markdown。"""
178
+ # 周一为起点
179
+ today = date.today()
180
+ monday = today - timedelta(days=today.weekday())
181
+ week_str = f'{monday.isoformat()} ~ {today.isoformat()}'
182
+ since = monday.isoformat()
183
+
184
+ commits = _git_commits(dev, since)
185
+ finished = _finished_this_week(dev, since)
186
+ in_progress = _my_tasks(dev, statuses=['in_progress'])
187
+
188
+ lines = [
189
+ f'# 周报 - {dev} - {week_str}',
190
+ '',
191
+ '## 本周完成',
192
+ ]
193
+ if finished:
194
+ for t in finished:
195
+ title = t['data'].get('title', t['name'])
196
+ lines.append(f'- [{t["name"]}] {title}')
197
+ else:
198
+ lines.append('- (本周无完成任务)')
199
+
200
+ lines.extend([
201
+ '', '## 关键产出',
202
+ f'- 提交数: {len(commits)} ',
203
+ ])
204
+ # 提取 REQ/PRD 相关提交
205
+ prd_commits = [c for c in commits if 'REQ-' in c['msg'].upper() or 'PRD' in c['msg'].upper()]
206
+ if prd_commits:
207
+ lines.append(f'- PRD 相关提交: {len(prd_commits)} 次')
208
+
209
+ lines.extend(['', '## 进行中'])
210
+ if in_progress:
211
+ for t in in_progress:
212
+ title = t['data'].get('title', t['name'])
213
+ lines.append(f'- [{t["name"]}] {title}')
214
+ else:
215
+ lines.append('- (无)')
216
+
217
+ lines.extend(['', '## 下周计划', '- (待规划)'])
218
+
219
+ return '\n'.join(lines)
220
+
221
+
222
+ def _finished_this_week(dev: str, since: str) -> list:
223
+ """本周完成的任务。"""
224
+ out = []
225
+ tasks_dir = get_tasks_dir(BASE)
226
+ archive_dir = BASE / '.qoder' / 'archive'
227
+ search_dirs = [tasks_dir, archive_dir] if archive_dir.is_dir() else [tasks_dir]
228
+ for sd in search_dirs:
229
+ if not sd.is_dir():
230
+ continue
231
+ for d in sd.iterdir():
232
+ if not d.is_dir():
233
+ continue
234
+ data = load_task_json(d)
235
+ if not data:
236
+ continue
237
+ if dev not in (data.get('assignee'), data.get('creator')):
238
+ continue
239
+ completed_ts = (data.get('stage_ts') or {}).get('completed') or data.get('updated_at', '')
240
+ if completed_ts >= since and data.get('status') == 'completed':
241
+ out.append({'name': d.name, 'data': data})
242
+ return out
243
+
244
+
245
+ def save_and_maybe_push(content: str, mode: str, push_feishu: bool):
246
+ """保存报告到 journal 目录, 可选推送飞书。"""
247
+ dev = get_developer(BASE)
248
+ journal_dir = BASE / 'workspace' / 'members' / dev / 'journal'
249
+ journal_dir.mkdir(parents=True, exist_ok=True)
250
+
251
+ today = date.today().isoformat()
252
+ filename = f'{today}-{mode}.md'
253
+ out_path = journal_dir / filename
254
+ out_path.write_text(content, encoding='utf-8')
255
+ print(f'报告已保存: workspace/members/{dev}/journal/{filename}')
256
+ print('---')
257
+ print(content)
258
+
259
+ if push_feishu:
260
+ try:
261
+ from foundation.integrations.feishu import send_card, is_enabled
262
+ if is_enabled():
263
+ # 报告作为长文本卡片推送
264
+ title = f'{dev} 的{"周报" if mode == "weekly" else "日报"} - {today}'
265
+ # 飞书卡片内容截断 (避免过长)
266
+ content_short = content[:2000] + ('\n...(截断)' if len(content) > 2000 else '')
267
+ send_card(title, [content_short], event_type=None, theme='blue')
268
+ print('\n[feishu] 报告已推送到飞书群')
269
+ else:
270
+ print('\n[feishu] 未启用, 跳过推送 (在 config.yaml 设 feishu.enabled: true)')
271
+ except Exception as e:
272
+ print(f'\n[feishu] 推送失败 (不影响报告): {e}')
273
+
274
+
275
+ def main():
276
+ parser = argparse.ArgumentParser(description='工作日报/周报生成')
277
+ parser.add_argument('mode', choices=['daily', 'weekly'], help='日报 or 周报')
278
+ parser.add_argument('--push-feishu', action='store_true', help='推送到飞书群')
279
+ args = parser.parse_args()
280
+
281
+ dev = _ensure_developer()
282
+
283
+ if args.mode == 'daily':
284
+ content = generate_daily(dev)
285
+ else:
286
+ content = generate_weekly(dev)
287
+
288
+ save_and_maybe_push(content, args.mode, args.push_feishu)
289
+
290
+
291
+ if __name__ == '__main__':
292
+ main()