@hupan56/wlkj 2.5.0 → 2.7.0

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 (127) hide show
  1. package/bin/cli.js +289 -12
  2. package/package.json +1 -1
  3. package/templates/qoder/agents/insight-planning.md +67 -0
  4. package/templates/qoder/agents/insight-research.md +61 -0
  5. package/templates/qoder/agents/prd-quick.md +1 -0
  6. package/templates/qoder/agents/prd-reference.md +10 -2
  7. package/templates/qoder/commands/optional/wl-insight.md +275 -0
  8. package/templates/qoder/commands/{wl-report.md → optional/wl-report.md} +13 -5
  9. package/templates/qoder/commands/{wl-spec.md → optional/wl-spec.md} +1 -1
  10. package/templates/qoder/commands/{wl-status.md → optional/wl-status.md} +28 -2
  11. package/templates/qoder/commands/wl-code.md +10 -2
  12. package/templates/qoder/commands/wl-commit.md +1 -1
  13. package/templates/qoder/commands/wl-design-draw.md +78 -0
  14. package/templates/qoder/commands/wl-design-scan.md +108 -0
  15. package/templates/qoder/commands/wl-design-spec.md +154 -0
  16. package/templates/qoder/commands/wl-design.md +32 -0
  17. package/templates/qoder/commands/wl-init.md +24 -3
  18. package/templates/qoder/commands/wl-prd-full.md +226 -0
  19. package/templates/qoder/commands/wl-prd-quick.md +134 -0
  20. package/templates/qoder/commands/wl-prd-review.md +104 -0
  21. package/templates/qoder/commands/wl-prd.md +17 -288
  22. package/templates/qoder/commands/wl-search.md +66 -30
  23. package/templates/qoder/commands/wl-task.md +290 -59
  24. package/templates/qoder/commands/wl-test.md +92 -24
  25. package/templates/qoder/config.yaml +59 -15
  26. package/templates/qoder/hooks/inject-workflow-state.py +35 -9
  27. package/templates/qoder/hooks/session-start.py +144 -62
  28. package/templates/qoder/rules/wl-pipeline.md +216 -105
  29. package/templates/qoder/scripts/__pycache__/search_index.cpython-39.pyc +0 -0
  30. package/templates/qoder/scripts/archive_prd.py +377 -0
  31. package/templates/qoder/scripts/autotest.py +1715 -0
  32. package/templates/qoder/scripts/autotest_batch.py +224 -0
  33. package/templates/qoder/scripts/autotest_run.py +297 -0
  34. package/templates/qoder/scripts/benchmark.py +210 -209
  35. package/templates/qoder/scripts/build_style_index.py +444 -4
  36. package/templates/qoder/scripts/check_carriers.py +238 -0
  37. package/templates/qoder/scripts/check_mcp.py +298 -0
  38. package/templates/qoder/scripts/check_qoderwork_consistency.py +166 -0
  39. package/templates/qoder/scripts/common/developer.py +26 -19
  40. package/templates/qoder/scripts/common/events.py +46 -0
  41. package/templates/qoder/scripts/common/extract.py +419 -0
  42. package/templates/qoder/scripts/common/graph_traverse.py +533 -0
  43. package/templates/qoder/scripts/common/identity.py +6 -1
  44. package/templates/qoder/scripts/common/paths.py +89 -0
  45. package/templates/qoder/scripts/common/pip_install.py +144 -0
  46. package/templates/qoder/scripts/common/platform_guard.py +61 -0
  47. package/templates/qoder/scripts/common/search_engine.py +205 -205
  48. package/templates/qoder/scripts/common/terms.py +57 -0
  49. package/templates/qoder/scripts/common/ts_extract.py +536 -0
  50. package/templates/qoder/scripts/context_pack.py +73 -13
  51. package/templates/qoder/scripts/enrich_prompt.py +226 -0
  52. package/templates/qoder/scripts/eval_prd.py +318 -225
  53. package/templates/qoder/scripts/export.py +487 -487
  54. package/templates/qoder/scripts/extract_api_params.py +246 -0
  55. package/templates/qoder/scripts/extract_routes.py +54 -0
  56. package/templates/qoder/scripts/extract_routes_tree.py +78 -0
  57. package/templates/qoder/scripts/fill_prototype.py +707 -0
  58. package/templates/qoder/scripts/gen_design_doc.py +394 -0
  59. package/templates/qoder/scripts/git_sync.py +27 -15
  60. package/templates/qoder/scripts/init_doctor.py +306 -41
  61. package/templates/qoder/scripts/install_qoderwork.py +366 -9
  62. package/templates/qoder/scripts/kg.py +708 -0
  63. package/templates/qoder/scripts/kg_auto_login.py +196 -0
  64. package/templates/qoder/scripts/kg_build.py +612 -0
  65. package/templates/qoder/scripts/kg_build_db.py +327 -0
  66. package/templates/qoder/scripts/kg_duckdb.py +549 -0
  67. package/templates/qoder/scripts/kg_incremental.py +393 -0
  68. package/templates/qoder/scripts/kg_link_db.py +224 -0
  69. package/templates/qoder/scripts/kg_mcp_server.py +801 -0
  70. package/templates/qoder/scripts/kg_semantic.py +150 -0
  71. package/templates/qoder/scripts/kg_test_runner.py +241 -0
  72. package/templates/qoder/scripts/lanhu_stdio_wrapper.py +119 -0
  73. package/templates/qoder/scripts/learn.py +118 -39
  74. package/templates/qoder/scripts/learn_aggregate.py +201 -0
  75. package/templates/qoder/scripts/mcp_launcher.py +359 -0
  76. package/templates/qoder/scripts/mysql_mcp_server.py +396 -0
  77. package/templates/qoder/scripts/repo_root.py +106 -0
  78. package/templates/qoder/scripts/role.py +12 -0
  79. package/templates/qoder/scripts/run_weekly_update.bat +5 -0
  80. package/templates/qoder/scripts/run_weekly_update.sh +5 -0
  81. package/templates/qoder/scripts/search_index.py +307 -60
  82. package/templates/qoder/scripts/secure-ls.js +5640 -0
  83. package/templates/qoder/scripts/setup.py +706 -453
  84. package/templates/qoder/scripts/setup_lanhu.py +963 -0
  85. package/templates/qoder/scripts/status.py +250 -11
  86. package/templates/qoder/scripts/sync_carriers.py +259 -0
  87. package/templates/qoder/scripts/syncgate.py +5 -4
  88. package/templates/qoder/scripts/task.py +75 -0
  89. package/templates/qoder/scripts/team_sync.py +60 -4
  90. package/templates/qoder/scripts/workspace_init.py +1 -1
  91. package/templates/qoder/skills/design-import/SKILL.md +226 -0
  92. package/templates/qoder/skills/design-import/figma-workflow.md +81 -0
  93. package/templates/qoder/skills/design-review/SKILL.md +82 -25
  94. package/templates/qoder/skills/prd-generator/SKILL.md +185 -58
  95. package/templates/qoder/skills/prd-review/SKILL.md +18 -1
  96. package/templates/qoder/skills/prompt-enrich/SKILL.md +90 -0
  97. package/templates/qoder/skills/prototype-generator/SKILL.md +256 -141
  98. package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
  99. package/templates/qoder/skills/spec-coder/SKILL.md +18 -1
  100. package/templates/qoder/skills/spec-generator/SKILL.md +18 -1
  101. package/templates/qoder/skills/test-generator/SKILL.md +15 -2
  102. package/templates/qoder/skills/wl-code/SKILL.md +55 -36
  103. package/templates/qoder/skills/wl-commit/SKILL.md +89 -76
  104. package/templates/qoder/skills/wl-design/SKILL.md +55 -0
  105. package/templates/qoder/skills/wl-init/SKILL.md +76 -67
  106. package/templates/qoder/skills/wl-insight/SKILL.md +201 -81
  107. package/templates/qoder/skills/wl-prd-full/SKILL.md +69 -0
  108. package/templates/qoder/skills/wl-prd-quick/SKILL.md +49 -0
  109. package/templates/qoder/skills/wl-prd-review/SKILL.md +34 -0
  110. package/templates/qoder/skills/wl-report/SKILL.md +131 -107
  111. package/templates/qoder/skills/wl-search/SKILL.md +141 -75
  112. package/templates/qoder/skills/wl-spec/SKILL.md +49 -39
  113. package/templates/qoder/skills/wl-status/SKILL.md +83 -61
  114. package/templates/qoder/skills/wl-task/SKILL.md +132 -58
  115. package/templates/qoder/skills/wl-test/SKILL.md +406 -40
  116. package/templates/qoder/templates/prd-full-template.md +2 -0
  117. package/templates/qoder/templates/prd-quick-template.md +1 -0
  118. package/templates/qoder/templates/prototype-app.html +13 -8
  119. package/templates/qoder/templates/prototype-web.html +376 -93
  120. package/templates/root/AGENTS.md +89 -34
  121. package/templates/root/requirements.txt +21 -0
  122. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +259 -259
  123. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -186
  124. package/templates/qoder/agents/prd-planning.md +0 -56
  125. package/templates/qoder/agents/prd-research.md +0 -33
  126. package/templates/qoder/commands/wl-insight.md +0 -51
  127. package/templates/qoder/skills/wl-prd/SKILL.md +0 -89
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ check_qoderwork_consistency.py — 防退化检查:命令/skills 文件不得用裸相对路径调脚本。
5
+
6
+ 背景:
7
+ QoderWork 桌面端工作目录是临时会话目录(workspace/xxx), 不是仓库根。
8
+ 命令/技能文件里写 `python .qoder/scripts/xxx.py` → 在 QoderWork 下找不到脚本 →
9
+ Shell 直接报错, Python 根本没启动。这就是"ZCode 行、QoderWork 不行"的根因。
10
+
11
+ 统一规约: 所有脚本调用必须用仓库根定位, 二选一:
12
+ ① python "$R/.qoder/scripts/xxx.py" ... (R = repo_root.py 输出)
13
+ ② cd "$R" && python .qoder/scripts/xxx.py ... (cd 到仓库根后相对路径)
14
+
15
+ 本脚本扫 .qoder/commands/**/*.md 和 .qoder/skills/**/SKILL.md,
16
+ 找出违反规约的裸相对路径调用, 退出码非 0 表示有退化。
17
+ 可挂 pre-commit / CI。
18
+
19
+ Usage:
20
+ python .qoder/scripts/check_qoderwork_consistency.py
21
+ python .qoder/scripts/check_qoderwork_consistency.py --strict # 警告也算失败
22
+ Exit:
23
+ 0 = 通过(无裸相对路径调用, 或仅有 --strict 才算失败的警告)
24
+ 1 = 发现裸相对路径调用(退化)
25
+ """
26
+ import argparse
27
+ import os
28
+ import re
29
+ import sys
30
+
31
+ if sys.platform == 'win32':
32
+ try:
33
+ sys.stdout.reconfigure(encoding='utf-8')
34
+ except (AttributeError, IOError):
35
+ pass
36
+
37
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
38
+ from common.paths import get_repo_root # noqa: E402
39
+
40
+ # 扫描目标: commands 全部 .md + skills 的 SKILL.md
41
+ SCAN_GLOBS = [
42
+ ('commands', '.qoder/commands', '*.md'),
43
+ ('skills', '.qoder/skills', 'SKILL.md'),
44
+ ]
45
+
46
+ # 裸相对路径调用: `python .qoder/scripts/xxx` 但前面没有 cd "$R" 或 "$R/ 前缀
47
+ # 匹配 `python .qoder/scripts/` 或 `python3 .qoder/scripts/`, 不带 $R 引号包裹
48
+ BARE_PAT = re.compile(r'(?<![\"\$])\bpython3?\s+\.qoder/scripts/([A-Za-z0-9_./-]+)')
49
+
50
+ # 已安全模式: cd "$R" && python .qoder/scripts/...
51
+ SAFE_CD_PAT = re.compile(r'cd\s+["\']?\$R["\']?\s*&&\s*python3?\s+\.qoder/scripts/')
52
+
53
+ # 仓库根定位块存在的标志
54
+ ROOT_LOCATOR_PAT = re.compile(r'repo_root\.py|R=\$\(python.*repo_root')
55
+
56
+ # 在 install_qoderwork.py 提示行里的相对路径(说明性文字, 非真实调用), 豁免
57
+ HINT_CTX = 'install_qoderwork.py'
58
+
59
+
60
+ def scan_file(path, strict):
61
+ """扫一个文件, 返回 (errors, warnings, root_block_present)。
62
+ errors = 裸相对路径真实调用(必修); warnings = 无 R 块等(建议修)。
63
+ """
64
+ errors = []
65
+ warnings = []
66
+ try:
67
+ with open(path, encoding='utf-8') as f:
68
+ lines = f.readlines()
69
+ except (OSError, IOError):
70
+ return errors, warnings, True
71
+
72
+ root_block = bool(ROOT_LOCATOR_PAT.search(''.join(lines)))
73
+
74
+ for i, line in enumerate(lines, 1):
75
+ # 豁免: 说明性文字里提到 install_qoderwork.py 的相对路径(这是"找不到时跑这个"的指引)
76
+ if HINT_CTX in line:
77
+ continue
78
+ # 豁免: 已安全的 cd "$R" && python 模式
79
+ if SAFE_CD_PAT.search(line):
80
+ continue
81
+ # 找裸相对路径
82
+ for m in BARE_PAT.finditer(line):
83
+ script = m.group(1)
84
+ # 过滤掉明显是文字描述(行里既无 bash 代码块上下文也无命令感)
85
+ # 简化: 只要匹配到就算(宁可误报让 reviewer 看一眼)
86
+ errors.append((i, line.rstrip(), script))
87
+
88
+ # 无 R 块但文件里有 $R 引用 → 警告
89
+ if not root_block and '$R' in ''.join(lines):
90
+ warnings.append((0, '(无 R 定位块, 但文件里用了 $R)', ''))
91
+
92
+ return errors, warnings, root_block
93
+
94
+
95
+ def main():
96
+ ap = argparse.ArgumentParser(description='检查命令/skills 是否含裸相对路径(防 ZCode/QoderWork 割裂)')
97
+ ap.add_argument('--strict', action='store_true', help='警告也算失败')
98
+ args = ap.parse_args()
99
+
100
+ repo = get_repo_root()
101
+ total_errors = 0
102
+ total_warnings = 0
103
+ files_with_errors = []
104
+ files_with_warnings = []
105
+
106
+ for label, rel_dir, pattern in SCAN_GLOBS:
107
+ scan_dir = repo / rel_dir
108
+ if not scan_dir.is_dir():
109
+ continue
110
+ for dirpath, dirnames, filenames in os.walk(str(scan_dir)):
111
+ for fn in filenames:
112
+ import fnmatch
113
+ if not fnmatch.fnmatch(fn, pattern):
114
+ continue
115
+ path = os.path.join(dirpath, fn)
116
+ errors, warnings, root_block = scan_file(path, args.strict)
117
+ rel = os.path.relpath(path, str(repo))
118
+ if errors:
119
+ files_with_errors.append((rel, errors))
120
+ total_errors += len(errors)
121
+ if warnings:
122
+ files_with_warnings.append((rel, warnings))
123
+ total_warnings += len(warnings)
124
+
125
+ # 输出
126
+ print('=' * 64)
127
+ print('QoderWork 一致性检查 (防 ZCode 行 / QoderWork 挂 的割裂)')
128
+ print('=' * 64)
129
+
130
+ if files_with_errors:
131
+ print()
132
+ print('🔴 裸相对路径调用 (QoderWork 必挂, 必修):')
133
+ for rel, errs in files_with_errors:
134
+ print(' ' + rel)
135
+ for lineno, line, script in errs:
136
+ snippet = line.strip()[:80]
137
+ print(' L%d: %s' % (lineno, snippet))
138
+
139
+ if files_with_warnings:
140
+ print()
141
+ print('🟡 警告 (建议修, --strict 才算失败):')
142
+ for rel, ws in files_with_warnings:
143
+ print(' ' + rel)
144
+ for lineno, line, script in ws:
145
+ print(' %s' % line)
146
+
147
+ if not files_with_errors and not files_with_warnings:
148
+ print('✅ 全部通过: 命令/skills 无裸相对路径调用, ZCode ≡ QoderWork 一致。')
149
+
150
+ print('-' * 64)
151
+ print('统计: 错误 %d (文件 %d) / 警告 %d (文件 %d)'
152
+ % (total_errors, len(files_with_errors), total_warnings, len(files_with_warnings)))
153
+ print()
154
+ print('规约: 脚本调用必须二选一:')
155
+ print(' ① python "$R/.qoder/scripts/xxx.py" ... (R = repo_root.py 输出)')
156
+ print(' ② cd "$R" && python .qoder/scripts/xxx.py ...')
157
+
158
+ if total_errors > 0:
159
+ return 1
160
+ if args.strict and total_warnings > 0:
161
+ return 1
162
+ return 0
163
+
164
+
165
+ if __name__ == '__main__':
166
+ sys.exit(main())
@@ -73,9 +73,11 @@ def _detect_git_author() -> str | None:
73
73
 
74
74
 
75
75
  def _ensure_git_config(name: str, repo_root: Path) -> None:
76
- """确保 git user.name/email 已配置。未配则用 --local 设为 init 的名字。
76
+ """确认 git user.name/email 已配置。未配则提示 (绝不自动造假账号)。
77
77
 
78
- 检测顺序: 全局 > local。都没有就提示用户。
78
+ 旧逻辑会塞 name@local 假账号, 与零信任 push 门禁冲突, 已废弃。
79
+ 真正的 git 配置由 setup.py configure_git_identity() 在 init 前完成。
80
+ 这里只做兜底检测: 如果走到这还没配, 提示用户去配。
79
81
  """
80
82
  import subprocess
81
83
  # 先看 git 是否可用
@@ -87,23 +89,20 @@ def _ensure_git_config(name: str, repo_root: Path) -> None:
87
89
 
88
90
  author = _detect_git_author()
89
91
  if author:
90
- print(f" [OK] git 作者: {author}")
92
+ # 检测到疑似假账号也要提示 (虽然配了, push 会被门禁拒)
93
+ if "@local" in author:
94
+ print(f" [WARN] git 作者疑似假账号: {author} (push 门禁会拒绝)")
95
+ print(f" 请重跑 'wlkj init' 配置真实团队 git 账号")
96
+ else:
97
+ print(f" [OK] git 作者: {author}")
91
98
  return
92
99
 
93
- # git 未配 user.name/email: 用 init 名字设 local config (不影响全局)
94
- print(f" [设置] git 未配置 user.name/email, 用你的名字设为 local:")
95
- fake_email = f"{name}@local"
96
- try:
97
- subprocess.run(["git", "config", "--local", "user.name", name],
98
- cwd=str(repo_root), capture_output=True, timeout=5)
99
- subprocess.run(["git", "config", "--local", "user.email", fake_email],
100
- cwd=str(repo_root), capture_output=True, timeout=5)
101
- print(f" user.name = {name}")
102
- print(f" user.email = {fake_email}")
103
- print(f" (仅本仓库生效。如有团队 git 账号, 自行改: git config --local user.email 你的邮箱)")
104
- except (FileNotFoundError, OSError, subprocess.SubprocessError) as e:
105
- print(f" [WARN] 设置 git config 失败 (不阻塞): {e}")
106
- print(f" 请手动跑: git config user.name {name} && git config user.email 你的邮箱")
100
+ # git 未配: 不造假, 只提示
101
+ print(f" [WARN] git 未配置 user.name/email")
102
+ print(f" push/commit 会失败。请配置团队 git 账号:")
103
+ print(f" git config --global user.name '你的名字'")
104
+ print(f" git config --global user.email '你的团队邮箱'")
105
+ print(f" 或重跑: wlkj init <你的名字>")
107
106
 
108
107
 
109
108
  def init_developer(name: str, role: str | None = None,
@@ -173,7 +172,15 @@ def init_developer(name: str, role: str | None = None,
173
172
  # 这是 push 门禁的前提: 没 member.json → syncgate 拒绝 push
174
173
  git_author = _detect_git_author()
175
174
  try:
176
- from common.identity import register_member, IdentityError
175
+ from common.identity import register_member, IdentityError, get_member
176
+ # 修正: 旧 member.json 若是假账号(@local), 且现在 git 配了真账号 → 用真账号覆盖
177
+ # register_member 已有"已注册则只更新 git_author, 密钥不动"的逻辑, 这里只加提示
178
+ existing_member = get_member(name, repo_root)
179
+ if existing_member:
180
+ old_author = existing_member.get("git_author", "")
181
+ if (old_author and "@local" in old_author
182
+ and git_author and "@local" not in git_author):
183
+ print(f" [修正] member.json git_author 从假账号更新为真账号: {git_author}")
177
184
  try:
178
185
  register_member(name, role or "pm", git_author, repo_root)
179
186
  print(f" [OK] 身份注册: member.json + 签名密钥已生成")
@@ -182,7 +189,7 @@ def init_developer(name: str, role: str | None = None,
182
189
  except ImportError:
183
190
  pass # identity 模块不可用, 跳过 (向后兼容)
184
191
 
185
- # 5. 确保 git 配置 (检测 + 自动设 local)
192
+ # 5. 确保 git 配置 (只检测+提示, 不造假; 真账号由 setup.py 在 init 前配好)
186
193
  _ensure_git_config(name, repo_root)
187
194
 
188
195
  print(f"Developer initialized: {name}" + (f" ({role})" if role else ""))
@@ -0,0 +1,46 @@
1
+ # -*- coding: utf-8 -*-
2
+ """events.py - 流水线事件字典 (Single Source of Truth)
3
+
4
+ 为什么需要它:
5
+ 之前 learn.py 的 stats_map 和 learn_aggregate.py 的 SQL FILTER 各自硬编码
6
+ 事件名, 两边漂移 → learning_stats 表的 reviews/commits 永远是 0 (没人埋点)。
7
+ 统一到这里, 任何 skill 加埋点只需在此声明, 两端自动对齐。
8
+
9
+ 事件 → 统计列 → 归属 skill 的映射是 SSOT:
10
+ EVENT_TYPES[event] = {
11
+ 'stat': 该事件累加到 learning.yaml / learning_stats 的哪一列 (None=不计数)
12
+ 'skill': 归属哪个 skill (用于「埋点覆盖率」自检)
13
+ 'label': 人类可读名 (/wl-status 展示用)
14
+ }
15
+ """
16
+
17
+ # 事件类型注册表 —— 唯一事实源
18
+ # 新增埋点: 在这里加一行 → learn.py 计数 + learn_aggregate.py SQL 自动生效
19
+ EVENT_TYPES = {
20
+ # PRD 链路
21
+ 'prd_accepted': {'stat': 'prds_generated', 'skill': 'wl-prd-full', 'label': 'PRD 发布'},
22
+ 'prd_rejected': {'stat': 'rejections', 'skill': 'wl-prd-full', 'label': 'PRD 打回'},
23
+ 'review_done': {'stat': 'reviews_done', 'skill': 'prd-review', 'label': 'PRD 评审'},
24
+ # 设计链路
25
+ 'design_import': {'stat': 'designs_imported','skill': 'design-import', 'label': '设计录入'},
26
+ # 任务链路
27
+ 'task_completed': {'stat': 'tasks_completed', 'skill': 'wl-task', 'label': '任务完成'},
28
+ # 提交链路
29
+ 'commit_done': {'stat': 'commits_done', 'skill': 'wl-commit', 'label': '代码提交'},
30
+ # 流水线自检事件 (不计入正向统计, 用于健康度看板)
31
+ 'rule_violation': {'stat': None, 'skill': None, 'label': '规则违规'},
32
+ }
33
+
34
+
35
+ def stats_event_map():
36
+ """返回 {event: stat_column} (只含需要计数的, None 的过滤掉)。
37
+ learn.py 用它驱动 _update_stats。
38
+ """
39
+ return {ev: meta['stat'] for ev, meta in EVENT_TYPES.items() if meta['stat']}
40
+
41
+
42
+ def event_to_sql_filter():
43
+ """返回 {stat_column: event} (反向映射)。
44
+ learn_aggregate.py 用它生成 SQL FILTER 子句, 不再硬编码。
45
+ """
46
+ return {meta['stat']: ev for ev, meta in EVENT_TYPES.items() if meta['stat']}