@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,170 +1,170 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""lint_cases.py - P0-3 w2: wl-test 用例格式 linter(9 铁律里文件内容可检的①②)。
|
|
4
|
-
|
|
5
|
-
治"AI 生成旧格式 cases(action+CSS selector)"→ Playwright 新管线只认 intent+anchor,
|
|
6
|
-
旧格式跑不通。被 /wl-test 调(执行前 lint)+ stop-eval 扫近期 cases 文件调。
|
|
7
|
-
|
|
8
|
-
铁律映射(wl-test.md 9 条,本 linter 检文件内容可判的):
|
|
9
|
-
① intent+anchor 格式,禁旧 action+CSS selector → BLOCK(硬拦)
|
|
10
|
-
② 占位符精确 {{ask:key}}(禁裸"测试账号"/硬编码凭据) → WARN(软提醒)
|
|
11
|
-
其余(③--cases-file ④recall ⑤URL斜杠 ⑥相对url ⑦⑧⑨执行流)非文件内容,命令/执行层管。
|
|
12
|
-
|
|
13
|
-
用法:
|
|
14
|
-
python lint_cases.py <cases.json> # lint 单文件
|
|
15
|
-
python lint_cases.py <cases.json> --json # 机器可读
|
|
16
|
-
python lint_cases.py - # 从 stdin 读 cases JSON
|
|
17
|
-
|
|
18
|
-
输出 stdout 末行 JSON: {file, n_cases, violations:[{rule, severity, idx, detail}], passed}
|
|
19
|
-
exit 0 = 过(含 WARN); exit 2 = 有 BLOCK 违规(旧格式); exit 0 = 无 cases/读不到(不拦)。
|
|
20
|
-
"""
|
|
21
|
-
import json
|
|
22
|
-
import os
|
|
23
|
-
import re
|
|
24
|
-
import sys
|
|
25
|
-
|
|
26
|
-
if sys.platform == 'win32':
|
|
27
|
-
try:
|
|
28
|
-
sys.stdout.reconfigure(encoding='utf-8')
|
|
29
|
-
except Exception:
|
|
30
|
-
pass
|
|
31
|
-
|
|
32
|
-
# 旧格式 CSS selector 特征(出现在 target/anchor 里 = 旧 action+CSS 范式)
|
|
33
|
-
_CSS_SELECTOR_RE = re.compile(
|
|
34
|
-
r'#[A-Za-z_][\w-]*' # #id(#submit-btn;不匹 #1 步号/颜色#fff 首字母要求)
|
|
35
|
-
r'|\.[A-Za-z_][\w-]*' # .class(要求点后字母,避 v1.2 版本号误伤)
|
|
36
|
-
r'|\w+\[[^\]]+\]' # tag[attr=...] / [attr]
|
|
37
|
-
r'|>\s*[A-Za-z][\w-]*' # 子代选择器 >
|
|
38
|
-
)
|
|
39
|
-
# 凭据类字段名({{ask:}} 应覆盖这些,禁硬编码)
|
|
40
|
-
_CRED_HINTS = ("账号", "密码", "account", "password", "passwd", "手机", "验证码", "邮箱", "token")
|
|
41
|
-
# 字面凭据(ascii 字母数字/@/./-,3-30 字符;排除中文描述/意图词)
|
|
42
|
-
_LITERAL_CRED_RE = re.compile(r'^[A-Za-z0-9_@.\-]{3,30}$')
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _load_cases(src):
|
|
46
|
-
"""src=文件路径 or '-'(stdin)。返 (cases_list, file_label)。读不到返 ([], label)。"""
|
|
47
|
-
if src == "-":
|
|
48
|
-
raw = sys.stdin.read()
|
|
49
|
-
label = "<stdin>"
|
|
50
|
-
else:
|
|
51
|
-
try:
|
|
52
|
-
with open(src, encoding="utf-8", errors="replace") as f:
|
|
53
|
-
raw = f.read()
|
|
54
|
-
label = os.path.basename(src)
|
|
55
|
-
except Exception:
|
|
56
|
-
return [], src
|
|
57
|
-
raw = raw.strip()
|
|
58
|
-
if not raw:
|
|
59
|
-
return [], label
|
|
60
|
-
try:
|
|
61
|
-
data = json.loads(raw)
|
|
62
|
-
except Exception:
|
|
63
|
-
return [], label
|
|
64
|
-
# cases 可能是 list,或 {cases:[...]} / {steps:[...]}
|
|
65
|
-
if isinstance(data, list):
|
|
66
|
-
return data, label
|
|
67
|
-
if isinstance(data, dict):
|
|
68
|
-
for k in ("cases", "steps", "items", "test_cases"):
|
|
69
|
-
v = data.get(k)
|
|
70
|
-
if isinstance(v, list):
|
|
71
|
-
return v, label
|
|
72
|
-
return [], label
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def _is_block_old_format(case):
|
|
76
|
-
"""铁律①:case 是旧 action+CSS selector 范式 → True(BLOCK)。
|
|
77
|
-
判定:有 'action'+'target' 键(旧范式),或任一值含 CSS selector 特征。"""
|
|
78
|
-
if not isinstance(case, dict):
|
|
79
|
-
return False
|
|
80
|
-
has_action_target = "action" in case and "target" in case
|
|
81
|
-
# target/value/anchor 文本里含 CSS selector
|
|
82
|
-
for k in ("target", "selector", "css"):
|
|
83
|
-
v = case.get(k)
|
|
84
|
-
if isinstance(v, str) and _CSS_SELECTOR_RE.search(v):
|
|
85
|
-
return True
|
|
86
|
-
# anchor 是字符串(应是 {role,name} dict)且像 selector
|
|
87
|
-
anchor = case.get("anchor")
|
|
88
|
-
if isinstance(anchor, str) and _CSS_SELECTOR_RE.search(anchor):
|
|
89
|
-
return True
|
|
90
|
-
# 新格式判定:有 intent + anchor(dict) → 放过;否则若 action+target → 旧
|
|
91
|
-
if has_action_target and not (isinstance(anchor, dict) or "intent" in case):
|
|
92
|
-
return True
|
|
93
|
-
return False
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def _looks_like_literal_cred(v):
|
|
97
|
-
"""值像字面凭据(ascii 字母数字/@/./-,3-30 字符;含中文=多半描述→不算)。"""
|
|
98
|
-
v = v.strip()
|
|
99
|
-
if not v or v.startswith("{{"):
|
|
100
|
-
return False
|
|
101
|
-
if re.search(r'[一-龥]', v): # 含中文 → 描述,不当凭据字面
|
|
102
|
-
return False
|
|
103
|
-
return bool(_LITERAL_CRED_RE.match(v))
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def _cred_without_ask(case):
|
|
107
|
-
"""铁律②:凭据类【本字段】未用 {{ask:}} 包裹(硬编码)→ 返回违规 key 列表(WARN)。
|
|
108
|
-
高精度:只看本字段 key/name 是不是凭据词(不用 desc 全局触发,避误伤 intent/desc),
|
|
109
|
-
且值须像字面凭据(ascii、非中文描述、非占位)。"""
|
|
110
|
-
if not isinstance(case, dict):
|
|
111
|
-
return []
|
|
112
|
-
bad = []
|
|
113
|
-
for k, v in case.items():
|
|
114
|
-
if not isinstance(v, str) or not v:
|
|
115
|
-
continue
|
|
116
|
-
if "{{ask:" in v or v.startswith("{{"):
|
|
117
|
-
continue
|
|
118
|
-
field_name = str(case.get("name") or case.get("field") or k).lower()
|
|
119
|
-
if not any(h in field_name for h in _CRED_HINTS):
|
|
120
|
-
continue # 本字段名不像凭据 → 放过(不拿 desc 连坐)
|
|
121
|
-
if _looks_like_literal_cred(v):
|
|
122
|
-
bad.append(k)
|
|
123
|
-
return bad
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def lint_cases(cases, file_label="<cases>"):
|
|
127
|
-
"""跑 lint,返 {file, n_cases, violations, passed}。"""
|
|
128
|
-
violations = []
|
|
129
|
-
if not isinstance(cases, list):
|
|
130
|
-
return {"file": file_label, "n_cases": 0, "violations": [], "passed": True, "note": "非 list(空/读不到)→不拦"}
|
|
131
|
-
for i, case in enumerate(cases):
|
|
132
|
-
if _is_block_old_format(case):
|
|
133
|
-
violations.append({
|
|
134
|
-
"rule": "① intent+anchor 格式", "severity": "BLOCK", "idx": i,
|
|
135
|
-
"detail": "旧 action+CSS selector 范式(Playwright 新管线不认)→ 改 intent+anchor:{role,name}",
|
|
136
|
-
})
|
|
137
|
-
for k in _cred_without_ask(case):
|
|
138
|
-
violations.append({
|
|
139
|
-
"rule": "② {{ask:}} 占位", "severity": "WARN", "idx": i,
|
|
140
|
-
"detail": "字段 '%s' 像凭据但未用 {{ask:key}} 包裹(硬编码风险)" % k,
|
|
141
|
-
})
|
|
142
|
-
has_block = any(v["severity"] == "BLOCK" for v in violations)
|
|
143
|
-
return {"file": file_label, "n_cases": len(cases),
|
|
144
|
-
"n_violations": len(violations), "violations": violations,
|
|
145
|
-
"passed": not has_block}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def main():
|
|
149
|
-
args = sys.argv[1:]
|
|
150
|
-
if not args:
|
|
151
|
-
sys.stderr.write("用法: python lint_cases.py <cases.json> [--json] | -\n")
|
|
152
|
-
return 1
|
|
153
|
-
src = args[0]
|
|
154
|
-
as_json = "--json" in args
|
|
155
|
-
cases, label = _load_cases(src)
|
|
156
|
-
result = lint_cases(cases, label)
|
|
157
|
-
if as_json:
|
|
158
|
-
print(json.dumps(result, ensure_ascii=False))
|
|
159
|
-
else:
|
|
160
|
-
print("=== wl-test cases lint: %s ===" % label)
|
|
161
|
-
print("cases: %d | violations: %d | passed: %s" % (
|
|
162
|
-
result["n_cases"], result.get("n_violations", 0), result["passed"]))
|
|
163
|
-
for v in result["violations"][:10]:
|
|
164
|
-
print(" [%s] case#%d %s — %s" % (v["severity"], v["idx"], v["rule"], v["detail"][:70]))
|
|
165
|
-
# BLOCK → exit 2;全过/仅 WARN / 空 → exit 0
|
|
166
|
-
return 2 if not result["passed"] else 0
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if __name__ == "__main__":
|
|
170
|
-
sys.exit(main())
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""lint_cases.py - P0-3 w2: wl-test 用例格式 linter(9 铁律里文件内容可检的①②)。
|
|
4
|
+
|
|
5
|
+
治"AI 生成旧格式 cases(action+CSS selector)"→ Playwright 新管线只认 intent+anchor,
|
|
6
|
+
旧格式跑不通。被 /wl-test 调(执行前 lint)+ stop-eval 扫近期 cases 文件调。
|
|
7
|
+
|
|
8
|
+
铁律映射(wl-test.md 9 条,本 linter 检文件内容可判的):
|
|
9
|
+
① intent+anchor 格式,禁旧 action+CSS selector → BLOCK(硬拦)
|
|
10
|
+
② 占位符精确 {{ask:key}}(禁裸"测试账号"/硬编码凭据) → WARN(软提醒)
|
|
11
|
+
其余(③--cases-file ④recall ⑤URL斜杠 ⑥相对url ⑦⑧⑨执行流)非文件内容,命令/执行层管。
|
|
12
|
+
|
|
13
|
+
用法:
|
|
14
|
+
python lint_cases.py <cases.json> # lint 单文件
|
|
15
|
+
python lint_cases.py <cases.json> --json # 机器可读
|
|
16
|
+
python lint_cases.py - # 从 stdin 读 cases JSON
|
|
17
|
+
|
|
18
|
+
输出 stdout 末行 JSON: {file, n_cases, violations:[{rule, severity, idx, detail}], passed}
|
|
19
|
+
exit 0 = 过(含 WARN); exit 2 = 有 BLOCK 违规(旧格式); exit 0 = 无 cases/读不到(不拦)。
|
|
20
|
+
"""
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
if sys.platform == 'win32':
|
|
27
|
+
try:
|
|
28
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
29
|
+
except Exception:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
# 旧格式 CSS selector 特征(出现在 target/anchor 里 = 旧 action+CSS 范式)
|
|
33
|
+
_CSS_SELECTOR_RE = re.compile(
|
|
34
|
+
r'#[A-Za-z_][\w-]*' # #id(#submit-btn;不匹 #1 步号/颜色#fff 首字母要求)
|
|
35
|
+
r'|\.[A-Za-z_][\w-]*' # .class(要求点后字母,避 v1.2 版本号误伤)
|
|
36
|
+
r'|\w+\[[^\]]+\]' # tag[attr=...] / [attr]
|
|
37
|
+
r'|>\s*[A-Za-z][\w-]*' # 子代选择器 >
|
|
38
|
+
)
|
|
39
|
+
# 凭据类字段名({{ask:}} 应覆盖这些,禁硬编码)
|
|
40
|
+
_CRED_HINTS = ("账号", "密码", "account", "password", "passwd", "手机", "验证码", "邮箱", "token")
|
|
41
|
+
# 字面凭据(ascii 字母数字/@/./-,3-30 字符;排除中文描述/意图词)
|
|
42
|
+
_LITERAL_CRED_RE = re.compile(r'^[A-Za-z0-9_@.\-]{3,30}$')
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _load_cases(src):
|
|
46
|
+
"""src=文件路径 or '-'(stdin)。返 (cases_list, file_label)。读不到返 ([], label)。"""
|
|
47
|
+
if src == "-":
|
|
48
|
+
raw = sys.stdin.read()
|
|
49
|
+
label = "<stdin>"
|
|
50
|
+
else:
|
|
51
|
+
try:
|
|
52
|
+
with open(src, encoding="utf-8", errors="replace") as f:
|
|
53
|
+
raw = f.read()
|
|
54
|
+
label = os.path.basename(src)
|
|
55
|
+
except Exception:
|
|
56
|
+
return [], src
|
|
57
|
+
raw = raw.strip()
|
|
58
|
+
if not raw:
|
|
59
|
+
return [], label
|
|
60
|
+
try:
|
|
61
|
+
data = json.loads(raw)
|
|
62
|
+
except Exception:
|
|
63
|
+
return [], label
|
|
64
|
+
# cases 可能是 list,或 {cases:[...]} / {steps:[...]}
|
|
65
|
+
if isinstance(data, list):
|
|
66
|
+
return data, label
|
|
67
|
+
if isinstance(data, dict):
|
|
68
|
+
for k in ("cases", "steps", "items", "test_cases"):
|
|
69
|
+
v = data.get(k)
|
|
70
|
+
if isinstance(v, list):
|
|
71
|
+
return v, label
|
|
72
|
+
return [], label
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _is_block_old_format(case):
|
|
76
|
+
"""铁律①:case 是旧 action+CSS selector 范式 → True(BLOCK)。
|
|
77
|
+
判定:有 'action'+'target' 键(旧范式),或任一值含 CSS selector 特征。"""
|
|
78
|
+
if not isinstance(case, dict):
|
|
79
|
+
return False
|
|
80
|
+
has_action_target = "action" in case and "target" in case
|
|
81
|
+
# target/value/anchor 文本里含 CSS selector
|
|
82
|
+
for k in ("target", "selector", "css"):
|
|
83
|
+
v = case.get(k)
|
|
84
|
+
if isinstance(v, str) and _CSS_SELECTOR_RE.search(v):
|
|
85
|
+
return True
|
|
86
|
+
# anchor 是字符串(应是 {role,name} dict)且像 selector
|
|
87
|
+
anchor = case.get("anchor")
|
|
88
|
+
if isinstance(anchor, str) and _CSS_SELECTOR_RE.search(anchor):
|
|
89
|
+
return True
|
|
90
|
+
# 新格式判定:有 intent + anchor(dict) → 放过;否则若 action+target → 旧
|
|
91
|
+
if has_action_target and not (isinstance(anchor, dict) or "intent" in case):
|
|
92
|
+
return True
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _looks_like_literal_cred(v):
|
|
97
|
+
"""值像字面凭据(ascii 字母数字/@/./-,3-30 字符;含中文=多半描述→不算)。"""
|
|
98
|
+
v = v.strip()
|
|
99
|
+
if not v or v.startswith("{{"):
|
|
100
|
+
return False
|
|
101
|
+
if re.search(r'[一-龥]', v): # 含中文 → 描述,不当凭据字面
|
|
102
|
+
return False
|
|
103
|
+
return bool(_LITERAL_CRED_RE.match(v))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _cred_without_ask(case):
|
|
107
|
+
"""铁律②:凭据类【本字段】未用 {{ask:}} 包裹(硬编码)→ 返回违规 key 列表(WARN)。
|
|
108
|
+
高精度:只看本字段 key/name 是不是凭据词(不用 desc 全局触发,避误伤 intent/desc),
|
|
109
|
+
且值须像字面凭据(ascii、非中文描述、非占位)。"""
|
|
110
|
+
if not isinstance(case, dict):
|
|
111
|
+
return []
|
|
112
|
+
bad = []
|
|
113
|
+
for k, v in case.items():
|
|
114
|
+
if not isinstance(v, str) or not v:
|
|
115
|
+
continue
|
|
116
|
+
if "{{ask:" in v or v.startswith("{{"):
|
|
117
|
+
continue
|
|
118
|
+
field_name = str(case.get("name") or case.get("field") or k).lower()
|
|
119
|
+
if not any(h in field_name for h in _CRED_HINTS):
|
|
120
|
+
continue # 本字段名不像凭据 → 放过(不拿 desc 连坐)
|
|
121
|
+
if _looks_like_literal_cred(v):
|
|
122
|
+
bad.append(k)
|
|
123
|
+
return bad
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def lint_cases(cases, file_label="<cases>"):
|
|
127
|
+
"""跑 lint,返 {file, n_cases, violations, passed}。"""
|
|
128
|
+
violations = []
|
|
129
|
+
if not isinstance(cases, list):
|
|
130
|
+
return {"file": file_label, "n_cases": 0, "violations": [], "passed": True, "note": "非 list(空/读不到)→不拦"}
|
|
131
|
+
for i, case in enumerate(cases):
|
|
132
|
+
if _is_block_old_format(case):
|
|
133
|
+
violations.append({
|
|
134
|
+
"rule": "① intent+anchor 格式", "severity": "BLOCK", "idx": i,
|
|
135
|
+
"detail": "旧 action+CSS selector 范式(Playwright 新管线不认)→ 改 intent+anchor:{role,name}",
|
|
136
|
+
})
|
|
137
|
+
for k in _cred_without_ask(case):
|
|
138
|
+
violations.append({
|
|
139
|
+
"rule": "② {{ask:}} 占位", "severity": "WARN", "idx": i,
|
|
140
|
+
"detail": "字段 '%s' 像凭据但未用 {{ask:key}} 包裹(硬编码风险)" % k,
|
|
141
|
+
})
|
|
142
|
+
has_block = any(v["severity"] == "BLOCK" for v in violations)
|
|
143
|
+
return {"file": file_label, "n_cases": len(cases),
|
|
144
|
+
"n_violations": len(violations), "violations": violations,
|
|
145
|
+
"passed": not has_block}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def main():
|
|
149
|
+
args = sys.argv[1:]
|
|
150
|
+
if not args:
|
|
151
|
+
sys.stderr.write("用法: python lint_cases.py <cases.json> [--json] | -\n")
|
|
152
|
+
return 1
|
|
153
|
+
src = args[0]
|
|
154
|
+
as_json = "--json" in args
|
|
155
|
+
cases, label = _load_cases(src)
|
|
156
|
+
result = lint_cases(cases, label)
|
|
157
|
+
if as_json:
|
|
158
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
159
|
+
else:
|
|
160
|
+
print("=== wl-test cases lint: %s ===" % label)
|
|
161
|
+
print("cases: %d | violations: %d | passed: %s" % (
|
|
162
|
+
result["n_cases"], result.get("n_violations", 0), result["passed"]))
|
|
163
|
+
for v in result["violations"][:10]:
|
|
164
|
+
print(" [%s] case#%d %s — %s" % (v["severity"], v["idx"], v["rule"], v["detail"][:70]))
|
|
165
|
+
# BLOCK → exit 2;全过/仅 WARN / 空 → exit 0
|
|
166
|
+
return 2 if not result["passed"] else 0
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
sys.exit(main())
|
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
"""test_context_cache.py — T1.3 context_cache 模块单测(纯逻辑,无需云/MCP)。
|
|
2
|
-
跑法: python scripts/validation/test/test_context_cache.py
|
|
3
|
-
"""
|
|
4
|
-
import os
|
|
5
|
-
import sys
|
|
6
|
-
import time
|
|
7
|
-
import tempfile
|
|
8
|
-
|
|
9
|
-
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
10
|
-
_SCRIPTS = os.path.abspath(os.path.join(_HERE, "..", "..")) # scripts/
|
|
11
|
-
if _SCRIPTS not in sys.path and os.path.isdir(_SCRIPTS):
|
|
12
|
-
sys.path.insert(0, _SCRIPTS)
|
|
13
|
-
|
|
14
|
-
from foundation.io import context_cache as cc # noqa: E402
|
|
15
|
-
|
|
16
|
-
PASSED = 0
|
|
17
|
-
FAILED = 0
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def check(cond, msg):
|
|
21
|
-
global PASSED, FAILED
|
|
22
|
-
if cond:
|
|
23
|
-
PASSED += 1
|
|
24
|
-
print("OK " + msg)
|
|
25
|
-
else:
|
|
26
|
-
FAILED += 1
|
|
27
|
-
print("FAIL " + msg)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def main():
|
|
31
|
-
base = tempfile.mkdtemp(prefix="ctxcache_test_")
|
|
32
|
-
|
|
33
|
-
# 1. 初始 miss
|
|
34
|
-
check(cc.get("REQ-001", "车辆保养", base=base) is None, "初始 miss")
|
|
35
|
-
|
|
36
|
-
# 2. put + hit
|
|
37
|
-
cc.put("REQ-001", "车辆保养", "CONTEXT_TEXT_HERE", base=base)
|
|
38
|
-
check(cc.get("REQ-001", "车辆保养", base=base) == "CONTEXT_TEXT_HERE", "put 后 hit")
|
|
39
|
-
|
|
40
|
-
# 3. query 归一化(首尾空格 + 大小写)命中同一桶
|
|
41
|
-
check(cc.get("REQ-001", " 车辆保养 ", base=base) == "CONTEXT_TEXT_HERE", "query trim 命中")
|
|
42
|
-
check(cc.get("REQ-001", "AbC", base=base) is None or True, "归一化不影响他词") # 占位,下条真测
|
|
43
|
-
cc.put("REQ-001", "AbC", "UP", base=base)
|
|
44
|
-
check(cc.get("REQ-001", "abc", base=base) == "UP", "query 大小写归一化命中")
|
|
45
|
-
|
|
46
|
-
# 4. 不同 query / 不同 REQ 隔离
|
|
47
|
-
check(cc.get("REQ-001", "考勤", base=base) is None, "他 query miss")
|
|
48
|
-
check(cc.get("REQ-002", "车辆保养", base=base) is None, "他 REQ 隔离 miss")
|
|
49
|
-
|
|
50
|
-
# 5. invalidate 清整个 REQ
|
|
51
|
-
cc.invalidate("REQ-001", base=base)
|
|
52
|
-
check(cc.get("REQ-001", "车辆保养", base=base) is None, "invalidate 后 miss")
|
|
53
|
-
check(cc.get("REQ-001", "abc", base=base) is None, "invalidate 清全 query")
|
|
54
|
-
|
|
55
|
-
# 6. TTL 过期
|
|
56
|
-
cc.put("REQ-003", "x", "OLD", base=base)
|
|
57
|
-
check(cc.get("REQ-003", "x", ttl=0.1, base=base) == "OLD", "TTL 内 hit")
|
|
58
|
-
time.sleep(0.25)
|
|
59
|
-
check(cc.get("REQ-003", "x", ttl=0.1, base=base) is None, "TTL 过期后 miss")
|
|
60
|
-
|
|
61
|
-
# 7. 路径消毒(防 REQ id 注入 ../)
|
|
62
|
-
p = cc.cache_path("REQ-evil/../../etc", base=base)
|
|
63
|
-
rel = os.path.relpath(p, base)
|
|
64
|
-
check(".." not in rel and "etc" not in rel.split(os.sep), "REQ id 路径消毒(无穿越)")
|
|
65
|
-
|
|
66
|
-
# 8. stats
|
|
67
|
-
cc.put("REQ-004", "a", "x", base=base)
|
|
68
|
-
cc.put("REQ-004", "b", "y", base=base)
|
|
69
|
-
s = cc.stats("REQ-004", base=base)
|
|
70
|
-
check(s["entries"] == 2, "stats 条数=2")
|
|
71
|
-
check(set(s["queries"]) == {"a", "b"}, "stats query 摘要对")
|
|
72
|
-
|
|
73
|
-
print("\n%s passed, %s failed" % (PASSED, FAILED))
|
|
74
|
-
return 0 if FAILED == 0 else 1
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if __name__ == "__main__":
|
|
78
|
-
sys.exit(main())
|
|
1
|
+
"""test_context_cache.py — T1.3 context_cache 模块单测(纯逻辑,无需云/MCP)。
|
|
2
|
+
跑法: python scripts/validation/test/test_context_cache.py
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import tempfile
|
|
8
|
+
|
|
9
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
10
|
+
_SCRIPTS = os.path.abspath(os.path.join(_HERE, "..", "..")) # scripts/
|
|
11
|
+
if _SCRIPTS not in sys.path and os.path.isdir(_SCRIPTS):
|
|
12
|
+
sys.path.insert(0, _SCRIPTS)
|
|
13
|
+
|
|
14
|
+
from foundation.io import context_cache as cc # noqa: E402
|
|
15
|
+
|
|
16
|
+
PASSED = 0
|
|
17
|
+
FAILED = 0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def check(cond, msg):
|
|
21
|
+
global PASSED, FAILED
|
|
22
|
+
if cond:
|
|
23
|
+
PASSED += 1
|
|
24
|
+
print("OK " + msg)
|
|
25
|
+
else:
|
|
26
|
+
FAILED += 1
|
|
27
|
+
print("FAIL " + msg)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main():
|
|
31
|
+
base = tempfile.mkdtemp(prefix="ctxcache_test_")
|
|
32
|
+
|
|
33
|
+
# 1. 初始 miss
|
|
34
|
+
check(cc.get("REQ-001", "车辆保养", base=base) is None, "初始 miss")
|
|
35
|
+
|
|
36
|
+
# 2. put + hit
|
|
37
|
+
cc.put("REQ-001", "车辆保养", "CONTEXT_TEXT_HERE", base=base)
|
|
38
|
+
check(cc.get("REQ-001", "车辆保养", base=base) == "CONTEXT_TEXT_HERE", "put 后 hit")
|
|
39
|
+
|
|
40
|
+
# 3. query 归一化(首尾空格 + 大小写)命中同一桶
|
|
41
|
+
check(cc.get("REQ-001", " 车辆保养 ", base=base) == "CONTEXT_TEXT_HERE", "query trim 命中")
|
|
42
|
+
check(cc.get("REQ-001", "AbC", base=base) is None or True, "归一化不影响他词") # 占位,下条真测
|
|
43
|
+
cc.put("REQ-001", "AbC", "UP", base=base)
|
|
44
|
+
check(cc.get("REQ-001", "abc", base=base) == "UP", "query 大小写归一化命中")
|
|
45
|
+
|
|
46
|
+
# 4. 不同 query / 不同 REQ 隔离
|
|
47
|
+
check(cc.get("REQ-001", "考勤", base=base) is None, "他 query miss")
|
|
48
|
+
check(cc.get("REQ-002", "车辆保养", base=base) is None, "他 REQ 隔离 miss")
|
|
49
|
+
|
|
50
|
+
# 5. invalidate 清整个 REQ
|
|
51
|
+
cc.invalidate("REQ-001", base=base)
|
|
52
|
+
check(cc.get("REQ-001", "车辆保养", base=base) is None, "invalidate 后 miss")
|
|
53
|
+
check(cc.get("REQ-001", "abc", base=base) is None, "invalidate 清全 query")
|
|
54
|
+
|
|
55
|
+
# 6. TTL 过期
|
|
56
|
+
cc.put("REQ-003", "x", "OLD", base=base)
|
|
57
|
+
check(cc.get("REQ-003", "x", ttl=0.1, base=base) == "OLD", "TTL 内 hit")
|
|
58
|
+
time.sleep(0.25)
|
|
59
|
+
check(cc.get("REQ-003", "x", ttl=0.1, base=base) is None, "TTL 过期后 miss")
|
|
60
|
+
|
|
61
|
+
# 7. 路径消毒(防 REQ id 注入 ../)
|
|
62
|
+
p = cc.cache_path("REQ-evil/../../etc", base=base)
|
|
63
|
+
rel = os.path.relpath(p, base)
|
|
64
|
+
check(".." not in rel and "etc" not in rel.split(os.sep), "REQ id 路径消毒(无穿越)")
|
|
65
|
+
|
|
66
|
+
# 8. stats
|
|
67
|
+
cc.put("REQ-004", "a", "x", base=base)
|
|
68
|
+
cc.put("REQ-004", "b", "y", base=base)
|
|
69
|
+
s = cc.stats("REQ-004", base=base)
|
|
70
|
+
check(s["entries"] == 2, "stats 条数=2")
|
|
71
|
+
check(set(s["queries"]) == {"a", "b"}, "stats query 摘要对")
|
|
72
|
+
|
|
73
|
+
print("\n%s passed, %s failed" % (PASSED, FAILED))
|
|
74
|
+
return 0 if FAILED == 0 else 1
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
sys.exit(main())
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
"""test_lint_cases.py — T1.6 w2 lint_cases 单测(good/旧格式BLOCK/凭据WARN)。"""
|
|
2
|
-
import os, sys
|
|
3
|
-
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
4
|
-
_SCRIPTS = os.path.abspath(os.path.join(_HERE, "..", "..")) # scripts/
|
|
5
|
-
_METRICS = os.path.join(_SCRIPTS, "validation", "metrics")
|
|
6
|
-
if _METRICS not in sys.path:
|
|
7
|
-
sys.path.insert(0, _METRICS)
|
|
8
|
-
|
|
9
|
-
import lint_cases as L # noqa
|
|
10
|
-
|
|
11
|
-
P = F = 0
|
|
12
|
-
def check(cond, msg):
|
|
13
|
-
global P, F
|
|
14
|
-
if cond: P += 1; print("OK ", msg)
|
|
15
|
-
else: F += 1; print("FAIL", msg)
|
|
16
|
-
|
|
17
|
-
# ① good: intent+anchor 新格式 → 过
|
|
18
|
-
good = [{"intent":"fill","desc":"账号框","value":"{{ask:登录账号}}","anchor":{"role":"textbox","name":"管理员"}}]
|
|
19
|
-
r = L.lint_cases(good, "good")
|
|
20
|
-
check(r["passed"] is True and r["n_cases"] == 1, "good 新格式 intent+anchor 过")
|
|
21
|
-
check(len(r["violations"]) == 0, "good 无违规")
|
|
22
|
-
|
|
23
|
-
# ② bad: 旧 action+CSS selector → BLOCK
|
|
24
|
-
bad = [{"action":"fill","target":"input[placeholder*='账号']","value":"admin"}]
|
|
25
|
-
r = L.lint_cases(bad, "bad")
|
|
26
|
-
check(r["passed"] is False, "旧 action+CSS BLOCK")
|
|
27
|
-
check(any(v["severity"]=="BLOCK" for v in r["violations"]), "旧格式产 BLOCK 违规")
|
|
28
|
-
|
|
29
|
-
# ③ CSS selector 在 target 里(即使有 intent)→ BLOCK
|
|
30
|
-
css = [{"intent":"click","anchor":{"role":"button"},"target":"#submit-btn"}]
|
|
31
|
-
r = L.lint_cases(css, "css")
|
|
32
|
-
check(r["passed"] is False, "CSS selector(#id) BLOCK")
|
|
33
|
-
|
|
34
|
-
# ④ 凭据字段硬编码未 {{ask:}} → WARN(不 BLOCK)
|
|
35
|
-
cred = [{"intent":"fill","desc":"账号","anchor":{"role":"textbox"},"账号":"admin123"}]
|
|
36
|
-
r = L.lint_cases(cred, "cred")
|
|
37
|
-
check(r["passed"] is True, "凭据 WARN 不 BLOCK(仍过)")
|
|
38
|
-
check(any(v["severity"]=="WARN" for v in r["violations"]), "硬编码凭据产 WARN")
|
|
39
|
-
|
|
40
|
-
# ⑤ 凭据字段用 {{ask:}} → 无 WARN
|
|
41
|
-
cred_ok = [{"intent":"fill","desc":"账号","anchor":{"role":"textbox"},"账号":"{{ask:登录账号}}"}]
|
|
42
|
-
r = L.lint_cases(cred_ok, "cred_ok")
|
|
43
|
-
check(len(r["violations"]) == 0, "{{ask:}} 包裹无违规")
|
|
44
|
-
|
|
45
|
-
# ⑥ 空/非 list → 过(不拦)
|
|
46
|
-
r = L.lint_cases([], "empty")
|
|
47
|
-
check(r["passed"] is True and r["n_cases"]==0, "空 cases 过(不拦)")
|
|
48
|
-
|
|
49
|
-
# ⑦ 包在 {cases:[...]} 里也能解
|
|
50
|
-
import json
|
|
51
|
-
# _load_cases 走文件/stdin; 直接验 lint_cases 接 list 已覆盖。验 _load_cases 的 dict 解包:
|
|
52
|
-
class _Fake:
|
|
53
|
-
pass
|
|
54
|
-
# 模拟 dict 解包: lint_cases 接收 list, 但 _load_cases 应从 {cases:[...]} 取出 list
|
|
55
|
-
# 这里直接测 lint_cases 不依赖文件 IO
|
|
56
|
-
r = L.lint_cases([{"intent":"x","anchor":{"role":"button"}}], "dict-wrap-sim")
|
|
57
|
-
check(r["passed"] is True, "单条新格式过")
|
|
58
|
-
|
|
59
|
-
print("\n%d passed, %d failed" % (P, F))
|
|
60
|
-
sys.exit(0 if F == 0 else 1)
|
|
1
|
+
"""test_lint_cases.py — T1.6 w2 lint_cases 单测(good/旧格式BLOCK/凭据WARN)。"""
|
|
2
|
+
import os, sys
|
|
3
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
4
|
+
_SCRIPTS = os.path.abspath(os.path.join(_HERE, "..", "..")) # scripts/
|
|
5
|
+
_METRICS = os.path.join(_SCRIPTS, "validation", "metrics")
|
|
6
|
+
if _METRICS not in sys.path:
|
|
7
|
+
sys.path.insert(0, _METRICS)
|
|
8
|
+
|
|
9
|
+
import lint_cases as L # noqa
|
|
10
|
+
|
|
11
|
+
P = F = 0
|
|
12
|
+
def check(cond, msg):
|
|
13
|
+
global P, F
|
|
14
|
+
if cond: P += 1; print("OK ", msg)
|
|
15
|
+
else: F += 1; print("FAIL", msg)
|
|
16
|
+
|
|
17
|
+
# ① good: intent+anchor 新格式 → 过
|
|
18
|
+
good = [{"intent":"fill","desc":"账号框","value":"{{ask:登录账号}}","anchor":{"role":"textbox","name":"管理员"}}]
|
|
19
|
+
r = L.lint_cases(good, "good")
|
|
20
|
+
check(r["passed"] is True and r["n_cases"] == 1, "good 新格式 intent+anchor 过")
|
|
21
|
+
check(len(r["violations"]) == 0, "good 无违规")
|
|
22
|
+
|
|
23
|
+
# ② bad: 旧 action+CSS selector → BLOCK
|
|
24
|
+
bad = [{"action":"fill","target":"input[placeholder*='账号']","value":"admin"}]
|
|
25
|
+
r = L.lint_cases(bad, "bad")
|
|
26
|
+
check(r["passed"] is False, "旧 action+CSS BLOCK")
|
|
27
|
+
check(any(v["severity"]=="BLOCK" for v in r["violations"]), "旧格式产 BLOCK 违规")
|
|
28
|
+
|
|
29
|
+
# ③ CSS selector 在 target 里(即使有 intent)→ BLOCK
|
|
30
|
+
css = [{"intent":"click","anchor":{"role":"button"},"target":"#submit-btn"}]
|
|
31
|
+
r = L.lint_cases(css, "css")
|
|
32
|
+
check(r["passed"] is False, "CSS selector(#id) BLOCK")
|
|
33
|
+
|
|
34
|
+
# ④ 凭据字段硬编码未 {{ask:}} → WARN(不 BLOCK)
|
|
35
|
+
cred = [{"intent":"fill","desc":"账号","anchor":{"role":"textbox"},"账号":"admin123"}]
|
|
36
|
+
r = L.lint_cases(cred, "cred")
|
|
37
|
+
check(r["passed"] is True, "凭据 WARN 不 BLOCK(仍过)")
|
|
38
|
+
check(any(v["severity"]=="WARN" for v in r["violations"]), "硬编码凭据产 WARN")
|
|
39
|
+
|
|
40
|
+
# ⑤ 凭据字段用 {{ask:}} → 无 WARN
|
|
41
|
+
cred_ok = [{"intent":"fill","desc":"账号","anchor":{"role":"textbox"},"账号":"{{ask:登录账号}}"}]
|
|
42
|
+
r = L.lint_cases(cred_ok, "cred_ok")
|
|
43
|
+
check(len(r["violations"]) == 0, "{{ask:}} 包裹无违规")
|
|
44
|
+
|
|
45
|
+
# ⑥ 空/非 list → 过(不拦)
|
|
46
|
+
r = L.lint_cases([], "empty")
|
|
47
|
+
check(r["passed"] is True and r["n_cases"]==0, "空 cases 过(不拦)")
|
|
48
|
+
|
|
49
|
+
# ⑦ 包在 {cases:[...]} 里也能解
|
|
50
|
+
import json
|
|
51
|
+
# _load_cases 走文件/stdin; 直接验 lint_cases 接 list 已覆盖。验 _load_cases 的 dict 解包:
|
|
52
|
+
class _Fake:
|
|
53
|
+
pass
|
|
54
|
+
# 模拟 dict 解包: lint_cases 接收 list, 但 _load_cases 应从 {cases:[...]} 取出 list
|
|
55
|
+
# 这里直接测 lint_cases 不依赖文件 IO
|
|
56
|
+
r = L.lint_cases([{"intent":"x","anchor":{"role":"button"}}], "dict-wrap-sim")
|
|
57
|
+
check(r["passed"] is True, "单条新格式过")
|
|
58
|
+
|
|
59
|
+
print("\n%d passed, %d failed" % (P, F))
|
|
60
|
+
sys.exit(0 if F == 0 else 1)
|