@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.
- package/bin/cli.js +0 -7
- package/package.json +29 -29
- package/templates/qoder/commands/optional/wl-spec.md +0 -8
- package/templates/qoder/commands/optional/wl-status.md +0 -8
- package/templates/qoder/commands/wl-code.md +0 -8
- package/templates/qoder/commands/wl-commit.md +0 -8
- package/templates/qoder/commands/wl-init.md +129 -129
- package/templates/qoder/commands/wl-search.md +0 -8
- package/templates/qoder/commands/wl-task.md +0 -8
- package/templates/qoder/commands/wl-test.md +0 -8
- package/templates/qoder/config.yaml +2 -3
- package/templates/qoder/contracts/insight.md +55 -55
- package/templates/qoder/hooks/pre-tool-use-commit.py +124 -124
- package/templates/qoder/hooks/session-start.py +384 -384
- package/templates/qoder/hooks/stop-eval.py +383 -383
- package/templates/qoder/scripts/capability/adapters/mcp.py +322 -322
- package/templates/qoder/scripts/capability/registry.py +245 -245
- package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +14 -0
- package/templates/qoder/scripts/deployment/setup/setup.py +0 -11
- package/templates/qoder/scripts/domain/task/wlkj_panel.py +1503 -1503
- package/templates/qoder/scripts/foundation/bootstrap.py +145 -156
- package/templates/qoder/scripts/foundation/io/context_cache.py +94 -94
- package/templates/qoder/scripts/orchestration/wlkj.py +295 -306
- package/templates/qoder/scripts/tool_guide.md +70 -70
- package/templates/qoder/scripts/validation/eval/alignment_matrix.py +176 -176
- package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +110 -110
- package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +104 -104
- package/templates/qoder/scripts/validation/eval/bf_score.py +218 -218
- package/templates/qoder/scripts/validation/eval/code_flywheel.py +150 -150
- package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +156 -156
- package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +23 -23
- package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +143 -143
- package/templates/qoder/scripts/validation/eval/gradient_matrix.py +261 -261
- package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +33 -33
- package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +105 -105
- package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +118 -118
- package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +128 -128
- package/templates/qoder/scripts/validation/eval/prd_flywheel.py +148 -148
- package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +166 -166
- package/templates/qoder/scripts/validation/eval/recall_flywheel.py +148 -148
- package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +139 -139
- package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +188 -188
- package/templates/qoder/scripts/validation/eval/task_flywheel.py +124 -124
- package/templates/qoder/scripts/validation/eval/token_flywheel.py +88 -88
- package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +177 -177
- package/templates/qoder/scripts/validation/metrics/lint_cases.py +170 -170
- package/templates/qoder/scripts/validation/test/test_context_cache.py +78 -78
- package/templates/qoder/scripts/validation/test/test_lint_cases.py +60 -60
- package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +70 -70
- package/templates/qoder/commands/wl-data.md +0 -46
- package/templates/qoder/commands/wl-fix.md +0 -47
- package/templates/qoder/commands/wl-knowledge.md +0 -41
- package/templates/qoder/commands/wl-review.md +0 -49
- package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +0 -1
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
"""token_flywheel.py — FW5 token 维度飞轮(§9.1 "+工作流 token ≤ 裸×0.8" 命令层)。
|
|
2
|
-
|
|
3
|
-
T1.2 把 wl-search/wl-prd 的接口铁律 boilerplate(每命令重复)抽到单一 tool_guide.md
|
|
4
|
-
(SessionStart 每会话注入一次)。本尺子量 token 节省:
|
|
5
|
-
- per_command_saving = 旧 boilerplate tokens - 新 ref tokens
|
|
6
|
-
- amortized: N 命令/会话 → 总省 = N×per_command_saving - tool_guide_injection(once)
|
|
7
|
-
- break_even = 注入几次命令回本
|
|
8
|
-
DashScope 分词器量真实 token。
|
|
9
|
-
|
|
10
|
-
跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/token_flywheel.py
|
|
11
|
-
"""
|
|
12
|
-
import os
|
|
13
|
-
import sys
|
|
14
|
-
import json
|
|
15
|
-
import urllib.request
|
|
16
|
-
import urllib.error
|
|
17
|
-
|
|
18
|
-
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
19
|
-
if os.path.isdir(_p) and _p not in sys.path:
|
|
20
|
-
sys.path.insert(0, _p)
|
|
21
|
-
|
|
22
|
-
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
23
|
-
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
24
|
-
MODEL = "qwen-turbo"
|
|
25
|
-
|
|
26
|
-
# T1.2 抽走的接口铁律内容(≈ 每命令旧 boilerplate,现归一到 tool_guide §2)
|
|
27
|
-
OLD_BLOCK = """## 🚨 接口铁律(防混名/防歧义,先读)
|
|
28
|
-
**① 三套名字已互通**(引擎层别名,AI 猜哪个都对,别因"未知子命令"慌):
|
|
29
|
-
| 干啥 | wlkj.py / kg.py 子命令 | MCP 工具名 |
|
|
30
|
-
|---|---|---|
|
|
31
|
-
| 搜代码 | search <词> | search_code |
|
|
32
|
-
| 搜 API | api <词> / search <词> | search_api |
|
|
33
|
-
| 语义检索 | semantic <词> | rag_search |
|
|
34
|
-
| 上下文打包 | context <词> | context_pack |
|
|
35
|
-
| 影响分析 | impact <实体> | get_impact |
|
|
36
|
-
**② 中文歧义词先提炼**:保险→车辆保险/商业险/交强险;异常→告警/报错;计划→作业/养护;列表→带业务对象。
|
|
37
|
-
**③ 禁止 find/grep 手动找模块**——那是 search 的活。只有 search 返 0 或要看具体文件实现时才 Grep/Read。"""
|
|
38
|
-
|
|
39
|
-
# T1.2 后命令里的引用(3 行,boilerplate 瘦身后)
|
|
40
|
-
NEW_REF = """## 🚨 接口铁律(见 tool_guide · 会话已注入精简版)
|
|
41
|
-
单一信源 .qoder/scripts/tool_guide.md(SessionStart 已注入)。两条红线:三套名都认别慌;禁 find/grep 全盘扫。"""
|
|
42
|
-
|
|
43
|
-
# SessionStart 注入的 tool_guide 精简版(每会话一次成本)
|
|
44
|
-
INJECTION = """## Tool Guide (共用工具用法 · 完整见 .qoder/scripts/tool_guide.md)
|
|
45
|
-
环境自检: R=$(python ~/.qoderwork/repo_root.py) || R=. ; PY=python 或 python3
|
|
46
|
-
接口三套名互通: wlkj.py/kg.py/MCP 名字都认(search/semantic/context/impact)
|
|
47
|
-
中文歧义词先提炼: 保险->车险 · 异常->告警 · 计划->作业
|
|
48
|
-
禁全盘扫: 不要 find/grep -r/findstr /s/os.walk data/code
|
|
49
|
-
优先级口诀: 精确搜用Grep · 语义找用rag_search · 取全用context_pack · 查库先list_envs · 影响用get_impact"""
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def tokens(text):
|
|
53
|
-
"""用 DashScope 分词器量真实 prompt token 数。"""
|
|
54
|
-
body = json.dumps({"model": MODEL,
|
|
55
|
-
"messages": [{"role": "user", "content": text}], "max_tokens": 1}).encode()
|
|
56
|
-
try:
|
|
57
|
-
req = urllib.request.Request(CHAT_URL, data=body,
|
|
58
|
-
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
59
|
-
return json.loads(urllib.request.urlopen(req, timeout=30).read())["usage"]["prompt_tokens"]
|
|
60
|
-
except Exception as e:
|
|
61
|
-
return -1
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def main():
|
|
65
|
-
old_t = tokens(OLD_BLOCK)
|
|
66
|
-
new_t = tokens(NEW_REF)
|
|
67
|
-
inj_t = tokens(INJECTION)
|
|
68
|
-
per_cmd_save = old_t - new_t
|
|
69
|
-
# amortized: N 命令/会话 → 净省 = N×per_cmd_save - inj_t(注入成本一次性)
|
|
70
|
-
print("## FW5 token 维度飞轮(T1.2 boilerplate→tool_guide 单一信源)")
|
|
71
|
-
print("| 项 | tokens |")
|
|
72
|
-
print("|---|---|")
|
|
73
|
-
print("| 旧接口铁律/命令(每命令重复) | %d |" % old_t)
|
|
74
|
-
print("| 新3行ref/命令 | %d |" % new_t)
|
|
75
|
-
print("| tool_guide 注入/会话(once) | %d |" % inj_t)
|
|
76
|
-
print("| per-命令节省 | %d |" % per_cmd_save)
|
|
77
|
-
print("")
|
|
78
|
-
print("amortized 净省(N 命令/会话)= N×%d - %d:" % (per_cmd_save, inj_t))
|
|
79
|
-
for n in (1, 2, 3, 5, 10):
|
|
80
|
-
net = n * per_cmd_save - inj_t
|
|
81
|
-
print(" N=%2d: 净省 %d tokens %s" % (n, net, "✓回本" if net > 0 else "(未回本)"))
|
|
82
|
-
be = (inj_t + per_cmd_save - 1) // per_cmd_save if per_cmd_save > 0 else 0
|
|
83
|
-
print("break-even: ≈%d 命令/会话回本(之后每命令净省 %d tokens)" % (be, per_cmd_save))
|
|
84
|
-
print("\n结论: 多命令会话(≥%d)净省 token,符 §9.1 命令层 token 降。" % max(be, 2))
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if __name__ == "__main__":
|
|
88
|
-
main()
|
|
1
|
+
"""token_flywheel.py — FW5 token 维度飞轮(§9.1 "+工作流 token ≤ 裸×0.8" 命令层)。
|
|
2
|
+
|
|
3
|
+
T1.2 把 wl-search/wl-prd 的接口铁律 boilerplate(每命令重复)抽到单一 tool_guide.md
|
|
4
|
+
(SessionStart 每会话注入一次)。本尺子量 token 节省:
|
|
5
|
+
- per_command_saving = 旧 boilerplate tokens - 新 ref tokens
|
|
6
|
+
- amortized: N 命令/会话 → 总省 = N×per_command_saving - tool_guide_injection(once)
|
|
7
|
+
- break_even = 注入几次命令回本
|
|
8
|
+
DashScope 分词器量真实 token。
|
|
9
|
+
|
|
10
|
+
跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/token_flywheel.py
|
|
11
|
+
"""
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import json
|
|
15
|
+
import urllib.request
|
|
16
|
+
import urllib.error
|
|
17
|
+
|
|
18
|
+
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
19
|
+
if os.path.isdir(_p) and _p not in sys.path:
|
|
20
|
+
sys.path.insert(0, _p)
|
|
21
|
+
|
|
22
|
+
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
23
|
+
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
24
|
+
MODEL = "qwen-turbo"
|
|
25
|
+
|
|
26
|
+
# T1.2 抽走的接口铁律内容(≈ 每命令旧 boilerplate,现归一到 tool_guide §2)
|
|
27
|
+
OLD_BLOCK = """## 🚨 接口铁律(防混名/防歧义,先读)
|
|
28
|
+
**① 三套名字已互通**(引擎层别名,AI 猜哪个都对,别因"未知子命令"慌):
|
|
29
|
+
| 干啥 | wlkj.py / kg.py 子命令 | MCP 工具名 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| 搜代码 | search <词> | search_code |
|
|
32
|
+
| 搜 API | api <词> / search <词> | search_api |
|
|
33
|
+
| 语义检索 | semantic <词> | rag_search |
|
|
34
|
+
| 上下文打包 | context <词> | context_pack |
|
|
35
|
+
| 影响分析 | impact <实体> | get_impact |
|
|
36
|
+
**② 中文歧义词先提炼**:保险→车辆保险/商业险/交强险;异常→告警/报错;计划→作业/养护;列表→带业务对象。
|
|
37
|
+
**③ 禁止 find/grep 手动找模块**——那是 search 的活。只有 search 返 0 或要看具体文件实现时才 Grep/Read。"""
|
|
38
|
+
|
|
39
|
+
# T1.2 后命令里的引用(3 行,boilerplate 瘦身后)
|
|
40
|
+
NEW_REF = """## 🚨 接口铁律(见 tool_guide · 会话已注入精简版)
|
|
41
|
+
单一信源 .qoder/scripts/tool_guide.md(SessionStart 已注入)。两条红线:三套名都认别慌;禁 find/grep 全盘扫。"""
|
|
42
|
+
|
|
43
|
+
# SessionStart 注入的 tool_guide 精简版(每会话一次成本)
|
|
44
|
+
INJECTION = """## Tool Guide (共用工具用法 · 完整见 .qoder/scripts/tool_guide.md)
|
|
45
|
+
环境自检: R=$(python ~/.qoderwork/repo_root.py) || R=. ; PY=python 或 python3
|
|
46
|
+
接口三套名互通: wlkj.py/kg.py/MCP 名字都认(search/semantic/context/impact)
|
|
47
|
+
中文歧义词先提炼: 保险->车险 · 异常->告警 · 计划->作业
|
|
48
|
+
禁全盘扫: 不要 find/grep -r/findstr /s/os.walk data/code
|
|
49
|
+
优先级口诀: 精确搜用Grep · 语义找用rag_search · 取全用context_pack · 查库先list_envs · 影响用get_impact"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def tokens(text):
|
|
53
|
+
"""用 DashScope 分词器量真实 prompt token 数。"""
|
|
54
|
+
body = json.dumps({"model": MODEL,
|
|
55
|
+
"messages": [{"role": "user", "content": text}], "max_tokens": 1}).encode()
|
|
56
|
+
try:
|
|
57
|
+
req = urllib.request.Request(CHAT_URL, data=body,
|
|
58
|
+
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
59
|
+
return json.loads(urllib.request.urlopen(req, timeout=30).read())["usage"]["prompt_tokens"]
|
|
60
|
+
except Exception as e:
|
|
61
|
+
return -1
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def main():
|
|
65
|
+
old_t = tokens(OLD_BLOCK)
|
|
66
|
+
new_t = tokens(NEW_REF)
|
|
67
|
+
inj_t = tokens(INJECTION)
|
|
68
|
+
per_cmd_save = old_t - new_t
|
|
69
|
+
# amortized: N 命令/会话 → 净省 = N×per_cmd_save - inj_t(注入成本一次性)
|
|
70
|
+
print("## FW5 token 维度飞轮(T1.2 boilerplate→tool_guide 单一信源)")
|
|
71
|
+
print("| 项 | tokens |")
|
|
72
|
+
print("|---|---|")
|
|
73
|
+
print("| 旧接口铁律/命令(每命令重复) | %d |" % old_t)
|
|
74
|
+
print("| 新3行ref/命令 | %d |" % new_t)
|
|
75
|
+
print("| tool_guide 注入/会话(once) | %d |" % inj_t)
|
|
76
|
+
print("| per-命令节省 | %d |" % per_cmd_save)
|
|
77
|
+
print("")
|
|
78
|
+
print("amortized 净省(N 命令/会话)= N×%d - %d:" % (per_cmd_save, inj_t))
|
|
79
|
+
for n in (1, 2, 3, 5, 10):
|
|
80
|
+
net = n * per_cmd_save - inj_t
|
|
81
|
+
print(" N=%2d: 净省 %d tokens %s" % (n, net, "✓回本" if net > 0 else "(未回本)"))
|
|
82
|
+
be = (inj_t + per_cmd_save - 1) // per_cmd_save if per_cmd_save > 0 else 0
|
|
83
|
+
print("break-even: ≈%d 命令/会话回本(之后每命令净省 %d tokens)" % (be, per_cmd_save))
|
|
84
|
+
print("\n结论: 多命令会话(≥%d)净省 token,符 §9.1 命令层 token 降。" % max(be, 2))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
main()
|
|
@@ -1,177 +1,177 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""eval_code_ac.py - P0-3/P0-5: spec AC × git diff 符号对账(代码漏实现 AC 检测)
|
|
4
|
-
|
|
5
|
-
被 stop-eval.py wl-code 分支调。读 spec 第6章 PRD 验收标准映射表 AC + git diff HEAD 改的符号
|
|
6
|
-
→ 每条 AC 词命中符号 → 覆盖率 + 未实现清单。AC<100% 且有 missed → exit 2(stop-eval 拦回继续)。
|
|
7
|
-
|
|
8
|
-
诚实(第一版):
|
|
9
|
-
- AC 覆盖判用"AC 文本词 ∈ git diff 符号集"——保守(多拦比漏好),不语义。
|
|
10
|
-
- 符号提取多语言(Java public/private/protected、JS function、Vue methods、Python def)。
|
|
11
|
-
- spec 无第6章 AC → exit 0 放行(不拦无 AC 的 spec,防误拦)。
|
|
12
|
-
- 无近 spec / git 不可用 → exit 0(绝不误拦)。
|
|
13
|
-
|
|
14
|
-
输出(stdout 末行 JSON): {spec, n_ac, n_symbols, rate, covered[], missed[]}
|
|
15
|
-
"""
|
|
16
|
-
import json
|
|
17
|
-
import os
|
|
18
|
-
import re
|
|
19
|
-
import subprocess
|
|
20
|
-
import sys
|
|
21
|
-
import time
|
|
22
|
-
|
|
23
|
-
if sys.platform == 'win32':
|
|
24
|
-
try:
|
|
25
|
-
sys.stdout.reconfigure(encoding='utf-8')
|
|
26
|
-
except Exception:
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
WINDOW = 1800 # 30 分钟内改的 spec 视为本轮产物
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _find_base():
|
|
33
|
-
"""向上找 repo 根(含 .git)。★不能只找 workspace/——.qoder/workspace/ 也存在会误中,
|
|
34
|
-
必须认 .git(仅 repo 根 qoderAll 有, .qoder 无)。"""
|
|
35
|
-
d = os.path.dirname(os.path.abspath(__file__))
|
|
36
|
-
for _ in range(10):
|
|
37
|
-
if os.path.isdir(os.path.join(d, '.git')):
|
|
38
|
-
return d
|
|
39
|
-
p = os.path.dirname(d)
|
|
40
|
-
if p == d:
|
|
41
|
-
break
|
|
42
|
-
d = p
|
|
43
|
-
return os.getcwd()
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
BASE = _find_base()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def find_recent_spec():
|
|
50
|
-
"""近 WINDOW 秒改的 spec.md(workspace/tasks|specs|members)。返最新路径 or None。"""
|
|
51
|
-
now = time.time()
|
|
52
|
-
roots = [os.path.join(BASE, 'workspace', t) for t in ('tasks', 'specs', 'members')]
|
|
53
|
-
best, best_mtime = None, 0
|
|
54
|
-
for root in roots:
|
|
55
|
-
if not os.path.isdir(root):
|
|
56
|
-
continue
|
|
57
|
-
for dp, dirs, files in os.walk(root):
|
|
58
|
-
dirs[:] = [d for d in dirs if d not in ('node_modules', '.git', '__pycache__')]
|
|
59
|
-
for fn in files:
|
|
60
|
-
if not (fn.endswith('.spec.md') or (fn.endswith('.md') and 'spec' in fn.lower())):
|
|
61
|
-
continue
|
|
62
|
-
fp = os.path.join(dp, fn)
|
|
63
|
-
try:
|
|
64
|
-
m = os.path.getmtime(fp)
|
|
65
|
-
except OSError:
|
|
66
|
-
continue
|
|
67
|
-
if now - m <= WINDOW and m > best_mtime:
|
|
68
|
-
best, best_mtime = fp, m
|
|
69
|
-
return best
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def parse_ac(spec_path):
|
|
73
|
-
"""解析 spec 第6章 PRD 验收标准映射表 AC。返 [ac_text, ...]。"""
|
|
74
|
-
try:
|
|
75
|
-
text = open(spec_path, encoding='utf-8', errors='replace').read()
|
|
76
|
-
except Exception:
|
|
77
|
-
return []
|
|
78
|
-
# 第6章"PRD 验收标准映射表"
|
|
79
|
-
m = re.search(r'#{1,3}\s*6[\.\s、]*[^\n]*验收[^\n]*\n(.*?)(?=\n#{1,3}\s|\Z)', text, re.S | re.I)
|
|
80
|
-
section = m.group(1) if m else ''
|
|
81
|
-
if not section: # 兜底: 任一"验收标准"段
|
|
82
|
-
m2 = re.search(r'#{1,3}[^\n]*验收标准[^\n]*\n(.*?)(?=\n#{1,3}\s|\Z)', text, re.S)
|
|
83
|
-
section = m2.group(1) if m2 else ''
|
|
84
|
-
if not section:
|
|
85
|
-
return []
|
|
86
|
-
acs = []
|
|
87
|
-
for line in section.split('\n'):
|
|
88
|
-
line = line.strip()
|
|
89
|
-
if not line.startswith('|') or '---' in line:
|
|
90
|
-
continue
|
|
91
|
-
cols = [c.strip() for c in line.split('|')]
|
|
92
|
-
cols = [c for c in cols if c]
|
|
93
|
-
if not cols:
|
|
94
|
-
continue
|
|
95
|
-
ac = cols[0]
|
|
96
|
-
if len(ac) < 4: # 跳短表头/序号
|
|
97
|
-
continue
|
|
98
|
-
acs.append(ac)
|
|
99
|
-
return acs
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def git_diff_symbols():
|
|
103
|
-
"""git diff HEAD 改的符号(函数/方法/类名)。返 set。多语言。"""
|
|
104
|
-
symbols = set()
|
|
105
|
-
try:
|
|
106
|
-
r = subprocess.run(['git', 'diff', 'HEAD'], cwd=BASE,
|
|
107
|
-
capture_output=True, text=True, timeout=15)
|
|
108
|
-
diff = r.stdout or ''
|
|
109
|
-
except Exception:
|
|
110
|
-
return symbols
|
|
111
|
-
# Java/Python/JS 声明: public/private/protected/static/def/function/class 后的符号
|
|
112
|
-
decl_re = re.compile(
|
|
113
|
-
r'^[+-]\s*(?:public|private|protected|static|final|async|def|function|void|class)\s+'
|
|
114
|
-
r'(?:[\w<>\[\],\s\.]+?\s+)?(\w+)\s*[\(\{]')
|
|
115
|
-
for line in diff.split('\n'):
|
|
116
|
-
if not (line.startswith('+') or line.startswith('-')):
|
|
117
|
-
continue
|
|
118
|
-
m = decl_re.match(line)
|
|
119
|
-
if m:
|
|
120
|
-
symbols.add(m.group(1))
|
|
121
|
-
# 改的文件名(实体线索)
|
|
122
|
-
try:
|
|
123
|
-
names = subprocess.run(['git', 'diff', 'HEAD', '--name-only'], cwd=BASE,
|
|
124
|
-
capture_output=True, text=True, timeout=10).stdout or ''
|
|
125
|
-
for fn in names.split('\n'):
|
|
126
|
-
fn = fn.strip().split('/')[-1]
|
|
127
|
-
base_name = re.sub(r'\.\w+$', '', fn)
|
|
128
|
-
if base_name:
|
|
129
|
-
for w in re.split(r'[-_]', base_name):
|
|
130
|
-
if len(w) > 2:
|
|
131
|
-
symbols.add(w)
|
|
132
|
-
except Exception:
|
|
133
|
-
pass
|
|
134
|
-
return symbols
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def check_coverage(spec_path):
|
|
138
|
-
acs = parse_ac(spec_path)
|
|
139
|
-
if not acs:
|
|
140
|
-
return {'spec': os.path.basename(spec_path), 'n_ac': 0, 'rate': 1.0,
|
|
141
|
-
'covered': [], 'missed': [], 'note': 'spec 第6章无 AC(不拦)'}
|
|
142
|
-
symbols = git_diff_symbols()
|
|
143
|
-
# 符号分词(_enforce_code_ac_gate → enforce/code/ac/gate)小写; AC 词也分词; 双向分词匹配
|
|
144
|
-
sym_words = set()
|
|
145
|
-
for s in symbols:
|
|
146
|
-
for w in re.split(r'[_\-]', s):
|
|
147
|
-
if len(w) > 2:
|
|
148
|
-
sym_words.add(w.lower())
|
|
149
|
-
covered, missed = [], []
|
|
150
|
-
for ac in acs:
|
|
151
|
-
ac_words = set()
|
|
152
|
-
for w in re.findall(r'[A-Za-z_]\w{2,}', ac):
|
|
153
|
-
for part in re.split(r'[_\-]', w):
|
|
154
|
-
if len(part) > 2:
|
|
155
|
-
ac_words.add(part.lower())
|
|
156
|
-
hit = any(w in sym_words for w in ac_words)
|
|
157
|
-
(covered if hit else missed).append(ac)
|
|
158
|
-
rate = round(len(covered) / len(acs), 3)
|
|
159
|
-
return {'spec': os.path.basename(spec_path), 'n_ac': len(acs),
|
|
160
|
-
'n_symbols': len(symbols), 'rate': rate,
|
|
161
|
-
'covered': covered, 'missed': missed}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def main():
|
|
165
|
-
spec = find_recent_spec()
|
|
166
|
-
if not spec:
|
|
167
|
-
print(json.dumps({'error': 'no recent spec', 'rate': 1.0}, ensure_ascii=False))
|
|
168
|
-
sys.exit(0) # 无 spec 不拦(非 wl-code 场景)
|
|
169
|
-
r = check_coverage(spec)
|
|
170
|
-
print(json.dumps(r, ensure_ascii=False))
|
|
171
|
-
if r.get('missed') and r['rate'] < 1.0:
|
|
172
|
-
sys.exit(2) # AC 未全覆盖 → stop-eval 拦
|
|
173
|
-
sys.exit(0)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if __name__ == '__main__':
|
|
177
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""eval_code_ac.py - P0-3/P0-5: spec AC × git diff 符号对账(代码漏实现 AC 检测)
|
|
4
|
+
|
|
5
|
+
被 stop-eval.py wl-code 分支调。读 spec 第6章 PRD 验收标准映射表 AC + git diff HEAD 改的符号
|
|
6
|
+
→ 每条 AC 词命中符号 → 覆盖率 + 未实现清单。AC<100% 且有 missed → exit 2(stop-eval 拦回继续)。
|
|
7
|
+
|
|
8
|
+
诚实(第一版):
|
|
9
|
+
- AC 覆盖判用"AC 文本词 ∈ git diff 符号集"——保守(多拦比漏好),不语义。
|
|
10
|
+
- 符号提取多语言(Java public/private/protected、JS function、Vue methods、Python def)。
|
|
11
|
+
- spec 无第6章 AC → exit 0 放行(不拦无 AC 的 spec,防误拦)。
|
|
12
|
+
- 无近 spec / git 不可用 → exit 0(绝不误拦)。
|
|
13
|
+
|
|
14
|
+
输出(stdout 末行 JSON): {spec, n_ac, n_symbols, rate, covered[], missed[]}
|
|
15
|
+
"""
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import time
|
|
22
|
+
|
|
23
|
+
if sys.platform == 'win32':
|
|
24
|
+
try:
|
|
25
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
WINDOW = 1800 # 30 分钟内改的 spec 视为本轮产物
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _find_base():
|
|
33
|
+
"""向上找 repo 根(含 .git)。★不能只找 workspace/——.qoder/workspace/ 也存在会误中,
|
|
34
|
+
必须认 .git(仅 repo 根 qoderAll 有, .qoder 无)。"""
|
|
35
|
+
d = os.path.dirname(os.path.abspath(__file__))
|
|
36
|
+
for _ in range(10):
|
|
37
|
+
if os.path.isdir(os.path.join(d, '.git')):
|
|
38
|
+
return d
|
|
39
|
+
p = os.path.dirname(d)
|
|
40
|
+
if p == d:
|
|
41
|
+
break
|
|
42
|
+
d = p
|
|
43
|
+
return os.getcwd()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
BASE = _find_base()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def find_recent_spec():
|
|
50
|
+
"""近 WINDOW 秒改的 spec.md(workspace/tasks|specs|members)。返最新路径 or None。"""
|
|
51
|
+
now = time.time()
|
|
52
|
+
roots = [os.path.join(BASE, 'workspace', t) for t in ('tasks', 'specs', 'members')]
|
|
53
|
+
best, best_mtime = None, 0
|
|
54
|
+
for root in roots:
|
|
55
|
+
if not os.path.isdir(root):
|
|
56
|
+
continue
|
|
57
|
+
for dp, dirs, files in os.walk(root):
|
|
58
|
+
dirs[:] = [d for d in dirs if d not in ('node_modules', '.git', '__pycache__')]
|
|
59
|
+
for fn in files:
|
|
60
|
+
if not (fn.endswith('.spec.md') or (fn.endswith('.md') and 'spec' in fn.lower())):
|
|
61
|
+
continue
|
|
62
|
+
fp = os.path.join(dp, fn)
|
|
63
|
+
try:
|
|
64
|
+
m = os.path.getmtime(fp)
|
|
65
|
+
except OSError:
|
|
66
|
+
continue
|
|
67
|
+
if now - m <= WINDOW and m > best_mtime:
|
|
68
|
+
best, best_mtime = fp, m
|
|
69
|
+
return best
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def parse_ac(spec_path):
|
|
73
|
+
"""解析 spec 第6章 PRD 验收标准映射表 AC。返 [ac_text, ...]。"""
|
|
74
|
+
try:
|
|
75
|
+
text = open(spec_path, encoding='utf-8', errors='replace').read()
|
|
76
|
+
except Exception:
|
|
77
|
+
return []
|
|
78
|
+
# 第6章"PRD 验收标准映射表"
|
|
79
|
+
m = re.search(r'#{1,3}\s*6[\.\s、]*[^\n]*验收[^\n]*\n(.*?)(?=\n#{1,3}\s|\Z)', text, re.S | re.I)
|
|
80
|
+
section = m.group(1) if m else ''
|
|
81
|
+
if not section: # 兜底: 任一"验收标准"段
|
|
82
|
+
m2 = re.search(r'#{1,3}[^\n]*验收标准[^\n]*\n(.*?)(?=\n#{1,3}\s|\Z)', text, re.S)
|
|
83
|
+
section = m2.group(1) if m2 else ''
|
|
84
|
+
if not section:
|
|
85
|
+
return []
|
|
86
|
+
acs = []
|
|
87
|
+
for line in section.split('\n'):
|
|
88
|
+
line = line.strip()
|
|
89
|
+
if not line.startswith('|') or '---' in line:
|
|
90
|
+
continue
|
|
91
|
+
cols = [c.strip() for c in line.split('|')]
|
|
92
|
+
cols = [c for c in cols if c]
|
|
93
|
+
if not cols:
|
|
94
|
+
continue
|
|
95
|
+
ac = cols[0]
|
|
96
|
+
if len(ac) < 4: # 跳短表头/序号
|
|
97
|
+
continue
|
|
98
|
+
acs.append(ac)
|
|
99
|
+
return acs
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def git_diff_symbols():
|
|
103
|
+
"""git diff HEAD 改的符号(函数/方法/类名)。返 set。多语言。"""
|
|
104
|
+
symbols = set()
|
|
105
|
+
try:
|
|
106
|
+
r = subprocess.run(['git', 'diff', 'HEAD'], cwd=BASE,
|
|
107
|
+
capture_output=True, text=True, timeout=15)
|
|
108
|
+
diff = r.stdout or ''
|
|
109
|
+
except Exception:
|
|
110
|
+
return symbols
|
|
111
|
+
# Java/Python/JS 声明: public/private/protected/static/def/function/class 后的符号
|
|
112
|
+
decl_re = re.compile(
|
|
113
|
+
r'^[+-]\s*(?:public|private|protected|static|final|async|def|function|void|class)\s+'
|
|
114
|
+
r'(?:[\w<>\[\],\s\.]+?\s+)?(\w+)\s*[\(\{]')
|
|
115
|
+
for line in diff.split('\n'):
|
|
116
|
+
if not (line.startswith('+') or line.startswith('-')):
|
|
117
|
+
continue
|
|
118
|
+
m = decl_re.match(line)
|
|
119
|
+
if m:
|
|
120
|
+
symbols.add(m.group(1))
|
|
121
|
+
# 改的文件名(实体线索)
|
|
122
|
+
try:
|
|
123
|
+
names = subprocess.run(['git', 'diff', 'HEAD', '--name-only'], cwd=BASE,
|
|
124
|
+
capture_output=True, text=True, timeout=10).stdout or ''
|
|
125
|
+
for fn in names.split('\n'):
|
|
126
|
+
fn = fn.strip().split('/')[-1]
|
|
127
|
+
base_name = re.sub(r'\.\w+$', '', fn)
|
|
128
|
+
if base_name:
|
|
129
|
+
for w in re.split(r'[-_]', base_name):
|
|
130
|
+
if len(w) > 2:
|
|
131
|
+
symbols.add(w)
|
|
132
|
+
except Exception:
|
|
133
|
+
pass
|
|
134
|
+
return symbols
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def check_coverage(spec_path):
|
|
138
|
+
acs = parse_ac(spec_path)
|
|
139
|
+
if not acs:
|
|
140
|
+
return {'spec': os.path.basename(spec_path), 'n_ac': 0, 'rate': 1.0,
|
|
141
|
+
'covered': [], 'missed': [], 'note': 'spec 第6章无 AC(不拦)'}
|
|
142
|
+
symbols = git_diff_symbols()
|
|
143
|
+
# 符号分词(_enforce_code_ac_gate → enforce/code/ac/gate)小写; AC 词也分词; 双向分词匹配
|
|
144
|
+
sym_words = set()
|
|
145
|
+
for s in symbols:
|
|
146
|
+
for w in re.split(r'[_\-]', s):
|
|
147
|
+
if len(w) > 2:
|
|
148
|
+
sym_words.add(w.lower())
|
|
149
|
+
covered, missed = [], []
|
|
150
|
+
for ac in acs:
|
|
151
|
+
ac_words = set()
|
|
152
|
+
for w in re.findall(r'[A-Za-z_]\w{2,}', ac):
|
|
153
|
+
for part in re.split(r'[_\-]', w):
|
|
154
|
+
if len(part) > 2:
|
|
155
|
+
ac_words.add(part.lower())
|
|
156
|
+
hit = any(w in sym_words for w in ac_words)
|
|
157
|
+
(covered if hit else missed).append(ac)
|
|
158
|
+
rate = round(len(covered) / len(acs), 3)
|
|
159
|
+
return {'spec': os.path.basename(spec_path), 'n_ac': len(acs),
|
|
160
|
+
'n_symbols': len(symbols), 'rate': rate,
|
|
161
|
+
'covered': covered, 'missed': missed}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def main():
|
|
165
|
+
spec = find_recent_spec()
|
|
166
|
+
if not spec:
|
|
167
|
+
print(json.dumps({'error': 'no recent spec', 'rate': 1.0}, ensure_ascii=False))
|
|
168
|
+
sys.exit(0) # 无 spec 不拦(非 wl-code 场景)
|
|
169
|
+
r = check_coverage(spec)
|
|
170
|
+
print(json.dumps(r, ensure_ascii=False))
|
|
171
|
+
if r.get('missed') and r['rate'] < 1.0:
|
|
172
|
+
sys.exit(2) # AC 未全覆盖 → stop-eval 拦
|
|
173
|
+
sys.exit(0)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == '__main__':
|
|
177
|
+
main()
|