@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,201 +1,212 @@
1
- # -*- coding: utf-8 -*-
2
- """learn_aggregate.py - 团队学习聚合 (同构 kg_build.py)
3
-
4
- 扫所有人的 workspace/members/{dev}/journal/feedback.jsonl
5
- 增量写入 kg.duckdb learning_events (团队单一真源)
6
-
7
- kg_build.py 对称:
8
- kg_build.py: 扫 data/code/* → 写 kg.duckdb (代码图谱)
9
- learn_aggregate.py: workspace/members/*/journal → 写 kg.duckdb (学习图谱)
10
-
11
- 增量策略 (复用 build_meta):
12
- 记录上次聚合时间, 只处理 mtime > last 的文件。全量重建用 --rebuild。
13
-
14
- 用法:
15
- python learn_aggregate.py # 增量
16
- python learn_aggregate.py --rebuild # 全量重建
17
- """
18
- import os
19
- import sys
20
- import json
21
- import hashlib
22
- import time
23
- from datetime import datetime
24
-
25
- SCRIPTS = os.path.dirname(os.path.abspath(__file__))
26
- sys.path.insert(0, SCRIPTS)
27
-
28
- from common.paths import MEMBERS_DIR
29
- from kg_duckdb import get_db, init_schema
30
-
31
- BUILD_META_KEY = 'learning_last_aggregate'
32
-
33
-
34
- def _collect_journal_files():
35
- """扫 workspace/members/*/journal/feedback.jsonl, 返回 [(dev, path)]。"""
36
- results = []
37
- if not MEMBERS_DIR.is_dir():
38
- return results
39
- for dev_dir in sorted(MEMBERS_DIR.iterdir()):
40
- if not dev_dir.is_dir():
41
- continue
42
- fb = dev_dir / 'journal' / 'feedback.jsonl'
43
- if fb.is_file():
44
- results.append((dev_dir.name, fb))
45
- return results
46
-
47
-
48
- def _event_id(entry):
49
- """生成事件唯一 ID (去重用): hash(ts+dev+event+data)。"""
50
- raw = entry.get('ts', '') + '|' + entry.get('dev', '') + '|' + \
51
- entry.get('event', '') + '|' + json.dumps(entry.get('data', {}), sort_keys=True)
52
- return hashlib.md5(raw.encode('utf-8')).hexdigest()[:16]
53
-
54
-
55
- def _read_events(dev, path):
56
- """读一个开发者的 feedback.jsonl, 返回事件列表。容错: 跳过坏行。"""
57
- events = []
58
- try:
59
- with open(path, encoding='utf-8') as f:
60
- for line in f:
61
- line = line.strip()
62
- if not line:
63
- continue
64
- try:
65
- e = json.loads(line)
66
- e.setdefault('dev', dev)
67
- events.append(e)
68
- except json.JSONDecodeError:
69
- continue # 跳过坏行, 不中断
70
- except (OSError, IOError):
71
- pass
72
- return events
73
-
74
-
75
- def aggregate(rebuild=False):
76
- """聚合所有开发者的 feedback → kg.duckdb。
77
- 返回写入的事件数。
78
- 增量: 只处理 mtime > last_aggregate 的文件。
79
- """
80
- con = get_db()
81
- init_schema(con)
82
-
83
- # 读上次聚合时间 (增量依据)
84
- last_ts = None
85
- if not rebuild:
86
- row = con.execute(
87
- "SELECT value FROM build_meta WHERE key = ?", [BUILD_META_KEY]
88
- ).fetchone()
89
- if row:
90
- try:
91
- last_ts = float(row[0])
92
- except (ValueError, TypeError):
93
- last_ts = None
94
-
95
- files = _collect_journal_files()
96
- if rebuild:
97
- con.execute("DELETE FROM learning_events")
98
- to_process = files
99
- else:
100
- to_process = [(d, p) for d, p in files
101
- if last_ts is None or p.stat().st_mtime > last_ts]
102
-
103
- if not to_process:
104
- print('无新增事件 (上次聚合后无变化)。')
105
- _refresh_stats(con)
106
- _update_meta(con)
107
- return 0
108
-
109
- # 收集所有事件 + 去重 (同 event_id 只写一次)
110
- seen_ids = set()
111
- if not rebuild:
112
- existing = con.execute("SELECT id FROM learning_events").fetchall()
113
- seen_ids = {r[0] for r in existing}
114
-
115
- rows = []
116
- total_events = 0
117
- for dev, path in to_process:
118
- for e in _read_events(dev, path):
119
- eid = _event_id(e)
120
- if eid in seen_ids:
121
- continue
122
- seen_ids.add(eid)
123
- rows.append((
124
- eid,
125
- e.get('ts', ''),
126
- e.get('dev', dev),
127
- e.get('event', ''),
128
- json.dumps(e.get('data', {}), ensure_ascii=False),
129
- ))
130
- total_events += 1
131
-
132
- # 批量写入 (DuckDB executemany)
133
- if rows:
134
- con.executemany(
135
- "INSERT OR IGNORE INTO learning_events(id, ts, dev, event, data) VALUES (?,?,?,?,?)",
136
- rows,
137
- )
138
-
139
- _refresh_stats(con)
140
- _update_meta(con)
141
- print('聚合完成: %d 条事件, 来自 %d 个开发者 (%d 个文件)。'
142
- % (total_events, len({d for d, _ in to_process}), len(to_process)))
143
- return total_events
144
-
145
-
146
- def _refresh_stats(con):
147
- """刷新 learning_stats 表 (从 learning_events 重新统计每人计数)。
148
-
149
- 事件→列映射来自 common/events.py (SSOT)。新增埋点只要在 events.py 注册,
150
- 这里自动生效, 不用改 SQL (修旧版硬编码漂移 bug)
151
- """
152
- from common.events import event_to_sql_filter
153
- con.execute("DELETE FROM learning_stats")
154
- # 列名 = events.py 的 stat 值; 动态拼 FILTER 子句
155
- cols = event_to_sql_filter() # {stat_column: event}
156
- col_names = list(cols.keys())
157
- select_cols = ', '.join(
158
- "COUNT(*) FILTER (WHERE event = '%s') AS %s" % (cols[c], c)
159
- for c in col_names
160
- )
161
- col_list = ', '.join(col_names)
162
- con.execute("""
163
- INSERT INTO learning_stats (dev, %s, updated_at)
164
- SELECT dev, %s, ?
165
- FROM learning_events
166
- GROUP BY dev
167
- """ % (col_list, select_cols), [datetime.now().isoformat(timespec='seconds')])
168
-
169
-
170
- def _update_meta(con):
171
- """记录本次聚合时间。"""
172
- con.execute(
173
- "INSERT OR REPLACE INTO build_meta(key, value) VALUES (?, ?)",
174
- [BUILD_META_KEY, str(time.time())],
175
- )
176
-
177
-
178
- def query_summary():
179
- """查询团队学习摘要 (给 /wl-status 用)。"""
180
- con = get_db()
181
- init_schema(con)
182
- total = con.execute("SELECT COUNT(*) FROM learning_events").fetchone()[0]
183
- by_event = con.execute("""
184
- SELECT event, COUNT(*) FROM learning_events GROUP BY event ORDER BY COUNT(*) DESC
185
- """).fetchall()
186
- by_dev = con.execute("""
187
- SELECT dev, COUNT(*) FROM learning_events GROUP BY dev ORDER BY COUNT(*) DESC
188
- """).fetchall()
189
- return {'total': total, 'by_event': by_event, 'by_dev': by_dev}
190
-
191
-
192
- if __name__ == '__main__':
193
- rebuild = '--rebuild' in sys.argv
194
- n = aggregate(rebuild=rebuild)
195
- s = query_summary()
196
- print('\n团队学习摘要:')
197
- print(' 总事件: %d' % s['total'])
198
- for ev, cnt in s['by_event']:
199
- print(' %s: %d' % (ev, cnt))
200
- for dev, cnt in s['by_dev']:
201
- print(' %s: %d 条' % (dev, cnt))
1
+ # -*- coding: utf-8 -*-
2
+ """learn_aggregate.py - 团队学习聚合 (同构 kg_build.py)
3
+
4
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
5
+ import os as _o, sys as _s
6
+ _f = _o.path.abspath(__file__)
7
+ for _ in range(10):
8
+ _f = _o.path.dirname(_f)
9
+ _cp = _o.path.join(_f, 'foundation')
10
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
11
+ break
12
+ if _cp not in _s.path: _s.path.insert(0, _cp)
13
+ from bootstrap import setup; setup()
14
+
15
+ 扫所有人的 workspace/members/{dev}/journal/feedback.jsonl
16
+ 增量写入 kg.duckdb learning_events 表 (团队单一真源)
17
+
18
+ kg_build.py 对称:
19
+ kg_build.py: 扫 data/code/* → 写 kg.duckdb (代码图谱)
20
+ learn_aggregate.py: 扫 workspace/members/*/journal → 写 kg.duckdb (学习图谱)
21
+
22
+ 增量策略 (复用 build_meta):
23
+ 记录上次聚合时间, 只处理 mtime > last 的文件。全量重建用 --rebuild。
24
+
25
+ 用法:
26
+ python learn_aggregate.py # 增量
27
+ python learn_aggregate.py --rebuild # 全量重建
28
+ """
29
+ import os
30
+ import sys
31
+ import json
32
+ import hashlib
33
+ import time
34
+ from datetime import datetime
35
+
36
+ SCRIPTS = os.path.dirname(os.path.abspath(__file__))
37
+ sys.path.insert(0, SCRIPTS)
38
+
39
+ from foundation.core.paths import MEMBERS_DIR
40
+ from domain.kg.storage.kg_duckdb import get_db, init_schema # v3.0: kg_duckdb 已迁入 kg/ 子包
41
+
42
+ BUILD_META_KEY = 'learning_last_aggregate'
43
+
44
+
45
+ def _collect_journal_files():
46
+ """扫 workspace/members/*/journal/feedback.jsonl, 返回 [(dev, path)]。"""
47
+ results = []
48
+ if not MEMBERS_DIR.is_dir():
49
+ return results
50
+ for dev_dir in sorted(MEMBERS_DIR.iterdir()):
51
+ if not dev_dir.is_dir():
52
+ continue
53
+ fb = dev_dir / 'journal' / 'feedback.jsonl'
54
+ if fb.is_file():
55
+ results.append((dev_dir.name, fb))
56
+ return results
57
+
58
+
59
+ def _event_id(entry):
60
+ """生成事件唯一 ID (去重用): hash(ts+dev+event+data)。"""
61
+ raw = entry.get('ts', '') + '|' + entry.get('dev', '') + '|' + \
62
+ entry.get('event', '') + '|' + json.dumps(entry.get('data', {}), sort_keys=True)
63
+ return hashlib.md5(raw.encode('utf-8')).hexdigest()[:16]
64
+
65
+
66
+ def _read_events(dev, path):
67
+ """读一个开发者的 feedback.jsonl, 返回事件列表。容错: 跳过坏行。"""
68
+ events = []
69
+ try:
70
+ with open(path, encoding='utf-8') as f:
71
+ for line in f:
72
+ line = line.strip()
73
+ if not line:
74
+ continue
75
+ try:
76
+ e = json.loads(line)
77
+ e.setdefault('dev', dev)
78
+ events.append(e)
79
+ except json.JSONDecodeError:
80
+ continue # 跳过坏行, 不中断
81
+ except (OSError, IOError):
82
+ pass
83
+ return events
84
+
85
+
86
+ def aggregate(rebuild=False):
87
+ """聚合所有开发者的 feedback kg.duckdb。
88
+ 返回写入的事件数。
89
+ 增量: 只处理 mtime > last_aggregate 的文件。
90
+ """
91
+ con = get_db()
92
+ init_schema(con)
93
+
94
+ # 读上次聚合时间 (增量依据)
95
+ last_ts = None
96
+ if not rebuild:
97
+ row = con.execute(
98
+ "SELECT value FROM build_meta WHERE key = ?", [BUILD_META_KEY]
99
+ ).fetchone()
100
+ if row:
101
+ try:
102
+ last_ts = float(row[0])
103
+ except (ValueError, TypeError):
104
+ last_ts = None
105
+
106
+ files = _collect_journal_files()
107
+ if rebuild:
108
+ con.execute("DELETE FROM learning_events")
109
+ to_process = files
110
+ else:
111
+ to_process = [(d, p) for d, p in files
112
+ if last_ts is None or p.stat().st_mtime > last_ts]
113
+
114
+ if not to_process:
115
+ print('无新增事件 (上次聚合后无变化)。')
116
+ _refresh_stats(con)
117
+ _update_meta(con)
118
+ return 0
119
+
120
+ # 收集所有事件 + 去重 (同 event_id 只写一次)
121
+ seen_ids = set()
122
+ if not rebuild:
123
+ existing = con.execute("SELECT id FROM learning_events").fetchall()
124
+ seen_ids = {r[0] for r in existing}
125
+
126
+ rows = []
127
+ total_events = 0
128
+ for dev, path in to_process:
129
+ for e in _read_events(dev, path):
130
+ eid = _event_id(e)
131
+ if eid in seen_ids:
132
+ continue
133
+ seen_ids.add(eid)
134
+ rows.append((
135
+ eid,
136
+ e.get('ts', ''),
137
+ e.get('dev', dev),
138
+ e.get('event', ''),
139
+ json.dumps(e.get('data', {}), ensure_ascii=False),
140
+ ))
141
+ total_events += 1
142
+
143
+ # 批量写入 (DuckDB executemany)
144
+ if rows:
145
+ con.executemany(
146
+ "INSERT OR IGNORE INTO learning_events(id, ts, dev, event, data) VALUES (?,?,?,?,?)",
147
+ rows,
148
+ )
149
+
150
+ _refresh_stats(con)
151
+ _update_meta(con)
152
+ print('聚合完成: %d 条事件, 来自 %d 个开发者 (%d 个文件)。'
153
+ % (total_events, len({d for d, _ in to_process}), len(to_process)))
154
+ return total_events
155
+
156
+
157
+ def _refresh_stats(con):
158
+ """刷新 learning_stats ( learning_events 重新统计每人计数)
159
+
160
+ 事件→列映射来自 common/events.py (SSOT)。新增埋点只要在 events.py 注册,
161
+ 这里自动生效, 不用改 SQL (修旧版硬编码漂移 bug)
162
+ """
163
+ from foundation.data.events import event_to_sql_filter
164
+ con.execute("DELETE FROM learning_stats")
165
+ # 列名 = events.py 的 stat 值; 动态拼 FILTER 子句
166
+ cols = event_to_sql_filter() # {stat_column: event}
167
+ col_names = list(cols.keys())
168
+ select_cols = ', '.join(
169
+ "COUNT(*) FILTER (WHERE event = '%s') AS %s" % (cols[c], c)
170
+ for c in col_names
171
+ )
172
+ col_list = ', '.join(col_names)
173
+ con.execute("""
174
+ INSERT INTO learning_stats (dev, %s, updated_at)
175
+ SELECT dev, %s, ?
176
+ FROM learning_events
177
+ GROUP BY dev
178
+ """ % (col_list, select_cols), [datetime.now().isoformat(timespec='seconds')])
179
+
180
+
181
+ def _update_meta(con):
182
+ """记录本次聚合时间。"""
183
+ con.execute(
184
+ "INSERT OR REPLACE INTO build_meta(key, value) VALUES (?, ?)",
185
+ [BUILD_META_KEY, str(time.time())],
186
+ )
187
+
188
+
189
+ def query_summary():
190
+ """查询团队学习摘要 (给 /wl-status 用)。"""
191
+ con = get_db()
192
+ init_schema(con)
193
+ total = con.execute("SELECT COUNT(*) FROM learning_events").fetchone()[0]
194
+ by_event = con.execute("""
195
+ SELECT event, COUNT(*) FROM learning_events GROUP BY event ORDER BY COUNT(*) DESC
196
+ """).fetchall()
197
+ by_dev = con.execute("""
198
+ SELECT dev, COUNT(*) FROM learning_events GROUP BY dev ORDER BY COUNT(*) DESC
199
+ """).fetchall()
200
+ return {'total': total, 'by_event': by_event, 'by_dev': by_dev}
201
+
202
+
203
+ if __name__ == '__main__':
204
+ rebuild = '--rebuild' in sys.argv
205
+ n = aggregate(rebuild=rebuild)
206
+ s = query_summary()
207
+ print('\n团队学习摘要:')
208
+ print(' 总事件: %d' % s['total'])
209
+ for ev, cnt in s['by_event']:
210
+ print(' %s: %d' % (ev, cnt))
211
+ for dev, cnt in s['by_dev']:
212
+ print(' %s: %d 条' % (dev, cnt))