@hupan56/wlkj 3.3.13 → 3.3.15

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 (54) hide show
  1. package/bin/cli.js +0 -7
  2. package/package.json +29 -29
  3. package/templates/qoder/commands/optional/wl-spec.md +0 -8
  4. package/templates/qoder/commands/optional/wl-status.md +0 -8
  5. package/templates/qoder/commands/wl-code.md +0 -8
  6. package/templates/qoder/commands/wl-commit.md +0 -8
  7. package/templates/qoder/commands/wl-init.md +129 -129
  8. package/templates/qoder/commands/wl-search.md +0 -8
  9. package/templates/qoder/commands/wl-task.md +0 -8
  10. package/templates/qoder/commands/wl-test.md +0 -8
  11. package/templates/qoder/config.yaml +2 -3
  12. package/templates/qoder/contracts/insight.md +55 -55
  13. package/templates/qoder/hooks/pre-tool-use-commit.py +124 -124
  14. package/templates/qoder/hooks/session-start.py +384 -384
  15. package/templates/qoder/hooks/stop-eval.py +383 -383
  16. package/templates/qoder/scripts/capability/adapters/mcp.py +322 -322
  17. package/templates/qoder/scripts/capability/registry.py +245 -245
  18. package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +14 -0
  19. package/templates/qoder/scripts/deployment/setup/setup.py +0 -11
  20. package/templates/qoder/scripts/domain/task/wlkj_panel.py +1503 -1503
  21. package/templates/qoder/scripts/foundation/bootstrap.py +145 -156
  22. package/templates/qoder/scripts/foundation/io/context_cache.py +94 -94
  23. package/templates/qoder/scripts/orchestration/wlkj.py +295 -306
  24. package/templates/qoder/scripts/tool_guide.md +70 -70
  25. package/templates/qoder/scripts/validation/eval/alignment_matrix.py +176 -176
  26. package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +110 -110
  27. package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +104 -104
  28. package/templates/qoder/scripts/validation/eval/bf_score.py +218 -218
  29. package/templates/qoder/scripts/validation/eval/code_flywheel.py +150 -150
  30. package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +156 -156
  31. package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +23 -23
  32. package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +143 -143
  33. package/templates/qoder/scripts/validation/eval/gradient_matrix.py +261 -261
  34. package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +33 -33
  35. package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +105 -105
  36. package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +118 -118
  37. package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +128 -128
  38. package/templates/qoder/scripts/validation/eval/prd_flywheel.py +148 -148
  39. package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +166 -166
  40. package/templates/qoder/scripts/validation/eval/recall_flywheel.py +148 -148
  41. package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +139 -139
  42. package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +188 -188
  43. package/templates/qoder/scripts/validation/eval/task_flywheel.py +124 -124
  44. package/templates/qoder/scripts/validation/eval/token_flywheel.py +88 -88
  45. package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +177 -177
  46. package/templates/qoder/scripts/validation/metrics/lint_cases.py +170 -170
  47. package/templates/qoder/scripts/validation/test/test_context_cache.py +78 -78
  48. package/templates/qoder/scripts/validation/test/test_lint_cases.py +60 -60
  49. package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +70 -70
  50. package/templates/qoder/commands/wl-data.md +0 -46
  51. package/templates/qoder/commands/wl-fix.md +0 -47
  52. package/templates/qoder/commands/wl-knowledge.md +0 -41
  53. package/templates/qoder/commands/wl-review.md +0 -49
  54. package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +0 -1
@@ -1,218 +1,218 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """bf_score.py - 业务保真(BF)统一打分器:吃【真命令产物文件】打 BF 分。
4
-
5
- 把 FW9-11 的 BF 尺子从"代理生成+打分"改成"读真产物文件+打分"。
6
- 宿主真跑 /wl-prd /wl-design /wl-code /wl-search 出的真产物 → 本工具打 BF 分(对真 ICS 业务数据)。
7
-
8
- 用法:
9
- python bf_score.py prd <PRD.md> [--feature 保险] # BF1 schema保真 + BF2 内容保真(LLM-judge)
10
- python bf_score.py proto <原型.html> # BF3 组件保真 + BF4 颜色保真
11
- python bf_score.py code <实体列表.txt|diff> --feature 保险 # BF5 feature精准
12
- python bf_score.py search <结果.json或txt> --feature 保险 # BF6 feature相关率
13
-
14
- 跑法(容器内,接真业务数据):
15
- docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/bf_score.py prd /path/REQ.md --feature 保险
16
- """
17
- import os
18
- import re
19
- import sys
20
- import json
21
- import colorsys
22
- import urllib.request
23
- import urllib.error
24
-
25
- for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
26
- if os.path.isdir(_p) and _p not in sys.path:
27
- sys.path.insert(0, _p)
28
-
29
- from sqlalchemy import text # noqa: E402
30
- from app.db import SessionLocal # noqa: E402
31
-
32
- _H = None # lazy: 避免与 mcp_handlers 循环 import
33
-
34
-
35
- def _hub():
36
- """惰性取 mcp_handlers(dispatch rag_search/get_design_system),避免循环 import。"""
37
- global _H
38
- if _H is None:
39
- from app.routers import mcp_handlers as _h
40
- try:
41
- _h._init_handlers()
42
- except Exception:
43
- pass
44
- _H = _h
45
- return _H
46
-
47
- CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
48
- CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
49
- JUDGE_MODEL = "qwen-max"
50
- _NEUTRAL = {(255, 255, 255), (0, 0, 0), (51, 51, 51), (102, 102, 102), (153, 153, 153),
51
- (240, 240, 240), (250, 250, 250), (217, 217, 217), (232, 232, 232), (245, 245, 245)}
52
- _TABLE_RE = re.compile(r'\b(t_[a-z_]+|tb_[a-z_]+|report_[a-z_]+|salary_[a-z_]+|[a-z]+_[a-z_]+_table)\b', re.IGNORECASE)
53
-
54
-
55
- def _read(path):
56
- try:
57
- with open(path, encoding="utf-8", errors="replace") as f:
58
- return f.read()
59
- except Exception as e:
60
- return ""
61
-
62
-
63
- def _q(s, sql, p=None):
64
- try:
65
- return s.execute(text(sql), p or {}).fetchall()
66
- except Exception:
67
- try:
68
- s.rollback()
69
- except Exception:
70
- pass
71
- return []
72
-
73
-
74
- def chat(model, prompt, max_tokens=10, timeout=60):
75
- body = json.dumps({"model": model, "messages": [{"role": "user", "content": prompt}],
76
- "temperature": 0.0, "max_tokens": max_tokens}).encode()
77
- for a in range(3):
78
- try:
79
- req = urllib.request.Request(CHAT_URL, data=body,
80
- headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
81
- return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
82
- except Exception:
83
- if a < 2:
84
- import time
85
- time.sleep(1.0 * (a + 1))
86
- else:
87
- return ""
88
-
89
-
90
- # ── BF1: PRD schema 保真 ──
91
- def bf1_schema(prd_text):
92
- mentioned = set(m.lower() for m in _TABLE_RE.findall(prd_text))
93
- if not mentioned:
94
- return {"score": 0.0, "mentioned": 0, "note": "PRD 未提表名"}
95
- s = SessionLocal()
96
- all_tabs = set(r[0].lower() for r in _q(s, "SELECT table_name FROM db_tables"))
97
- s.close()
98
- real = mentioned & all_tabs
99
- return {"score": round(len(real) / len(mentioned), 3), "mentioned": len(mentioned), "real": len(real)}
100
-
101
-
102
- # ── BF2: PRD 内容保真(LLM-judge) ──
103
- def bf2_content(prd_text, feature):
104
- r = _hub().dispatch("rag_search", {"query": feature, "kind": "prd", "top_k": 1}) or {}
105
- items = r.get("items") or []
106
- ref = (items[0].get("text") or "")[:600] if items else ""
107
- if not ref:
108
- return {"score": 0, "note": "无真 PRD 范本"}
109
- p = ("你是 ICS(车辆物联网系统)资深产品专家。对照【真实 ICS PRD 范本】,给【生成 PRD】业务保真度打分 1-5:\n"
110
- "5=完全 ICS 业务写法(系统级/字段级/真业务流程);3=部分;1=通用产品宣传文。\n"
111
- f"【真 ICS PRD 范本】:\n{ref}\n\n【待评 PRD】:\n{prd_text[:1000]}\n\n只输出一个整数(1-5)。")
112
- out = chat(JUDGE_MODEL, p)
113
- m = re.search(r"[1-5]", out)
114
- return {"score": int(m.group()) if m else 0, "ref_len": len(ref)}
115
-
116
-
117
- # ── BF3/4: 原型保真 ──
118
- def _design():
119
- r = _hub().dispatch("get_design_system", {}) or {}
120
- proj = next((p for p in r.get("projects", []) if p.get("project") == "fywl-ui"), {})
121
- colors = proj.get("colors", {}) or {}
122
- palette = set()
123
- for v in colors.values():
124
- if isinstance(v, str) and v.startswith("hsl"):
125
- m = re.match(r"hsl\(\s*([\d.]+)[,\s]+([\d.]+)%[,\s]+([\d.]+)%", v)
126
- if m:
127
- h, s_, l = float(m.group(1)) / 360, float(m.group(2)) / 100, float(m.group(3)) / 100
128
- r_, g_, b_ = colorsys.hls_to_rgb(h, l, s_)
129
- palette.add((round(r_ * 255), round(g_ * 255), round(b_ * 255)))
130
- comps = set()
131
- for d in (proj.get("commonComponents", {}), proj.get("formComponents", {})):
132
- if isinstance(d, dict):
133
- comps.update(k.lower() for k in d)
134
- return palette, comps
135
-
136
-
137
- def _extract_colors(html):
138
- out = set()
139
- for m in re.findall(r"#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b", html):
140
- h = m if len(m) == 6 else m * 2
141
- out.add((int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)))
142
- for m in re.findall(r"hsl\(\s*([\d.]+)[,\s]+([\d.]+)%[,\s]+([\d.]+)%", html):
143
- h, s_, l = float(m[0]) / 360, float(m[1]) / 100, float(m[2]) / 100
144
- r_, g_, b_ = colorsys.hls_to_rgb(h, l, s_)
145
- out.add((round(r_ * 255), round(g_ * 255), round(b_ * 255)))
146
- for m in re.findall(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)", html):
147
- out.add((int(m[0]), int(m[1]), int(m[2])))
148
- return out
149
-
150
-
151
- def _color_real(c, palette):
152
- if c in _NEUTRAL:
153
- return True
154
- return any(all(abs(a - b) <= 40 for a, b in zip(c, p)) for p in palette)
155
-
156
-
157
- def bf34_proto(html):
158
- palette, comps = _design()
159
- cols = _extract_colors(html)
160
- color_score = (sum(1 for c in cols if _color_real(c, palette)) / len(cols)) if cols else 0.0
161
- used = set(m.lower() for m in re.findall(r"<(?:el|van|a|n)-([\w-]+)", html))
162
- comp_score = (sum(1 for c in used if c in comps) / len(used)) if used and comps else (1.0 if not comps else 0.0)
163
- return {"bf3_component": round(comp_score, 3), "bf4_color": round(color_score, 3),
164
- "colors": len(cols), "components": len(used)}
165
-
166
-
167
- # ── BF5/6: feature 实体精准/相关率 ──
168
- def _feature_gold(feature):
169
- s = SessionLocal()
170
- rows = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) "
171
- "AND type IN ('CONTROLLER','SERVICE','TABLE','COLUMN','FUNCTION') LIMIT 30", {"p": f"%{feature}%"})
172
- s.close()
173
- return set(c for c, _ in rows if c)
174
-
175
-
176
- def bf56_entities(text, feature):
177
- gold = _feature_gold(feature)
178
- cands = set(re.findall(r"[A-Za-z_][\w]{2,}", text))
179
- if not cands:
180
- return {"score": 0.0, "candidates": 0, "gold": len(gold)}
181
- hit = sum(1 for c in cands if c in gold)
182
- return {"score": round(hit / len(cands), 3), "candidates": len(cands), "gold_hit": hit, "gold": len(gold)}
183
-
184
-
185
- def main():
186
- if len(sys.argv) < 3:
187
- sys.stderr.write(__doc__)
188
- return 1
189
- kind, path = sys.argv[1], sys.argv[2]
190
- feature = None
191
- if "--feature" in sys.argv:
192
- feature = sys.argv[sys.argv.index("--feature") + 1]
193
- text = _read(path)
194
- if not text:
195
- sys.stderr.write("读不到文件: %s\n" % path)
196
- return 1
197
- out = {"file": os.path.basename(path), "kind": kind}
198
- if kind == "prd":
199
- out["BF1_schema"] = bf1_schema(text)
200
- if feature:
201
- out["BF2_content"] = bf2_content(text, feature)
202
- elif kind == "proto":
203
- out.update(bf34_proto(text))
204
- elif kind in ("code", "search"):
205
- if not feature:
206
- sys.stderr.write("code/search 需 --feature\n")
207
- return 1
208
- bf = bf56_entities(text, feature)
209
- out["BF5_code" if kind == "code" else "BF6_search"] = bf
210
- else:
211
- sys.stderr.write("未知 kind: %s (prd/proto/code/search)\n" % kind)
212
- return 1
213
- print(json.dumps(out, ensure_ascii=False, indent=2))
214
- return 0
215
-
216
-
217
- if __name__ == "__main__":
218
- sys.exit(main())
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """bf_score.py - 业务保真(BF)统一打分器:吃【真命令产物文件】打 BF 分。
4
+
5
+ 把 FW9-11 的 BF 尺子从"代理生成+打分"改成"读真产物文件+打分"。
6
+ 宿主真跑 /wl-prd /wl-design /wl-code /wl-search 出的真产物 → 本工具打 BF 分(对真 ICS 业务数据)。
7
+
8
+ 用法:
9
+ python bf_score.py prd <PRD.md> [--feature 保险] # BF1 schema保真 + BF2 内容保真(LLM-judge)
10
+ python bf_score.py proto <原型.html> # BF3 组件保真 + BF4 颜色保真
11
+ python bf_score.py code <实体列表.txt|diff> --feature 保险 # BF5 feature精准
12
+ python bf_score.py search <结果.json或txt> --feature 保险 # BF6 feature相关率
13
+
14
+ 跑法(容器内,接真业务数据):
15
+ docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/bf_score.py prd /path/REQ.md --feature 保险
16
+ """
17
+ import os
18
+ import re
19
+ import sys
20
+ import json
21
+ import colorsys
22
+ import urllib.request
23
+ import urllib.error
24
+
25
+ for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
26
+ if os.path.isdir(_p) and _p not in sys.path:
27
+ sys.path.insert(0, _p)
28
+
29
+ from sqlalchemy import text # noqa: E402
30
+ from app.db import SessionLocal # noqa: E402
31
+
32
+ _H = None # lazy: 避免与 mcp_handlers 循环 import
33
+
34
+
35
+ def _hub():
36
+ """惰性取 mcp_handlers(dispatch rag_search/get_design_system),避免循环 import。"""
37
+ global _H
38
+ if _H is None:
39
+ from app.routers import mcp_handlers as _h
40
+ try:
41
+ _h._init_handlers()
42
+ except Exception:
43
+ pass
44
+ _H = _h
45
+ return _H
46
+
47
+ CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
48
+ CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
49
+ JUDGE_MODEL = "qwen-max"
50
+ _NEUTRAL = {(255, 255, 255), (0, 0, 0), (51, 51, 51), (102, 102, 102), (153, 153, 153),
51
+ (240, 240, 240), (250, 250, 250), (217, 217, 217), (232, 232, 232), (245, 245, 245)}
52
+ _TABLE_RE = re.compile(r'\b(t_[a-z_]+|tb_[a-z_]+|report_[a-z_]+|salary_[a-z_]+|[a-z]+_[a-z_]+_table)\b', re.IGNORECASE)
53
+
54
+
55
+ def _read(path):
56
+ try:
57
+ with open(path, encoding="utf-8", errors="replace") as f:
58
+ return f.read()
59
+ except Exception as e:
60
+ return ""
61
+
62
+
63
+ def _q(s, sql, p=None):
64
+ try:
65
+ return s.execute(text(sql), p or {}).fetchall()
66
+ except Exception:
67
+ try:
68
+ s.rollback()
69
+ except Exception:
70
+ pass
71
+ return []
72
+
73
+
74
+ def chat(model, prompt, max_tokens=10, timeout=60):
75
+ body = json.dumps({"model": model, "messages": [{"role": "user", "content": prompt}],
76
+ "temperature": 0.0, "max_tokens": max_tokens}).encode()
77
+ for a in range(3):
78
+ try:
79
+ req = urllib.request.Request(CHAT_URL, data=body,
80
+ headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
81
+ return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
82
+ except Exception:
83
+ if a < 2:
84
+ import time
85
+ time.sleep(1.0 * (a + 1))
86
+ else:
87
+ return ""
88
+
89
+
90
+ # ── BF1: PRD schema 保真 ──
91
+ def bf1_schema(prd_text):
92
+ mentioned = set(m.lower() for m in _TABLE_RE.findall(prd_text))
93
+ if not mentioned:
94
+ return {"score": 0.0, "mentioned": 0, "note": "PRD 未提表名"}
95
+ s = SessionLocal()
96
+ all_tabs = set(r[0].lower() for r in _q(s, "SELECT table_name FROM db_tables"))
97
+ s.close()
98
+ real = mentioned & all_tabs
99
+ return {"score": round(len(real) / len(mentioned), 3), "mentioned": len(mentioned), "real": len(real)}
100
+
101
+
102
+ # ── BF2: PRD 内容保真(LLM-judge) ──
103
+ def bf2_content(prd_text, feature):
104
+ r = _hub().dispatch("rag_search", {"query": feature, "kind": "prd", "top_k": 1}) or {}
105
+ items = r.get("items") or []
106
+ ref = (items[0].get("text") or "")[:600] if items else ""
107
+ if not ref:
108
+ return {"score": 0, "note": "无真 PRD 范本"}
109
+ p = ("你是 ICS(车辆物联网系统)资深产品专家。对照【真实 ICS PRD 范本】,给【生成 PRD】业务保真度打分 1-5:\n"
110
+ "5=完全 ICS 业务写法(系统级/字段级/真业务流程);3=部分;1=通用产品宣传文。\n"
111
+ f"【真 ICS PRD 范本】:\n{ref}\n\n【待评 PRD】:\n{prd_text[:1000]}\n\n只输出一个整数(1-5)。")
112
+ out = chat(JUDGE_MODEL, p)
113
+ m = re.search(r"[1-5]", out)
114
+ return {"score": int(m.group()) if m else 0, "ref_len": len(ref)}
115
+
116
+
117
+ # ── BF3/4: 原型保真 ──
118
+ def _design():
119
+ r = _hub().dispatch("get_design_system", {}) or {}
120
+ proj = next((p for p in r.get("projects", []) if p.get("project") == "fywl-ui"), {})
121
+ colors = proj.get("colors", {}) or {}
122
+ palette = set()
123
+ for v in colors.values():
124
+ if isinstance(v, str) and v.startswith("hsl"):
125
+ m = re.match(r"hsl\(\s*([\d.]+)[,\s]+([\d.]+)%[,\s]+([\d.]+)%", v)
126
+ if m:
127
+ h, s_, l = float(m.group(1)) / 360, float(m.group(2)) / 100, float(m.group(3)) / 100
128
+ r_, g_, b_ = colorsys.hls_to_rgb(h, l, s_)
129
+ palette.add((round(r_ * 255), round(g_ * 255), round(b_ * 255)))
130
+ comps = set()
131
+ for d in (proj.get("commonComponents", {}), proj.get("formComponents", {})):
132
+ if isinstance(d, dict):
133
+ comps.update(k.lower() for k in d)
134
+ return palette, comps
135
+
136
+
137
+ def _extract_colors(html):
138
+ out = set()
139
+ for m in re.findall(r"#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b", html):
140
+ h = m if len(m) == 6 else m * 2
141
+ out.add((int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)))
142
+ for m in re.findall(r"hsl\(\s*([\d.]+)[,\s]+([\d.]+)%[,\s]+([\d.]+)%", html):
143
+ h, s_, l = float(m[0]) / 360, float(m[1]) / 100, float(m[2]) / 100
144
+ r_, g_, b_ = colorsys.hls_to_rgb(h, l, s_)
145
+ out.add((round(r_ * 255), round(g_ * 255), round(b_ * 255)))
146
+ for m in re.findall(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)", html):
147
+ out.add((int(m[0]), int(m[1]), int(m[2])))
148
+ return out
149
+
150
+
151
+ def _color_real(c, palette):
152
+ if c in _NEUTRAL:
153
+ return True
154
+ return any(all(abs(a - b) <= 40 for a, b in zip(c, p)) for p in palette)
155
+
156
+
157
+ def bf34_proto(html):
158
+ palette, comps = _design()
159
+ cols = _extract_colors(html)
160
+ color_score = (sum(1 for c in cols if _color_real(c, palette)) / len(cols)) if cols else 0.0
161
+ used = set(m.lower() for m in re.findall(r"<(?:el|van|a|n)-([\w-]+)", html))
162
+ comp_score = (sum(1 for c in used if c in comps) / len(used)) if used and comps else (1.0 if not comps else 0.0)
163
+ return {"bf3_component": round(comp_score, 3), "bf4_color": round(color_score, 3),
164
+ "colors": len(cols), "components": len(used)}
165
+
166
+
167
+ # ── BF5/6: feature 实体精准/相关率 ──
168
+ def _feature_gold(feature):
169
+ s = SessionLocal()
170
+ rows = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) "
171
+ "AND type IN ('CONTROLLER','SERVICE','TABLE','COLUMN','FUNCTION') LIMIT 30", {"p": f"%{feature}%"})
172
+ s.close()
173
+ return set(c for c, _ in rows if c)
174
+
175
+
176
+ def bf56_entities(text, feature):
177
+ gold = _feature_gold(feature)
178
+ cands = set(re.findall(r"[A-Za-z_][\w]{2,}", text))
179
+ if not cands:
180
+ return {"score": 0.0, "candidates": 0, "gold": len(gold)}
181
+ hit = sum(1 for c in cands if c in gold)
182
+ return {"score": round(hit / len(cands), 3), "candidates": len(cands), "gold_hit": hit, "gold": len(gold)}
183
+
184
+
185
+ def main():
186
+ if len(sys.argv) < 3:
187
+ sys.stderr.write(__doc__)
188
+ return 1
189
+ kind, path = sys.argv[1], sys.argv[2]
190
+ feature = None
191
+ if "--feature" in sys.argv:
192
+ feature = sys.argv[sys.argv.index("--feature") + 1]
193
+ text = _read(path)
194
+ if not text:
195
+ sys.stderr.write("读不到文件: %s\n" % path)
196
+ return 1
197
+ out = {"file": os.path.basename(path), "kind": kind}
198
+ if kind == "prd":
199
+ out["BF1_schema"] = bf1_schema(text)
200
+ if feature:
201
+ out["BF2_content"] = bf2_content(text, feature)
202
+ elif kind == "proto":
203
+ out.update(bf34_proto(text))
204
+ elif kind in ("code", "search"):
205
+ if not feature:
206
+ sys.stderr.write("code/search 需 --feature\n")
207
+ return 1
208
+ bf = bf56_entities(text, feature)
209
+ out["BF5_code" if kind == "code" else "BF6_search"] = bf
210
+ else:
211
+ sys.stderr.write("未知 kind: %s (prd/proto/code/search)\n" % kind)
212
+ return 1
213
+ print(json.dumps(out, ensure_ascii=False, indent=2))
214
+ return 0
215
+
216
+
217
+ if __name__ == "__main__":
218
+ sys.exit(main())