@harmonyos-arkts/d2h 0.1.11 → 0.1.12
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/.claude-plugin/marketplace.json +15 -13
- package/.claude-plugin/plugin.json +13 -7
- package/AGENTS.md +5 -5
- package/README.md +212 -212
- package/agents/android-to-hmos-00-orchestrator.md +318 -318
- package/agents/d2h.md +157 -157
- package/dist/bin/install-opencode.js +30 -30
- package/docs/environment-check.md +86 -86
- package/install-opencode.sh +160 -160
- package/opencode/agents.json +16 -16
- package/opencode/vscode-d2h-package.json +7 -7
- package/opencode/vscode-d2h-plugin.mjs +29 -29
- package/package.json +54 -54
- package/schemas/android-source-manifest.schema.json +25 -25
- package/schemas/checkpoint-provenance.schema.json +67 -67
- package/schemas/final-acceptance.schema.json +29 -29
- package/schemas/managed-evidence-index.schema.json +15 -15
- package/schemas/managed-evidence.schema.json +114 -114
- package/schemas/migration-config.schema.json +51 -51
- package/schemas/migration-report-index.schema.json +39 -39
- package/schemas/migration-report-item.schema.json +51 -51
- package/schemas/migration-report-summary.schema.json +25 -25
- package/schemas/migration-status.schema.json +202 -202
- package/schemas/preflight.schema.json +60 -60
- package/schemas/source-order-audit.schema.json +16 -16
- package/schemas/source-provenance-event.schema.json +19 -19
- package/schemas/spec-app-shard.schema.json +45 -45
- package/schemas/spec-fact-corrections-shard.schema.json +13 -13
- package/schemas/spec-features-shard.schema.json +43 -43
- package/schemas/spec-index.schema.json +26 -26
- package/schemas/spec-interactions-shard.schema.json +40 -40
- package/schemas/spec-page.schema.json +98 -98
- package/schemas/spec-pages-index.schema.json +1 -1
- package/schemas/spec-unresolved-shard.schema.json +1 -1
- package/schemas/task-envelope.schema.json +109 -109
- package/schemas/task-plan.schema.json +341 -341
- package/scripts/check-environment.mjs +320 -320
- package/skills/android2hmos_resources_convert/SKILL.md +180 -180
- package/skills/android2hmos_resources_convert/references/image-conversion-rules.md +230 -230
- package/skills/android2hmos_resources_convert/references/svg-fix-patterns.md +175 -175
- package/skills/android2hmos_resources_convert/references/xml-drawable-to-svg-rules.md +513 -513
- package/skills/arkts-app-identity/SKILL.md +238 -238
- package/skills/arkts-i18n/SKILL.md +496 -496
- package/skills/arkts-i18n/evals/evals.json +84 -84
- package/skills/arkts-i18n/references/code-examples.md +302 -302
- package/skills/arkts-i18n/references/common-pitfalls.md +391 -391
- package/skills/arkts-i18n/references/dynamic-language-switch.md +604 -604
- package/skills/arkts-i18n/references/hardcoded-string-scanner.md +348 -348
- package/skills/arkts-i18n/references/language-codes.md +104 -104
- package/skills/arkts-i18n/references/resource-file-structure.md +775 -775
- package/skills/arkts-i18n/references/static-vs-dynamic.md +242 -242
- package/skills/arkts-i18n/references/v1-compat.md +244 -244
- package/skills/arkts-i18n/scripts/audit_i18n_completeness.sh +174 -174
- package/skills/arkts-icon-sizing/SKILL.md +211 -211
- package/skills/arkts-icon-sizing/scripts/icon_audit.py +131 -131
- package/skills/arkts-icon-sizing/scripts/icon_autofix.py +88 -88
- package/skills/arkts-icon-sizing/scripts/icon_dims.py +179 -179
- package/skills/arkts-icon-sizing/scripts/icon_fix.py +119 -119
- package/skills/arkts-mvvm-architecture/SKILL.md +613 -613
- package/skills/d2h-hypium-forge/SKILL.md +309 -309
- package/skills/d2h-hypium-forge/references/functional-spec-template.md +140 -140
- package/skills/d2h-hypium-forge/references/official-api.md +135 -135
- package/skills/d2h-hypium-forge/references/pitfalls.md +98 -98
- package/skills/d2h-hypium-forge/references/testability-template.md +71 -71
- package/skills/d2h-hypium-forge/scripts/_common_template.py +659 -659
- package/skills/d2h-hypium-forge/scripts/analyze_results.py +165 -165
- package/skills/d2h-hypium-forge/scripts/assert_audit.py +107 -107
- package/skills/d2h-hypium-forge/scripts/collect_runtime_pages.py +169 -169
- package/skills/d2h-hypium-forge/scripts/forge_report.py +325 -325
- package/skills/d2h-hypium-forge/scripts/inject_resource_ids.py +114 -114
- package/skills/d2h-hypium-forge/scripts/lint_asserts.py +80 -80
- package/skills/d2h-hypium-forge/scripts/lint_locate.py +60 -60
- package/skills/d2h-hypium-forge/scripts/merge_appgraph.py +249 -249
- package/skills/d2h-hypium-forge/scripts/precondition.py +140 -140
- package/skills/d2h-hypium-forge/scripts/preinject_ids.py +232 -232
- package/skills/d2h-hypium-forge/scripts/probe.py +197 -197
- package/skills/d2h-hypium-forge/scripts/run_direct.py +823 -823
- package/skills/d2h-hypium-forge/scripts/run_parallel.py +121 -121
- package/skills/d2h-hypium-forge/scripts/scan_features.py +255 -255
- package/skills/d2h-hypium-forge/scripts/upgrade_asserts.py +66 -66
- package/skills/d2h-hypium-forge/scripts/validate_scenarios.py +84 -84
- package/skills/d2h-page-develop/SKILL.md +112 -112
- package/skills/d2h-page-develop/agents/openai.yaml +7 -7
- package/skills/d2h-page-spec-generate/SKILL.md +206 -206
- package/skills/d2h-page-spec-generate/scripts/build_batch_report.py +227 -227
- package/skills/d2h-utg-ai-generate/SKILL.md +128 -128
- package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +76 -76
- package/skills/d2h-utg-ai-generate/references/extraction-guide.md +34 -34
- package/skills/d2h-utg-ai-generate/references/facts-contract.md +96 -96
- package/skills/d2h-utg-ai-generate/references/page-schema.json +61 -61
- package/skills/d2h-utg-ai-generate/references/utg-schema.json +89 -89
- package/skills/d2h-utg-ai-generate/scripts/build_page_json.py +314 -314
- package/skills/d2h-utg-ai-generate/scripts/build_utg.py +123 -123
- package/skills/d2h-utg-ai-generate/scripts/capture_from_page_json.ts +280 -280
- package/skills/d2h-utg-ai-generate/scripts/utg_common.py +524 -524
- package/skills/d2h-utg-ai-generate/scripts/utg_html.py +489 -489
- package/skills/d2h-utg-ai-generate/scripts/validate_utg.py +69 -69
- package/skills/d2h-utg-ai-generate/scripts/vendor/D3-LICENSE +13 -13
- package/skills/d2h-utg-ai-generate/scripts/vendor/d3-7.9.0.min.js +2 -2
- package/skills/d2h-utg-ai-generate/tests/test_pipeline.py +369 -369
- package/skills/d2h-utg-static-generate/SKILL.md +73 -73
- package/skills/d2h-utg-static-generate/references/discoveries.md +38 -38
- package/skills/d2h-utg-static-generate/scripts/generate.py +499 -499
- package/skills/d2h-utg-static-generate/tests/test_static.py +335 -335
- package/skills/d2h-utg-static-generate/tests/verify-offline-html.py +60 -60
- package/skills/d2h-utg-static-generate/tests/verify-produced-fixture.py +131 -131
- package/skills/harmonyos-migration-playbook/SKILL.md +83 -83
- package/skills/harmonyos-migration-playbook/agents/openai.yaml +7 -7
- package/skills/harmonyos-migration-playbook/references/arkts-compile.md +24 -24
- package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +53 -53
- package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +45 -45
- package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +23 -23
- package/skills/harmonyos-migration-playbook/references/ui-automation.md +53 -53
- package/skills/harmonyos-migration-playbook/references/windows-environment.md +38 -38
- package/skills/maintaining-migration-report/SKILL.md +144 -144
- package/skills/maintaining-migration-report/references/final-acceptance.md +48 -48
- package/skills/maintaining-migration-report/references/source-provenance.md +66 -66
- package/skills/managing-d2h-toolchain/SKILL.md +24 -24
- package/skills/managing-d2h-toolchain/environment-requirements.json +16 -16
- package/skills/managing-d2h-toolchain/scripts/manage-toolchain.mjs +98 -98
- package/skills/managing-d2h-toolchain/toolchain.json +16 -16
- package/skills/native-library-substitution/SKILL.md +384 -384
- package/skills/native-library-substitution/references/native-library-substitution.json +56905 -56905
- package/skills/native-library-substitution/references/native-library-substitution.md +163 -163
- package/skills/preparing-migration-workspace/SKILL.md +131 -131
- package/skills/reviewing-migration-process/SKILL.md +62 -62
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
inject_resource_ids.py — 资源化文案工程的 ID 注入器(DFT-010 场景)。
|
|
4
|
-
|
|
5
|
-
流程:target 文本 → string.json 资源名 → 全工程搜 $r('app.string.X') 引用点
|
|
6
|
-
→ 组件属性链内注入 .id('tc_<file>_<hash>')(幂等,已有 tc_ id 复用)
|
|
7
|
-
|
|
8
|
-
输出 injected_ids.json(target → id 映射,供脚本生成器消费)
|
|
9
|
-
用法: inject_resource_ids.py <harmony_root> <targets_json文件或逗号分隔> [--cases cases.json]
|
|
10
|
-
"""
|
|
11
|
-
import argparse
|
|
12
|
-
import json
|
|
13
|
-
import os
|
|
14
|
-
import re
|
|
15
|
-
import hashlib
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def build_res_map(root):
|
|
19
|
-
res_map = {}
|
|
20
|
-
for dp, _d, files in os.walk(root):
|
|
21
|
-
if 'oh_modules' in dp or '/build' in dp or '.testdesign' in dp or '.hypium_forge' in dp:
|
|
22
|
-
continue
|
|
23
|
-
for f in files:
|
|
24
|
-
if f == 'string.json':
|
|
25
|
-
try:
|
|
26
|
-
for item in json.load(open(os.path.join(dp, f), encoding='utf-8')).get('string', []):
|
|
27
|
-
res_map.setdefault(item['value'], item['name'])
|
|
28
|
-
except Exception:
|
|
29
|
-
pass
|
|
30
|
-
return res_map
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def collect_src(root):
|
|
34
|
-
out = []
|
|
35
|
-
for dp, _d, files in os.walk(root):
|
|
36
|
-
if 'oh_modules' in dp or '/build' in dp or '.testdesign' in dp or '.hypium_forge' in dp:
|
|
37
|
-
continue
|
|
38
|
-
for f in files:
|
|
39
|
-
if f.endswith('.ets'):
|
|
40
|
-
out.append(os.path.join(dp, f))
|
|
41
|
-
return out
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
ID_RE = re.compile(r"\.id\('tc_[a-z0-9_]+'\)")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def inject_in_file(sf, rx):
|
|
48
|
-
"""在 sf 中找 rx 引用行,属性链内注入 id;返回 (tid or None)。幂等。"""
|
|
49
|
-
src = open(sf, encoding='utf-8', errors='ignore').read()
|
|
50
|
-
lines = src.split('\n')
|
|
51
|
-
for i, ln in enumerate(lines):
|
|
52
|
-
if rx.search(ln):
|
|
53
|
-
seg = '\n'.join(lines[i:i + 12])
|
|
54
|
-
m = ID_RE.search(seg)
|
|
55
|
-
if m:
|
|
56
|
-
return m.group(0)[5:-2] # 已有 → 复用
|
|
57
|
-
for k in range(i, min(len(lines), i + 10)):
|
|
58
|
-
if re.match(r'^\s*\.[a-zA-Z]', lines[k]):
|
|
59
|
-
indent = re.match(r'(\s*)', lines[k]).group(1)
|
|
60
|
-
tid = 'tc_' + os.path.splitext(os.path.basename(sf))[0].lower() + '_' + \
|
|
61
|
-
hashlib.md5(ln.encode()).hexdigest()[:6]
|
|
62
|
-
lines.insert(k, f"{indent}.id('{tid}')")
|
|
63
|
-
open(sf, 'w', encoding='utf-8').write('\n'.join(lines))
|
|
64
|
-
return tid
|
|
65
|
-
return None
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def main():
|
|
69
|
-
ap = argparse.ArgumentParser()
|
|
70
|
-
ap.add_argument('root')
|
|
71
|
-
ap.add_argument('--cases', help='forge 格式 cases.json(取 target)')
|
|
72
|
-
ap.add_argument('--targets', help='逗号分隔 target 列表(二选一)')
|
|
73
|
-
ap.add_argument('--out', default=None)
|
|
74
|
-
args = ap.parse_args()
|
|
75
|
-
|
|
76
|
-
targets = []
|
|
77
|
-
if args.cases:
|
|
78
|
-
for c in json.load(open(args.cases, encoding='utf-8')):
|
|
79
|
-
for s in c.get('steps', []):
|
|
80
|
-
t = s.get('target')
|
|
81
|
-
if t and not t.startswith('请输入') and t != '数量':
|
|
82
|
-
targets.append(t)
|
|
83
|
-
else:
|
|
84
|
-
targets = args.targets.split(',')
|
|
85
|
-
targets = list(dict.fromkeys(targets))
|
|
86
|
-
|
|
87
|
-
res_map = build_res_map(args.root)
|
|
88
|
-
src_files = collect_src(args.root)
|
|
89
|
-
|
|
90
|
-
injected, unresolved = {}, []
|
|
91
|
-
for t in targets:
|
|
92
|
-
resname = res_map.get(t)
|
|
93
|
-
tid = None
|
|
94
|
-
if resname:
|
|
95
|
-
rx = re.compile(r"\$r\(\s*['\"]app\.string\." + re.escape(resname) + r"['\"]")
|
|
96
|
-
for sf in src_files:
|
|
97
|
-
tid = inject_in_file(sf, rx)
|
|
98
|
-
if tid:
|
|
99
|
-
injected[t] = tid
|
|
100
|
-
break
|
|
101
|
-
if not tid:
|
|
102
|
-
unresolved.append(t)
|
|
103
|
-
|
|
104
|
-
out = args.out or os.path.join(args.root, '.hypium_forge', 'injected_ids.json')
|
|
105
|
-
json.dump(injected, open(out, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
|
|
106
|
-
print(f'资源命中注入: {len(injected)} / 未解析: {len(unresolved)} → {out}')
|
|
107
|
-
for t, tid in sorted(injected.items()):
|
|
108
|
-
print(f' + {t} → {tid}')
|
|
109
|
-
if unresolved:
|
|
110
|
-
print('未解析(动态文案/图标按钮,脚本走文本/兜底定位):', unresolved[:15])
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if __name__ == '__main__':
|
|
114
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
inject_resource_ids.py — 资源化文案工程的 ID 注入器(DFT-010 场景)。
|
|
4
|
+
|
|
5
|
+
流程:target 文本 → string.json 资源名 → 全工程搜 $r('app.string.X') 引用点
|
|
6
|
+
→ 组件属性链内注入 .id('tc_<file>_<hash>')(幂等,已有 tc_ id 复用)
|
|
7
|
+
|
|
8
|
+
输出 injected_ids.json(target → id 映射,供脚本生成器消费)
|
|
9
|
+
用法: inject_resource_ids.py <harmony_root> <targets_json文件或逗号分隔> [--cases cases.json]
|
|
10
|
+
"""
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import hashlib
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_res_map(root):
|
|
19
|
+
res_map = {}
|
|
20
|
+
for dp, _d, files in os.walk(root):
|
|
21
|
+
if 'oh_modules' in dp or '/build' in dp or '.testdesign' in dp or '.hypium_forge' in dp:
|
|
22
|
+
continue
|
|
23
|
+
for f in files:
|
|
24
|
+
if f == 'string.json':
|
|
25
|
+
try:
|
|
26
|
+
for item in json.load(open(os.path.join(dp, f), encoding='utf-8')).get('string', []):
|
|
27
|
+
res_map.setdefault(item['value'], item['name'])
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
return res_map
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def collect_src(root):
|
|
34
|
+
out = []
|
|
35
|
+
for dp, _d, files in os.walk(root):
|
|
36
|
+
if 'oh_modules' in dp or '/build' in dp or '.testdesign' in dp or '.hypium_forge' in dp:
|
|
37
|
+
continue
|
|
38
|
+
for f in files:
|
|
39
|
+
if f.endswith('.ets'):
|
|
40
|
+
out.append(os.path.join(dp, f))
|
|
41
|
+
return out
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
ID_RE = re.compile(r"\.id\('tc_[a-z0-9_]+'\)")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def inject_in_file(sf, rx):
|
|
48
|
+
"""在 sf 中找 rx 引用行,属性链内注入 id;返回 (tid or None)。幂等。"""
|
|
49
|
+
src = open(sf, encoding='utf-8', errors='ignore').read()
|
|
50
|
+
lines = src.split('\n')
|
|
51
|
+
for i, ln in enumerate(lines):
|
|
52
|
+
if rx.search(ln):
|
|
53
|
+
seg = '\n'.join(lines[i:i + 12])
|
|
54
|
+
m = ID_RE.search(seg)
|
|
55
|
+
if m:
|
|
56
|
+
return m.group(0)[5:-2] # 已有 → 复用
|
|
57
|
+
for k in range(i, min(len(lines), i + 10)):
|
|
58
|
+
if re.match(r'^\s*\.[a-zA-Z]', lines[k]):
|
|
59
|
+
indent = re.match(r'(\s*)', lines[k]).group(1)
|
|
60
|
+
tid = 'tc_' + os.path.splitext(os.path.basename(sf))[0].lower() + '_' + \
|
|
61
|
+
hashlib.md5(ln.encode()).hexdigest()[:6]
|
|
62
|
+
lines.insert(k, f"{indent}.id('{tid}')")
|
|
63
|
+
open(sf, 'w', encoding='utf-8').write('\n'.join(lines))
|
|
64
|
+
return tid
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def main():
|
|
69
|
+
ap = argparse.ArgumentParser()
|
|
70
|
+
ap.add_argument('root')
|
|
71
|
+
ap.add_argument('--cases', help='forge 格式 cases.json(取 target)')
|
|
72
|
+
ap.add_argument('--targets', help='逗号分隔 target 列表(二选一)')
|
|
73
|
+
ap.add_argument('--out', default=None)
|
|
74
|
+
args = ap.parse_args()
|
|
75
|
+
|
|
76
|
+
targets = []
|
|
77
|
+
if args.cases:
|
|
78
|
+
for c in json.load(open(args.cases, encoding='utf-8')):
|
|
79
|
+
for s in c.get('steps', []):
|
|
80
|
+
t = s.get('target')
|
|
81
|
+
if t and not t.startswith('请输入') and t != '数量':
|
|
82
|
+
targets.append(t)
|
|
83
|
+
else:
|
|
84
|
+
targets = args.targets.split(',')
|
|
85
|
+
targets = list(dict.fromkeys(targets))
|
|
86
|
+
|
|
87
|
+
res_map = build_res_map(args.root)
|
|
88
|
+
src_files = collect_src(args.root)
|
|
89
|
+
|
|
90
|
+
injected, unresolved = {}, []
|
|
91
|
+
for t in targets:
|
|
92
|
+
resname = res_map.get(t)
|
|
93
|
+
tid = None
|
|
94
|
+
if resname:
|
|
95
|
+
rx = re.compile(r"\$r\(\s*['\"]app\.string\." + re.escape(resname) + r"['\"]")
|
|
96
|
+
for sf in src_files:
|
|
97
|
+
tid = inject_in_file(sf, rx)
|
|
98
|
+
if tid:
|
|
99
|
+
injected[t] = tid
|
|
100
|
+
break
|
|
101
|
+
if not tid:
|
|
102
|
+
unresolved.append(t)
|
|
103
|
+
|
|
104
|
+
out = args.out or os.path.join(args.root, '.hypium_forge', 'injected_ids.json')
|
|
105
|
+
json.dump(injected, open(out, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
|
|
106
|
+
print(f'资源命中注入: {len(injected)} / 未解析: {len(unresolved)} → {out}')
|
|
107
|
+
for t, tid in sorted(injected.items()):
|
|
108
|
+
print(f' + {t} → {tid}')
|
|
109
|
+
if unresolved:
|
|
110
|
+
print('未解析(动态文案/图标按钮,脚本走文本/兜底定位):', unresolved[:15])
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == '__main__':
|
|
114
|
+
main()
|
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""lint_asserts.py — 断言强度门禁(白盒直写形态,2026-09-29 none 臂对照实证落地)。
|
|
3
|
-
|
|
4
|
-
背景:none 臂(无 skill)LLM 凭通用能力达成 34% 强断言(hilog 防污染/dump Toast 轮询/
|
|
5
|
-
剪贴板),B 线(抄模板)仅 15%——模板能力边界效应。本门禁把下限固化:
|
|
6
|
-
- 强断言步骤占比 ≥ 30%(默认阈值),低于即 FAIL
|
|
7
|
-
- vacuous 用例点名(纯点击链、无任何值级断言)——对应 assert_audit.py 语义(.py 版)
|
|
8
|
-
|
|
9
|
-
值级断言原语:checked/unchecked/enabled/equals/text_equals/text_absent/toast_text_seen/
|
|
10
|
-
hilog_tail(关键字)/pasteboard/剪贴板/_dump_layout 断言性使用/text_contains/count
|
|
11
|
-
用法: python3 lint_asserts.py --dir testcases [--threshold 30]
|
|
12
|
-
"""
|
|
13
|
-
import argparse
|
|
14
|
-
import glob
|
|
15
|
-
import os
|
|
16
|
-
import re
|
|
17
|
-
import sys
|
|
18
|
-
|
|
19
|
-
STRONG_CALLS = re.compile(
|
|
20
|
-
r'\b(checked|unchecked|enabled\(|disabled|text_equals|text_absent|toast_text_seen|'
|
|
21
|
-
r'hilog_clear|hilog_tail|pasteboard|剪贴板|text_contains|find_all_components\s*\(\s*BY\.)'
|
|
22
|
-
r'[^)]*\)|\bchecked\s*\(\s*True\)|assert\s')
|
|
23
|
-
STEP_PAT = re.compile(r'^\s*(?:Step\(|step_shot\(|#)', re.M)
|
|
24
|
-
ACTION_PAT = re.compile(r'\b(id_click|text_click|click_text|click_tab|id_input|d\.touch|press_back|swipe|scroll_click)\b')
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
STRONG_STEP_WORDS = ('断言', '校验', 'equals', 'checked', 'absent', 'toast', 'hilog',
|
|
28
|
-
'剪贴板', 'pasteboard', '包含', '等于', '消失', '翻转为', '已开启', '已关闭',
|
|
29
|
-
'一致性', '业务数据', '关键字', 'dump')
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
VALUE_CALLS = re.compile(
|
|
33
|
-
r'\b(text_equals|text_absent|toast_text_seen|hilog_clear|hilog_tail|pasteboard|'
|
|
34
|
-
r'find_all_components|checked\s*\(|asserts?\s*\.|\.assert)')
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def audit_file(path):
|
|
38
|
-
"""双轨审计(两种白盒写法都覆盖):
|
|
39
|
-
轨1 Step 结构:Step 描述含值级断言语义词 = 强步骤;
|
|
40
|
-
轨2 无 Step(内联代码):值级断言调用数 / (值级调用+动作调用) 为强度。"""
|
|
41
|
-
src = open(path, encoding='utf-8').read()
|
|
42
|
-
steps_all = re.findall(r'Step\(\s*"([^"]+)"', src)
|
|
43
|
-
value_calls = len(VALUE_CALLS.findall(src))
|
|
44
|
-
if steps_all:
|
|
45
|
-
strong = sum(1 for t in steps_all if any(w in t.lower() for w in STRONG_STEP_WORDS))
|
|
46
|
-
return len(steps_all), 0, strong, strong > 0 or value_calls > 0
|
|
47
|
-
actions = len(ACTION_PAT.findall(src))
|
|
48
|
-
return value_calls + actions, 0, value_calls, value_calls > 0
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def main():
|
|
52
|
-
ap = argparse.ArgumentParser()
|
|
53
|
-
ap.add_argument('--dir', required=True)
|
|
54
|
-
ap.add_argument('--threshold', type=int, default=30)
|
|
55
|
-
args = ap.parse_args()
|
|
56
|
-
total_steps = total_strong = vacuous = n = 0
|
|
57
|
-
vacuous_list = []
|
|
58
|
-
for p in sorted(glob.glob(os.path.join(args.dir, 'TC*.py'))):
|
|
59
|
-
steps, marks, strong_steps, has_value = audit_file(p)
|
|
60
|
-
n += 1
|
|
61
|
-
total_steps += steps
|
|
62
|
-
total_strong += (strong_steps or marks)
|
|
63
|
-
if not has_value:
|
|
64
|
-
vacuous += 1
|
|
65
|
-
vacuous_list.append(os.path.basename(p))
|
|
66
|
-
pct = round(total_strong / total_steps * 100, 1) if total_steps else 0
|
|
67
|
-
print(f'断言强度: 强断言 {total_strong}/{total_steps} 步 = {pct}%(下限 {args.threshold}%)')
|
|
68
|
-
if vacuous_list:
|
|
69
|
-
print(f'⚠ vacuous 用例(无任何值级断言){vacuous} 个: {", ".join(vacuous_list[:8])}')
|
|
70
|
-
if n and pct < args.threshold:
|
|
71
|
-
print(f'✗ FAIL:断言强度 {pct}% < {args.threshold}%——按场景目的补值级断言(最佳区间 40~70%)'
|
|
72
|
-
f'(Toast→toast_text_seen;系统事件→hilog 轮询+用例前 hilog_clear;'
|
|
73
|
-
f'状态→checked;文案→text_equals;消失→text_absent)后重检')
|
|
74
|
-
sys.exit(1)
|
|
75
|
-
print('✓ PASS')
|
|
76
|
-
sys.exit(0)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if __name__ == '__main__':
|
|
80
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""lint_asserts.py — 断言强度门禁(白盒直写形态,2026-09-29 none 臂对照实证落地)。
|
|
3
|
+
|
|
4
|
+
背景:none 臂(无 skill)LLM 凭通用能力达成 34% 强断言(hilog 防污染/dump Toast 轮询/
|
|
5
|
+
剪贴板),B 线(抄模板)仅 15%——模板能力边界效应。本门禁把下限固化:
|
|
6
|
+
- 强断言步骤占比 ≥ 30%(默认阈值),低于即 FAIL
|
|
7
|
+
- vacuous 用例点名(纯点击链、无任何值级断言)——对应 assert_audit.py 语义(.py 版)
|
|
8
|
+
|
|
9
|
+
值级断言原语:checked/unchecked/enabled/equals/text_equals/text_absent/toast_text_seen/
|
|
10
|
+
hilog_tail(关键字)/pasteboard/剪贴板/_dump_layout 断言性使用/text_contains/count
|
|
11
|
+
用法: python3 lint_asserts.py --dir testcases [--threshold 30]
|
|
12
|
+
"""
|
|
13
|
+
import argparse
|
|
14
|
+
import glob
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
STRONG_CALLS = re.compile(
|
|
20
|
+
r'\b(checked|unchecked|enabled\(|disabled|text_equals|text_absent|toast_text_seen|'
|
|
21
|
+
r'hilog_clear|hilog_tail|pasteboard|剪贴板|text_contains|find_all_components\s*\(\s*BY\.)'
|
|
22
|
+
r'[^)]*\)|\bchecked\s*\(\s*True\)|assert\s')
|
|
23
|
+
STEP_PAT = re.compile(r'^\s*(?:Step\(|step_shot\(|#)', re.M)
|
|
24
|
+
ACTION_PAT = re.compile(r'\b(id_click|text_click|click_text|click_tab|id_input|d\.touch|press_back|swipe|scroll_click)\b')
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
STRONG_STEP_WORDS = ('断言', '校验', 'equals', 'checked', 'absent', 'toast', 'hilog',
|
|
28
|
+
'剪贴板', 'pasteboard', '包含', '等于', '消失', '翻转为', '已开启', '已关闭',
|
|
29
|
+
'一致性', '业务数据', '关键字', 'dump')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
VALUE_CALLS = re.compile(
|
|
33
|
+
r'\b(text_equals|text_absent|toast_text_seen|hilog_clear|hilog_tail|pasteboard|'
|
|
34
|
+
r'find_all_components|checked\s*\(|asserts?\s*\.|\.assert)')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def audit_file(path):
|
|
38
|
+
"""双轨审计(两种白盒写法都覆盖):
|
|
39
|
+
轨1 Step 结构:Step 描述含值级断言语义词 = 强步骤;
|
|
40
|
+
轨2 无 Step(内联代码):值级断言调用数 / (值级调用+动作调用) 为强度。"""
|
|
41
|
+
src = open(path, encoding='utf-8').read()
|
|
42
|
+
steps_all = re.findall(r'Step\(\s*"([^"]+)"', src)
|
|
43
|
+
value_calls = len(VALUE_CALLS.findall(src))
|
|
44
|
+
if steps_all:
|
|
45
|
+
strong = sum(1 for t in steps_all if any(w in t.lower() for w in STRONG_STEP_WORDS))
|
|
46
|
+
return len(steps_all), 0, strong, strong > 0 or value_calls > 0
|
|
47
|
+
actions = len(ACTION_PAT.findall(src))
|
|
48
|
+
return value_calls + actions, 0, value_calls, value_calls > 0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def main():
|
|
52
|
+
ap = argparse.ArgumentParser()
|
|
53
|
+
ap.add_argument('--dir', required=True)
|
|
54
|
+
ap.add_argument('--threshold', type=int, default=30)
|
|
55
|
+
args = ap.parse_args()
|
|
56
|
+
total_steps = total_strong = vacuous = n = 0
|
|
57
|
+
vacuous_list = []
|
|
58
|
+
for p in sorted(glob.glob(os.path.join(args.dir, 'TC*.py'))):
|
|
59
|
+
steps, marks, strong_steps, has_value = audit_file(p)
|
|
60
|
+
n += 1
|
|
61
|
+
total_steps += steps
|
|
62
|
+
total_strong += (strong_steps or marks)
|
|
63
|
+
if not has_value:
|
|
64
|
+
vacuous += 1
|
|
65
|
+
vacuous_list.append(os.path.basename(p))
|
|
66
|
+
pct = round(total_strong / total_steps * 100, 1) if total_steps else 0
|
|
67
|
+
print(f'断言强度: 强断言 {total_strong}/{total_steps} 步 = {pct}%(下限 {args.threshold}%)')
|
|
68
|
+
if vacuous_list:
|
|
69
|
+
print(f'⚠ vacuous 用例(无任何值级断言){vacuous} 个: {", ".join(vacuous_list[:8])}')
|
|
70
|
+
if n and pct < args.threshold:
|
|
71
|
+
print(f'✗ FAIL:断言强度 {pct}% < {args.threshold}%——按场景目的补值级断言(最佳区间 40~70%)'
|
|
72
|
+
f'(Toast→toast_text_seen;系统事件→hilog 轮询+用例前 hilog_clear;'
|
|
73
|
+
f'状态→checked;文案→text_equals;消失→text_absent)后重检')
|
|
74
|
+
sys.exit(1)
|
|
75
|
+
print('✓ PASS')
|
|
76
|
+
sys.exit(0)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == '__main__':
|
|
80
|
+
main()
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""lint_locate.py — 脚本定位方式自检门禁(铁律①的执行期强制)。
|
|
3
|
-
|
|
4
|
-
统计 testcases/*.py 的定位调用(id_click 等 BY.key 原语 vs text_click/click_text),
|
|
5
|
-
text 定位占比超阈值(默认 20%)即 FAIL 并列出改写建议(ids.json 有映射优先)。
|
|
6
|
-
写完脚本后必须先过本门禁再执行。
|
|
7
|
-
|
|
8
|
-
用法: python3 lint_locate.py --dir testcases [--ids ids.json] [--threshold 20]
|
|
9
|
-
"""
|
|
10
|
-
import argparse
|
|
11
|
-
import glob
|
|
12
|
-
import json
|
|
13
|
-
import os
|
|
14
|
-
import re
|
|
15
|
-
import sys
|
|
16
|
-
|
|
17
|
-
KEY_PRIMS = r'\bid_click\(|BY\.key\('
|
|
18
|
-
TEXT_PRIMS = r'\btext_click\(|\bclick_text\(|BY\.text\((?![^)]*CONTAINS)|find_component\(\s*BY\.text'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def main():
|
|
22
|
-
ap = argparse.ArgumentParser()
|
|
23
|
-
ap.add_argument('--dir', required=True)
|
|
24
|
-
ap.add_argument('--ids', default=None)
|
|
25
|
-
ap.add_argument('--threshold', type=int, default=20)
|
|
26
|
-
args = ap.parse_args()
|
|
27
|
-
ids = {}
|
|
28
|
-
if args.ids and os.path.exists(args.ids):
|
|
29
|
-
ids = json.load(open(args.ids, encoding='utf-8'))
|
|
30
|
-
total_key = total_text = 0
|
|
31
|
-
offenders = []
|
|
32
|
-
for p in sorted(glob.glob(os.path.join(args.dir, 'TC*.py'))):
|
|
33
|
-
src = open(p, encoding='utf-8').read()
|
|
34
|
-
k = len(re.findall(KEY_PRIMS, src))
|
|
35
|
-
t = len(re.findall(TEXT_PRIMS, src))
|
|
36
|
-
total_key += k
|
|
37
|
-
total_text += t
|
|
38
|
-
if t > k:
|
|
39
|
-
hint = ''
|
|
40
|
-
m = re.findall(r'(?:text_click|click_text)\(\s*d\s*,\s*[\'"]([^\'"]+)[\'"]', src)
|
|
41
|
-
mapped = [f"{x} → {ids[x]}" for x in m if x in ids][:3]
|
|
42
|
-
if mapped:
|
|
43
|
-
hint = ' | ids.json 有映射: ' + '; '.join(mapped)
|
|
44
|
-
offenders.append(f"{os.path.basename(p)}: key={k} text={t}{hint}")
|
|
45
|
-
total = total_key + total_text
|
|
46
|
-
pct = round(total_key / total * 100) if total else 100
|
|
47
|
-
print(f"定位统计: BY.key={total_key} / text={total_text} → key 占比 {pct}%(阈值 ≥{100 - args.threshold}%)")
|
|
48
|
-
if offenders:
|
|
49
|
-
print(f"text 主导的脚本 {len(offenders)} 个:")
|
|
50
|
-
for o in offenders[:15]:
|
|
51
|
-
print(' -', o)
|
|
52
|
-
if total and pct < 100 - args.threshold:
|
|
53
|
-
print(f"✗ FAIL:text 定位占比 {100 - pct}% > {args.threshold}% —— 须把 ids.json 有映射的 text_click 改写为 id_click 后重过门禁")
|
|
54
|
-
sys.exit(1)
|
|
55
|
-
print('✓ PASS')
|
|
56
|
-
sys.exit(0)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if __name__ == '__main__':
|
|
60
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""lint_locate.py — 脚本定位方式自检门禁(铁律①的执行期强制)。
|
|
3
|
+
|
|
4
|
+
统计 testcases/*.py 的定位调用(id_click 等 BY.key 原语 vs text_click/click_text),
|
|
5
|
+
text 定位占比超阈值(默认 20%)即 FAIL 并列出改写建议(ids.json 有映射优先)。
|
|
6
|
+
写完脚本后必须先过本门禁再执行。
|
|
7
|
+
|
|
8
|
+
用法: python3 lint_locate.py --dir testcases [--ids ids.json] [--threshold 20]
|
|
9
|
+
"""
|
|
10
|
+
import argparse
|
|
11
|
+
import glob
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
KEY_PRIMS = r'\bid_click\(|BY\.key\('
|
|
18
|
+
TEXT_PRIMS = r'\btext_click\(|\bclick_text\(|BY\.text\((?![^)]*CONTAINS)|find_component\(\s*BY\.text'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main():
|
|
22
|
+
ap = argparse.ArgumentParser()
|
|
23
|
+
ap.add_argument('--dir', required=True)
|
|
24
|
+
ap.add_argument('--ids', default=None)
|
|
25
|
+
ap.add_argument('--threshold', type=int, default=20)
|
|
26
|
+
args = ap.parse_args()
|
|
27
|
+
ids = {}
|
|
28
|
+
if args.ids and os.path.exists(args.ids):
|
|
29
|
+
ids = json.load(open(args.ids, encoding='utf-8'))
|
|
30
|
+
total_key = total_text = 0
|
|
31
|
+
offenders = []
|
|
32
|
+
for p in sorted(glob.glob(os.path.join(args.dir, 'TC*.py'))):
|
|
33
|
+
src = open(p, encoding='utf-8').read()
|
|
34
|
+
k = len(re.findall(KEY_PRIMS, src))
|
|
35
|
+
t = len(re.findall(TEXT_PRIMS, src))
|
|
36
|
+
total_key += k
|
|
37
|
+
total_text += t
|
|
38
|
+
if t > k:
|
|
39
|
+
hint = ''
|
|
40
|
+
m = re.findall(r'(?:text_click|click_text)\(\s*d\s*,\s*[\'"]([^\'"]+)[\'"]', src)
|
|
41
|
+
mapped = [f"{x} → {ids[x]}" for x in m if x in ids][:3]
|
|
42
|
+
if mapped:
|
|
43
|
+
hint = ' | ids.json 有映射: ' + '; '.join(mapped)
|
|
44
|
+
offenders.append(f"{os.path.basename(p)}: key={k} text={t}{hint}")
|
|
45
|
+
total = total_key + total_text
|
|
46
|
+
pct = round(total_key / total * 100) if total else 100
|
|
47
|
+
print(f"定位统计: BY.key={total_key} / text={total_text} → key 占比 {pct}%(阈值 ≥{100 - args.threshold}%)")
|
|
48
|
+
if offenders:
|
|
49
|
+
print(f"text 主导的脚本 {len(offenders)} 个:")
|
|
50
|
+
for o in offenders[:15]:
|
|
51
|
+
print(' -', o)
|
|
52
|
+
if total and pct < 100 - args.threshold:
|
|
53
|
+
print(f"✗ FAIL:text 定位占比 {100 - pct}% > {args.threshold}% —— 须把 ids.json 有映射的 text_click 改写为 id_click 后重过门禁")
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
print('✓ PASS')
|
|
56
|
+
sys.exit(0)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == '__main__':
|
|
60
|
+
main()
|