@hupan56/wlkj 3.0.0 → 3.1.3

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 (247) hide show
  1. package/bin/cli.js +1435 -1373
  2. package/package.json +1 -1
  3. package/templates/qoder/commands/optional/wl-insight.md +6 -4
  4. package/templates/qoder/commands/optional/wl-report.md +20 -39
  5. package/templates/qoder/commands/optional/wl-status.md +4 -4
  6. package/templates/qoder/commands/wl-code.md +2 -2
  7. package/templates/qoder/commands/wl-design.md +5 -5
  8. package/templates/qoder/commands/wl-init.md +3 -3
  9. package/templates/qoder/commands/wl-prd.md +26 -13
  10. package/templates/qoder/commands/wl-req.md +3 -3
  11. package/templates/qoder/commands/wl-search.md +78 -47
  12. package/templates/qoder/commands/wl-task.md +343 -40
  13. package/templates/qoder/commands/wl-test.md +10 -10
  14. package/templates/qoder/config.yaml +35 -13
  15. package/templates/qoder/hooks/post-tool-use.py +181 -0
  16. package/templates/qoder/hooks/session-start.py +82 -1
  17. package/templates/qoder/hooks/stop-eval.py +207 -0
  18. package/templates/qoder/hooks/user-prompt-submit.py +139 -0
  19. package/templates/qoder/rules/wl-pipeline.md +70 -8
  20. package/templates/qoder/scripts/README.md +78 -115
  21. package/templates/qoder/scripts/capability/__init__.py +26 -0
  22. package/templates/qoder/scripts/capability/__main__.py +72 -0
  23. package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
  24. package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
  25. package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
  26. package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
  27. package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
  28. package/templates/qoder/scripts/capability/caps/context.py +36 -0
  29. package/templates/qoder/scripts/capability/caps/cron.py +50 -0
  30. package/templates/qoder/scripts/capability/caps/identity.py +43 -0
  31. package/templates/qoder/scripts/capability/caps/memory.py +71 -0
  32. package/templates/qoder/scripts/capability/caps/notify.py +41 -0
  33. package/templates/qoder/scripts/capability/caps/present.py +48 -0
  34. package/templates/qoder/scripts/capability/caps/repo.py +31 -0
  35. package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
  36. package/templates/qoder/scripts/capability/chain.py +92 -0
  37. package/templates/qoder/scripts/capability/memory_chain.py +41 -0
  38. package/templates/qoder/scripts/capability/registry.py +246 -0
  39. package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
  40. package/templates/qoder/scripts/capability/smoke_test.py +211 -0
  41. package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
  42. package/templates/qoder/scripts/{report → deployment/setup}/__init__.py +11 -11
  43. package/templates/qoder/scripts/{setup → deployment/setup}/carriers.py +33 -26
  44. package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
  45. package/templates/qoder/scripts/{setup → deployment/setup}/init_doctor.py +55 -23
  46. package/templates/qoder/scripts/{setup → deployment/setup}/install_qoderwork.py +20 -12
  47. package/templates/qoder/scripts/{setup → deployment/setup}/platform_doctor.py +8 -2
  48. package/templates/qoder/scripts/{setup → deployment/setup}/repo_root.py +113 -112
  49. package/templates/qoder/scripts/{setup → deployment/setup}/setup.py +35 -12
  50. package/templates/qoder/scripts/{setup → deployment/setup}/setup_lanhu.py +13 -7
  51. package/templates/qoder/scripts/domain/__init__.py +0 -0
  52. package/templates/qoder/scripts/domain/design/__init__.py +0 -0
  53. package/templates/qoder/scripts/{report → domain/design}/fill_prototype.py +12 -6
  54. package/templates/qoder/scripts/{report → domain/design}/gen_design_doc.py +8 -2
  55. package/templates/qoder/scripts/{setup → domain/kg}/__init__.py +11 -11
  56. package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
  57. package/templates/qoder/scripts/{kg → domain/kg/build}/build_entity_registry.py +7 -2
  58. package/templates/qoder/scripts/{kg → domain/kg/build}/build_relations.py +7 -2
  59. package/templates/qoder/scripts/{kg → domain/kg/build}/build_style_index.py +13 -7
  60. package/templates/qoder/scripts/{kg → domain/kg/build}/build_workflows.py +7 -2
  61. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build.py +76 -9
  62. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build_db.py +7 -2
  63. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_incremental.py +11 -6
  64. package/templates/qoder/scripts/{report → domain/kg/build}/learn_aggregate.py +9 -4
  65. package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
  66. package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
  67. package/templates/qoder/scripts/{common → domain/kg/extract}/test_extract.py +115 -115
  68. package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
  69. package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
  70. package/templates/qoder/scripts/{kg → domain/kg/graph}/kg_link_db.py +7 -2
  71. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
  72. package/templates/qoder/scripts/{kg → domain/kg}/kg.py +263 -30
  73. package/templates/qoder/scripts/{common → domain/kg}/kg_capabilities.py +182 -182
  74. package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
  75. package/templates/qoder/scripts/{kg → domain/kg/search}/context_pack.py +30 -9
  76. package/templates/qoder/scripts/{kg → domain/kg/search}/enrich_prompt.py +10 -4
  77. package/templates/qoder/scripts/{kg → domain/kg/search}/prefetch.py +33 -8
  78. package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
  79. package/templates/qoder/scripts/{kg → domain/kg/search}/search_index.py +143 -77
  80. package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
  81. package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
  82. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  83. package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
  84. package/templates/qoder/scripts/{kg → domain/kg/storage}/kg_duckdb.py +38 -14
  85. package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
  86. package/templates/qoder/scripts/{report → domain/learning}/learn.py +8 -3
  87. package/templates/qoder/scripts/{mcp → domain/report}/__init__.py +11 -11
  88. package/templates/qoder/scripts/{report → domain/report}/add_session.py +9 -3
  89. package/templates/qoder/scripts/{report → domain/report}/export.py +10 -5
  90. package/templates/qoder/scripts/{report → domain/report}/report.py +9 -4
  91. package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
  92. package/templates/qoder/scripts/{report → domain/report}/role.py +10 -4
  93. package/templates/qoder/scripts/{report → domain/report}/status.py +112 -9
  94. package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
  95. package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
  96. package/templates/qoder/scripts/{report → domain/requirement}/archive_prd.py +8 -2
  97. package/templates/qoder/scripts/{report → domain/requirement}/req.py +228 -222
  98. package/templates/qoder/scripts/{kg → domain/task}/__init__.py +11 -11
  99. package/templates/qoder/scripts/domain/task/__pycache__/syncgate.cpython-39.pyc +0 -0
  100. package/templates/qoder/scripts/{task → domain/task}/git_sync.py +40 -21
  101. package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
  102. package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
  103. package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
  104. package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
  105. package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
  106. package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
  107. package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
  108. package/templates/qoder/scripts/foundation/__init__.py +0 -0
  109. package/templates/qoder/scripts/foundation/__pycache__/__init__.cpython-39.pyc +0 -0
  110. package/templates/qoder/scripts/foundation/__pycache__/bootstrap.cpython-39.pyc +0 -0
  111. package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
  112. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  113. package/templates/qoder/scripts/foundation/core/__pycache__/__init__.cpython-39.pyc +0 -0
  114. package/templates/qoder/scripts/foundation/core/__pycache__/paths.cpython-39.pyc +0 -0
  115. package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
  116. package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
  117. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
  118. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  119. package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
  120. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  121. package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
  122. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
  123. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  124. package/templates/qoder/scripts/foundation/identity/__pycache__/__init__.cpython-39.pyc +0 -0
  125. package/templates/qoder/scripts/foundation/identity/__pycache__/identity.cpython-39.pyc +0 -0
  126. package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
  127. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
  128. package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
  129. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
  130. package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
  131. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  132. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  133. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +2 -2
  134. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  135. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  136. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  137. package/templates/qoder/scripts/foundation/io/__pycache__/__init__.cpython-39.pyc +0 -0
  138. package/templates/qoder/scripts/foundation/io/__pycache__/atomicio.cpython-39.pyc +0 -0
  139. package/templates/qoder/scripts/foundation/io/__pycache__/filelock.cpython-39.pyc +0 -0
  140. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  141. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  142. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  143. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  144. package/templates/qoder/scripts/{common → foundation/protocol}/mcp_base.py +1 -1
  145. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  146. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  147. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  148. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  149. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  150. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  151. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  152. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  153. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  154. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  155. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  156. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  157. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  158. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  159. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  160. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  161. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  162. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  163. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  164. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  165. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  166. package/templates/qoder/scripts/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
  167. package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
  168. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  169. package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
  170. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  171. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  172. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  173. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  174. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  175. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  176. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  177. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  178. package/templates/qoder/scripts/validation/__init__.py +0 -0
  179. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  180. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  181. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  182. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  183. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  184. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  185. package/templates/qoder/scripts/{report → validation/metrics}/eval_prd.py +17 -10
  186. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  187. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  188. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  189. package/templates/qoder/scripts/{test → validation/test}/autotest.py +64 -4
  190. package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
  191. package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
  192. package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
  193. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  194. package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
  195. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  196. package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
  197. package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
  198. package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
  199. package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
  200. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  201. package/templates/qoder/settings.json +30 -0
  202. package/templates/qoder/skills/design-import/SKILL.md +22 -13
  203. package/templates/qoder/skills/design-review/SKILL.md +19 -10
  204. package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
  205. package/templates/qoder/skills/prd-review/SKILL.md +6 -4
  206. package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
  207. package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
  208. package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
  209. package/templates/qoder/skills/test-generator/SKILL.md +7 -5
  210. package/templates/qoder/skills/wl-code/SKILL.md +23 -11
  211. package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
  212. package/templates/qoder/skills/wl-design/SKILL.md +5 -3
  213. package/templates/qoder/skills/wl-init/SKILL.md +6 -6
  214. package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
  215. package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
  216. package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
  217. package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
  218. package/templates/qoder/skills/wl-report/SKILL.md +25 -23
  219. package/templates/qoder/skills/wl-search/SKILL.md +149 -78
  220. package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
  221. package/templates/qoder/skills/wl-status/SKILL.md +48 -16
  222. package/templates/qoder/skills/wl-task/SKILL.md +92 -10
  223. package/templates/qoder/skills/wl-test/SKILL.md +75 -29
  224. package/templates/qoder/templates/prd-full-template.md +7 -0
  225. package/templates/root/AGENTS.md +275 -236
  226. package/templates/root/requirements.txt +3 -0
  227. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  228. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  229. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
  230. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
  231. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
  232. package/templates/.qoder/learning/eval-history.jsonl +0 -28
  233. package/templates/data/index/wiki-index.json +0 -8
  234. package/templates/qoder/nul +0 -4
  235. package/templates/qoder/scripts/kg/extract.py +0 -398
  236. package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
  237. package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
  238. package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
  239. package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
  240. package/templates/qoder/scripts/task/__init__.py +0 -11
  241. package/templates/qoder/scripts/test/__init__.py +0 -11
  242. package/templates/qoder/scripts/wlkj.py +0 -116
  243. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  244. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  245. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  246. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  247. /package/templates/qoder/scripts/{test → validation/test}/secure-ls.js +0 -0
@@ -0,0 +1,571 @@
1
+ # -*- coding: utf-8 -*-
2
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
3
+ import os as _o, sys as _s
4
+ _f = _o.path.abspath(__file__)
5
+ for _ in range(10):
6
+ _f = _o.path.dirname(_f)
7
+ _cp = _o.path.join(_f, 'foundation')
8
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
9
+ break
10
+ if _cp not in _s.path: _s.path.insert(0, _cp)
11
+ from bootstrap import setup; setup()
12
+
13
+ # 强制 stdout 无缓冲 — 被 timeout 杀掉/管道消费时也要看到输出 (实测踩坑)
14
+ try:
15
+ sys.stdout.reconfigure(line_buffering=True)
16
+ except Exception:
17
+ pass
18
+
19
+ """autotest_webaccess.py — 真功能测试执行器 (web-access CDP 直连已登录 Chrome).
20
+
21
+ 与 autotest_run.py 平级, 镜像同一个 task.json 契约, 引擎换成 web-access.
22
+ 吃同样的 --task 文件, 吐同样结构的 result JSON.
23
+
24
+ 为什么用 web-access 而非 opencli:
25
+ - opencli 走 chrome.debugger.attach (扩展代理), 在 Windows 后台 session 静默失败.
26
+ - web-access 走 CDP 端口直连 (chrome://inspect#remote-debugging), 不装扩展,
27
+ 和 QoderWork chrome 连通器同源机制, 稳.
28
+ - 登录态天然复用 (连的是日常 Chrome, cookie/session 全在), 验证码零接触.
29
+
30
+ 用法: python autotest_webaccess.py --task <任务文件> [--browser chrome]
31
+ 任务文件 JSON: {
32
+ "page_path": "/veh/vehicle/vehAffair/insurance",
33
+ "page_name": "保险管理",
34
+ "cases": [ {case_id, title, steps:[{intent,desc,value,anchor,check,expect}], expected} ]
35
+ }
36
+
37
+ 前置: 用户 Chrome 开 chrome://inspect/#remote-debugging 勾选允许调试.
38
+ 没开 → 友好提示 + 自动回退 playwright (50 人零报错).
39
+ """
40
+ import json, sys, time, os, re, argparse, subprocess
41
+ import urllib.request, urllib.error
42
+
43
+ # ===== 配置 (路径自检测, 与 autotest_run.py 一致) =====
44
+ SCRIPTS = os.path.dirname(os.path.abspath(__file__))
45
+ # test 目录在 .qoder/scripts/test, 回项目根要上 3 级. 向上找含 .qoder 的目录兜底.
46
+ _d = SCRIPTS
47
+ while _d != os.path.dirname(_d):
48
+ if os.path.isdir(os.path.join(_d, '.qoder')):
49
+ PROJECT = _d
50
+ break
51
+ _d = os.path.dirname(_d)
52
+ else:
53
+ PROJECT = os.path.normpath(os.path.join(SCRIPTS, '..', '..', '..'))
54
+
55
+ WEBACCESS_DIR = os.path.join(os.path.dirname(SCRIPTS), 'web-access') # .qoder/scripts/web-access (test 同级)
56
+ CDP_PROXY_PORT = 3456
57
+ PROXY_URL = 'http://localhost:%d' % CDP_PROXY_PORT
58
+ AUTOLEARN = os.path.join(SCRIPTS, 'autotest.py')
59
+ BASE = 'https://icsqas.inforecloud.com'
60
+
61
+
62
+ # ===== web-access HTTP API 封装 (零依赖, 用 urllib) =====
63
+ def _http(method, path, body=None, timeout=15):
64
+ """调 cdp-proxy 的 HTTP API. 返回解析后的 JSON dict."""
65
+ url = PROXY_URL + path
66
+ data = body.encode('utf-8') if body is not None else None
67
+ req = urllib.request.Request(url, data=data, method=method)
68
+ if body is not None:
69
+ req.add_header('Content-Type', 'text/plain')
70
+ try:
71
+ with urllib.request.urlopen(req, timeout=timeout) as r:
72
+ return json.loads(r.read().decode('utf-8', 'ignore'))
73
+ except urllib.error.URLError as e:
74
+ return {'error': '连接失败: %s (proxy 没起? 跑 node web-access/scripts/check-deps.mjs --browser chrome)' % str(e)[:50]}
75
+ except Exception as e:
76
+ return {'error': str(e)[:60]}
77
+
78
+
79
+ def proxy_ready():
80
+ """检查 cdp-proxy 是否在跑 + 连着浏览器."""
81
+ h = _http('GET', '/health', timeout=5)
82
+ return h.get('connected') is True, h
83
+
84
+
85
+ def start_proxy(browser='chrome'):
86
+ """启动 cdp-proxy (自动发现 Chrome 调试端口). 返回 (ok, msg).
87
+ proxy 是长驻进程 — 已在跑就直接复用, 不重复 spawn (避免冲突/卡死)."""
88
+ # 快路径: proxy 已在跑且连着浏览器 → 直接用
89
+ ok, h = proxy_ready()
90
+ if ok:
91
+ return True, h.get('browser', {}).get('label', '')
92
+ check = os.path.join(WEBACCESS_DIR, 'scripts', 'check-deps.mjs')
93
+ if not os.path.isfile(check):
94
+ return False, 'web-access 引擎未安装 (%s 不存在)' % check
95
+ # check-deps 会自动起 proxy 并连浏览器. stdout 含 proxy: ready 即成功.
96
+ try:
97
+ r = subprocess.run(['node', check, '--browser', browser],
98
+ capture_output=True, timeout=40, cwd=WEBACCESS_DIR)
99
+ out = (r.stdout + r.stderr).decode('utf-8', 'ignore')
100
+ except subprocess.TimeoutExpired:
101
+ return False, 'check-deps 超时 (Chrome 调试开关没开?)'
102
+ except FileNotFoundError:
103
+ return False, 'node 未安装或不在 PATH (web-access 要 Node 22+)'
104
+ if 'proxy: ready' in out or 'proxy: connecting' in out:
105
+ time.sleep(2) # 等 proxy 完全就绪
106
+ ok, h = proxy_ready()
107
+ return ok, h.get('browser', {}).get('label', '') if ok else 'proxy 起了但没连上'
108
+ # 没成功 → 解析失败原因给用户
109
+ if 'needs decision' in out or '请询问用户' in out:
110
+ return False, '需指定浏览器: 加 --browser chrome'
111
+ if 'remote debugging' in out.lower() or '调试' in out:
112
+ return False, 'Chrome 远程调试未开启. 地址栏开 chrome://inspect/#remote-debugging 勾选 "Allow remote debugging"'
113
+ return False, out.strip().split('\n')[-1][:80]
114
+
115
+
116
+ # ===== tab 管理 =====
117
+ def new_tab(url, wait=4):
118
+ """开新后台 tab, 返回 targetId 或 None."""
119
+ r = _http('POST', '/new', body=url, timeout=30)
120
+ tid = r.get('targetId')
121
+ if tid and wait:
122
+ time.sleep(wait)
123
+ return tid
124
+
125
+
126
+ def navigate(tid, url, wait=3):
127
+ """在已有 tab 导航."""
128
+ r = _http('POST', '/navigate?target=%s' % tid, body=url, timeout=30)
129
+ if wait:
130
+ time.sleep(wait)
131
+ return 'error' not in r
132
+
133
+
134
+ def close_tab(tid):
135
+ """关闭 tab."""
136
+ try:
137
+ _http('GET', '/close?target=%s' % tid, timeout=10)
138
+ except Exception:
139
+ pass
140
+
141
+
142
+ def page_info(tid):
143
+ """读页面 title/url/readyState."""
144
+ return _http('GET', '/info?target=%s' % tid, timeout=10)
145
+
146
+
147
+ def eval_js(tid, code, timeout=15):
148
+ """执行 JS, 返回 (value, error). value 是任意类型."""
149
+ r = _http('POST', '/eval?target=%s' % tid, body=code, timeout=timeout)
150
+ if 'error' in r:
151
+ return None, r['error']
152
+ return r.get('value'), None
153
+
154
+
155
+ def click_css(tid, selector):
156
+ """JS 点击 (CSS 选择器). 返回 (ok, msg)."""
157
+ r = _http('POST', '/click?target=%s' % tid, body=selector, timeout=10)
158
+ return 'error' not in r, r.get('error', 'clicked')
159
+
160
+
161
+ # ===== 登录态: 无需伪造, CDP 连的是已登录 Chrome. 但需验证登录态有效 =====
162
+ def check_logged_in(tid):
163
+ """检查当前页是否在登录页 (在登录页 = 未登录/失效)."""
164
+ info = page_info(tid)
165
+ url = info.get('url', '')
166
+ # SPA 登录页通常 url 含 /auth/login 或 title 是登录相关
167
+ return '/auth/login' not in url, url
168
+
169
+
170
+ # ===== anchor → CSS 选择器 (web-access click 用 CSS) =====
171
+ def anchor_to_css(anchor, desc=''):
172
+ """把 anchor {role,name,placeholder,field,text} 转 CSS 选择器.
173
+ web-access 的 click 接受 CSS, 比 opencli 的语义 flag 弱一点, 但 eval 能兜底."""
174
+ if not anchor:
175
+ return ''
176
+ field = anchor.get('field', '')
177
+ if field:
178
+ return "input[name='%s']" % field
179
+ name = anchor.get('name', '')
180
+ ph = anchor.get('placeholder', '')
181
+ role = anchor.get('role', '')
182
+ text = anchor.get('text', '')
183
+ # 优先按 placeholder/name 文本匹配 input/button
184
+ if role == 'textbox':
185
+ if ph:
186
+ return "input[placeholder*='%s']" % ph
187
+ if name:
188
+ return "input[placeholder*='%s']" % name
189
+ if role == 'button':
190
+ # button 文本匹配用 eval 兜底更准, 这里先给个基础
191
+ if name:
192
+ return "button"
193
+ if text:
194
+ return "*"
195
+ return ''
196
+
197
+
198
+ def click_by_anchor(tid, anchor, desc=''):
199
+ """按 anchor 点击: 先试 CSS, 失败用 eval 文本匹配兜底."""
200
+ css = anchor_to_css(anchor, desc)
201
+ name = anchor.get('name', '')
202
+ role = anchor.get('role', '')
203
+ text = anchor.get('text', '')
204
+ # button/link/tab 用 eval 按 innerText 精确匹配 (比 CSS :has-text 稳)
205
+ if role in ('button', 'link', 'tab') and (name or text):
206
+ kw = name or text
207
+ code = ("(function(){const els=[...document.querySelectorAll('%s')];"
208
+ "const el=els.find(e=>(e.innerText||'').trim()==='%s'||(e.innerText||'').includes('%s'));"
209
+ "if(el){el.scrollIntoView();el.click();return 'ok'}return 'notfound'})()" % (
210
+ 'button,a,[role=tab],.ant-tabs-tab' if role == 'tab' else 'button,a,[role=button]',
211
+ kw, kw))
212
+ v, e = eval_js(tid, code)
213
+ return (v == 'ok'), ('clicked:%s' % kw if v == 'ok' else '未找到%s' % kw)
214
+ # textbox 用 CSS
215
+ if css:
216
+ ok, msg = click_css(tid, css)
217
+ if ok:
218
+ return True, 'clicked'
219
+ return False, '元素未找到(%s)' % desc
220
+
221
+
222
+ def fill_by_anchor(tid, anchor, val, desc=''):
223
+ """填值: 用 eval 直接设 value + dispatch input event (兼容 Vue)."""
224
+ css = anchor_to_css(anchor, desc)
225
+ if not css:
226
+ return False, '元素未找到(anchor空:%s)' % desc
227
+ # Vue 需要触发 input 事件框架才认. 用原生 setter + dispatchEvent.
228
+ safe_val = str(val).replace("\\", "\\\\").replace("'", "\\'")
229
+ code = ("(function(){const el=document.querySelector(\"%s\");"
230
+ "if(!el)return 'notfound';"
231
+ "const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;"
232
+ "setter.call(el,'%s');"
233
+ "el.dispatchEvent(new Event('input',{bubbles:true}));"
234
+ "el.dispatchEvent(new Event('change',{bubbles:true}));"
235
+ "return el.value})()" % (css.replace('"', '\\"'), safe_val))
236
+ v, e = eval_js(tid, code)
237
+ if v == 'notfound':
238
+ return False, '元素未找到(%s)' % desc
239
+ return (v == str(val)), 'filled(actual=%s)' % _short(v)
240
+
241
+
242
+ # ===== 表格等待 (与 autotest_run.py 等价语义) =====
243
+ def wait_table_stable(tid, max_wait=8):
244
+ """轮询 .vxe-body--row 行数, 等表格加载稳定."""
245
+ deadline = time.time() + max_wait
246
+ zero_streak = 0
247
+ last = -1
248
+ while time.time() < deadline:
249
+ v, _ = eval_js(tid, "document.querySelectorAll('.vxe-body--row').length", timeout=5)
250
+ try:
251
+ n = int(v)
252
+ except (TypeError, ValueError):
253
+ n = 0
254
+ if n > 0:
255
+ time.sleep(0.6)
256
+ v2, _ = eval_js(tid, "document.querySelectorAll('.vxe-body--row').length", timeout=5)
257
+ try:
258
+ n2 = int(v2)
259
+ except (TypeError, ValueError):
260
+ n2 = 0
261
+ if n2 >= n:
262
+ return n2
263
+ else:
264
+ zero_streak += 1
265
+ if zero_streak >= 3:
266
+ return 0
267
+ time.sleep(0.5)
268
+ return last
269
+
270
+
271
+ # ===== 单步执行 (镜像 autotest_run.py exec_step 结构) =====
272
+ def exec_step(tid, step, timeout=8):
273
+ """执行一步, 返回 (ok, detail)."""
274
+ intent = step.get('intent', '')
275
+ desc = step.get('desc', '')
276
+ anchor = step.get('anchor', {}) or {}
277
+ val = step.get('value', '')
278
+
279
+ try:
280
+ if intent == 'goto':
281
+ url = BASE + val if val.startswith('/') else val
282
+ navigate(tid, url)
283
+ wait_table_stable(tid, max_wait=8)
284
+ return True, 'navigated'
285
+
286
+ if intent == 'fill':
287
+ return fill_by_anchor(tid, anchor, val, desc)
288
+
289
+ if intent == 'click':
290
+ ok, msg = click_by_anchor(tid, anchor, desc)
291
+ time.sleep(1.5)
292
+ return ok, msg
293
+
294
+ if intent == 'observe':
295
+ return True, 'observed'
296
+
297
+ if intent == 'extract':
298
+ # 优先 expect (数据断言, 可能是 DOM 值 或 fetch API)
299
+ if step.get('expect'):
300
+ return _check_expect(tid, step['expect'])
301
+ # 否则走旧语义: body 文本含 check 关键词
302
+ check = step.get('check', '')
303
+ v, _ = eval_js(tid, "document.body.innerText", timeout=10)
304
+ body = str(v) if v else ''
305
+ if check:
306
+ hit = check in body
307
+ return (hit, '命中:%s' % check if hit else '未命中:%s' % check)
308
+ return True, 'extracted(no check)'
309
+
310
+ if intent == 'assert':
311
+ return _check_assert(tid, step.get('check', ''))
312
+
313
+ except Exception as e:
314
+ return False, str(e)[:60]
315
+ return False, '未知intent:%s' % intent
316
+
317
+
318
+ # ===== 数据断言 (web-access 核心增量: eval DOM 值 或 eval fetch API) =====
319
+ def _check_expect(tid, expect):
320
+ """数据断言. expect 支持两种模式:
321
+ 1. {dom: "css", jsonpath: "text", equals: "湖南石门"} — 读 DOM 元素值
322
+ 2. {fetch: "/api/...", jsonpath: "code", equals: 200} — 调后端接口验字段
323
+ jsonpath 用点路径 (data.total / rows[0].name), 轻量实现不引依赖.
324
+ """
325
+ # 模式 1: DOM 值断言
326
+ if 'dom' in expect:
327
+ css = expect['dom']
328
+ jp = expect.get('jsonpath', 'text').lstrip('$.').lstrip('.')
329
+ want = expect.get('equals')
330
+ code = "(function(){const el=document.querySelector(\"%s\");if(!el)return null;" \
331
+ "const v=el.innerText||el.value||el.textContent;return v.trim()})()" % css.replace('"', '\\"')
332
+ v, e = eval_js(tid, code)
333
+ actual = str(v).strip() if v else ''
334
+ ok = (actual == str(want)) if want is not None else bool(actual)
335
+ return ok, '%s=%s(期望%s)' % (jp, _short(actual), _short(want))
336
+
337
+ # 模式 2: API 断言 (从页面上下文 fetch, 带登录态)
338
+ if 'fetch' in expect:
339
+ endpoint = expect['fetch']
340
+ jp = expect.get('jsonpath', '').lstrip('$.').lstrip('.')
341
+ want = expect.get('equals')
342
+ method = expect.get('method', 'POST')
343
+ body = expect.get('body', {})
344
+ body_js = json.dumps(body, ensure_ascii=False)
345
+ # 注意: 裸 fetch 可能 401 (没带 token). 优先建议用 dom 断言.
346
+ code = ("(async()=>{try{const r=await fetch('%s',{method:'%s',headers:{'Content-Type':'application/json'}," \
347
+ "body:%s});const d=await r.json();return JSON.stringify(d)}catch(e){return 'ERR:'+e.message}})()" % (
348
+ endpoint, method, body_js))
349
+ v, e = eval_js(tid, code, timeout=20)
350
+ if e:
351
+ return False, 'fetch失败:%s' % e
352
+ try:
353
+ data = json.loads(v) if isinstance(v, str) else v
354
+ except Exception:
355
+ return False, '响应非JSON:%s' % _short(v)
356
+ actual = _dig(data, jp)
357
+ ok = (actual == want)
358
+ return ok, '%s=%s(期望%s)' % (jp, _short(actual), _short(want))
359
+
360
+ return True, 'expect(无dom/fetch字段)'
361
+
362
+
363
+ def _check_assert(tid, check):
364
+ """镜像 autotest_run.py 的 assert: rows>/rows==/url_contains/input_empty."""
365
+ if not check:
366
+ return True, 'assert(no check)'
367
+ if check.startswith('rows>'):
368
+ n = int(check[5:])
369
+ wait_table_stable(tid, max_wait=6)
370
+ v, _ = eval_js(tid, "document.querySelectorAll('.vxe-body--row').length", timeout=5)
371
+ try:
372
+ rows = int(v)
373
+ except (TypeError, ValueError):
374
+ rows = 0
375
+ return (rows > n, '行数%d>%d' % (rows, n) if rows > n else '行数%d<=%d' % (rows, n))
376
+ if check.startswith('rows=='):
377
+ n = int(check[6:])
378
+ wait_table_stable(tid, max_wait=6)
379
+ v, _ = eval_js(tid, "document.querySelectorAll('.vxe-body--row').length", timeout=5)
380
+ try:
381
+ rows = int(v)
382
+ except (TypeError, ValueError):
383
+ rows = 0
384
+ return (rows == n, '行数=%d' % rows)
385
+ if check.startswith('url_contains'):
386
+ kw = check.split(':', 1)[1] if ':' in check else ''
387
+ info = page_info(tid)
388
+ cur = info.get('url', '')
389
+ return (kw in cur, 'url含%s' % kw if kw in cur else 'url不含%s' % kw)
390
+ if check.startswith('input_empty:'):
391
+ field = check.split(':', 1)[1]
392
+ v, _ = eval_js(tid, "(document.querySelector(\"input[name='%s']\")||{}).value||''" % field, timeout=5)
393
+ val = str(v) if v else ''
394
+ return (val == '', '框值=%r' % val)
395
+ return True, 'assert(未知check)'
396
+
397
+
398
+ def _dig(obj, path):
399
+ """简单点路径取值: data.total / rows[0].name."""
400
+ if not path:
401
+ return obj
402
+ cur = obj
403
+ for part in re.split(r'\.', path):
404
+ m = re.match(r'([^\[]+)(\[(\d+)\])?', part)
405
+ if not m:
406
+ return None
407
+ key = m.group(1)
408
+ idx = m.group(3)
409
+ if isinstance(cur, dict) and key in cur:
410
+ cur = cur[key]
411
+ elif isinstance(cur, list) and key.isdigit() and int(key) < len(cur):
412
+ cur = cur[int(key)]
413
+ else:
414
+ return None
415
+ if idx is not None and isinstance(cur, list) and int(idx) < len(cur):
416
+ cur = cur[int(idx)]
417
+ return cur
418
+
419
+
420
+ def _short(v, n=30):
421
+ s = str(v)
422
+ return s if len(s) <= n else s[:n] + '...'
423
+
424
+
425
+ # ===== 跑一个用例 (镜像 autotest_run.py run_case) =====
426
+ def run_case(tid, case):
427
+ cid = case.get('case_id', '?')
428
+ steps = case.get('steps', [])
429
+ details = []
430
+ all_ok = True
431
+ for step in steps:
432
+ ok, detail = exec_step(tid, step)
433
+ details.append('%s:%s(%s)' % (step.get('intent', '?'), '✓' if ok else '✗', detail))
434
+ if not ok:
435
+ all_ok = False
436
+ break
437
+ return {'case_id': cid, 'title': case.get('title', ''), 'pass': all_ok,
438
+ 'expected': case.get('expected', ''), 'details': details}
439
+
440
+
441
+ # ===== 沉淀锚点 (与 autotest_run.py 一致) =====
442
+ def collect_anchors(tid):
443
+ js = '''()=>{const out=[];document.querySelectorAll('button,.ant-tabs-tab,input[placeholder],input[name],[role=tab]').forEach(el=>{const name=(el.innerText||el.getAttribute('placeholder')||el.getAttribute('name')||'').trim();const role=el.getAttribute('role')||(el.tagName==='INPUT'?'textbox':el.tagName.toLowerCase());if(name&&name.length<25&&!/^[0-9\s]+$/.test(name))out.push({role,name});});return out.slice(0,30);}'''
444
+ v, _ = eval_js(tid, js, timeout=10)
445
+ els = v if isinstance(v, list) else []
446
+ seen = set(); uniq = []
447
+ for el in els:
448
+ k = (el.get('role'), el.get('name'))
449
+ if k not in seen:
450
+ seen.add(k); uniq.append(el)
451
+ return uniq
452
+
453
+
454
+ def sediment(page_path, anchors):
455
+ if not anchors:
456
+ return 0
457
+ try:
458
+ pj = json.dumps({'url': page_path, 'elements': [
459
+ {'role': a.get('role'), 'name': a.get('name'), 'desc': a.get('name')} for a in anchors
460
+ ]}, ensure_ascii=False)
461
+ subprocess.run([sys.executable, AUTOLEARN, 'learn', '--url', page_path, '--page-json', pj],
462
+ capture_output=True, timeout=20)
463
+ return len(anchors)
464
+ except Exception:
465
+ return 0
466
+
467
+
468
+ # ===== 友好提示 + 自动回退 =====
469
+ def _print_not_ready(reason, explicit=False):
470
+ print('')
471
+ print('╔══ web-access 引擎未就绪 ═════════════════════════════════════')
472
+ print('║ 原因: %s' % str(reason)[:56])
473
+ print('║ ────────────────────────────────────────────────────────────')
474
+ print('║ 怎么修:')
475
+ print('║ 1. 打开 Chrome, 地址栏输入 chrome://inspect/#remote-debugging')
476
+ print('║ 2. 勾选 "Allow remote debugging for this browser instance"')
477
+ print('║ 3. 确保 Chrome 开着 (不用切前台, 后台即可)')
478
+ if not explicit:
479
+ print('║')
480
+ print('║ ✅ 已自动回退: 改用 playwright 引擎 (autotest_run.py)')
481
+ print('║ playwright 开箱即用, 不需要开调试开关.')
482
+ print('║ web-access 是可选增强 —— 复用已登录 Chrome, 验证码零接触.')
483
+ print('╚══════════════════════════════════════════════════════════════')
484
+ print('')
485
+
486
+
487
+ # ===== 主 =====
488
+ def main():
489
+ ap = argparse.ArgumentParser()
490
+ ap.add_argument('--task', required=True, help='任务文件 JSON')
491
+ ap.add_argument('--browser', default='chrome', help='浏览器 (chrome/edge, 默认 chrome)')
492
+ ap.add_argument('--url', default='', help='临时覆盖 base url')
493
+ ap.add_argument('--force', action='store_true', help='强制 web-access, 没装不回退')
494
+ args = ap.parse_args()
495
+
496
+ global BASE
497
+ if args.url:
498
+ BASE = args.url.rstrip('/')
499
+
500
+ task = json.load(open(args.task, encoding='utf-8'))
501
+ page_path = task['page_path']
502
+ cases = task.get('cases', [])
503
+
504
+ # 1. 启动 proxy + 连浏览器 (没开调试开关 → 友好提示 + 回退)
505
+ ok, msg = start_proxy(args.browser)
506
+ if not ok:
507
+ _print_not_ready(msg, explicit=args.force)
508
+ if args.force:
509
+ r = {'engine': 'web-access', 'error': 'WEBAccess_NOT_READY', 'case_results': [], 'page': page_path}
510
+ print(json.dumps(r, ensure_ascii=False)); sys.exit(1)
511
+ print('>>> 自动转用 playwright 引擎 ...')
512
+ runner = os.path.join(SCRIPTS, 'autotest_run.py')
513
+ rc = subprocess.run([sys.executable, runner, '--task', args.task], cwd=PROJECT).returncode
514
+ sys.exit(rc)
515
+
516
+ result = {'page': page_path, 'page_name': task.get('page_name', ''),
517
+ 'engine': 'web-access', 'case_results': [], 'anchors_count': 0, 'error': '', 'video': ''}
518
+ t0 = time.time()
519
+ tid = None
520
+ try:
521
+ # 2. 开目标页 (新 tab, 复用已登录 Chrome 的登录态)
522
+ tid = new_tab(BASE + page_path, wait=4)
523
+ if not tid:
524
+ result['error'] = 'OPEN_FAIL'; print(json.dumps(result, ensure_ascii=False)); sys.exit(1)
525
+
526
+ logged_in, cur_url = check_logged_in(tid)
527
+ if not logged_in:
528
+ result['error'] = 'NOT_LOGGED_IN: 请先在 Chrome 手动登录 %s' % BASE
529
+ print(json.dumps(result, ensure_ascii=False)); sys.exit(1)
530
+ print(' 已连接: %s' % cur_url)
531
+
532
+ # 3. 跑每个用例
533
+ # 第一个用例已在目标页 (new_tab 已导航), 无需重导; 后续用例才重导保证干净状态.
534
+ # wait_table_stable 首次等久点 (表格可能没加载), 后续缩到 4s (页面已热).
535
+ first = True
536
+ for case in cases:
537
+ if not first:
538
+ navigate(tid, BASE + page_path, wait=2)
539
+ first = False
540
+ wait_table_stable(tid, max_wait=8 if result['case_results'] == [] else 4)
541
+ cr = run_case(tid, case)
542
+ result['case_results'].append(cr)
543
+ tag = '✓' if cr['pass'] else '✗'
544
+ print(' %s %s %s' % (tag, cr['case_id'], cr['title'][:30]))
545
+ for d in cr['details']:
546
+ print(' %s' % d)
547
+
548
+ # 4. 沉淀
549
+ anc = collect_anchors(tid)
550
+ result['anchors_count'] = len(anc)
551
+ sediment(page_path, anc)
552
+
553
+ except Exception as e:
554
+ result['error'] = str(e)[:100]
555
+ finally:
556
+ if tid:
557
+ close_tab(tid)
558
+
559
+ result['duration'] = round(time.time() - t0, 1)
560
+ passed = sum(1 for c in result['case_results'] if c['pass'])
561
+ total = len(result['case_results'])
562
+ result['summary'] = '%d/%d 通过' % (passed, total)
563
+ print()
564
+ print('汇总: %s 锚点%d 耗时%ss %s' % (result['summary'], result['anchors_count'], result['duration'], result['error']))
565
+ out = args.task.replace('.json', '_webaccess.json')
566
+ json.dump(result, open(out, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)
567
+ print('结果: %s' % out)
568
+
569
+
570
+ if __name__ == '__main__':
571
+ main()
@@ -2,9 +2,15 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
4
4
  import os as _o, sys as _s
5
- _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
5
+ _f = _o.path.abspath(__file__)
6
+ for _ in range(10):
7
+ _f = _o.path.dirname(_f)
8
+ _cp = _o.path.join(_f, 'foundation')
9
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
10
+ break
6
11
  if _cp not in _s.path: _s.path.insert(0, _cp)
7
12
  from bootstrap import setup; setup()
13
+ from foundation.core.paths import get_repo_root
8
14
 
9
15
  """
10
16
  benchmark.py - QODER 流水线性能基准 (性能优化 A7)
@@ -27,7 +33,7 @@ Usage:
27
33
  """
28
34
 
29
35
  import argparse
30
- from common.paths import BUILD_CACHE_DIR
36
+ from foundation.core.paths import BUILD_CACHE_DIR
31
37
  import json
32
38
  import os
33
39
  import sys
@@ -42,7 +48,7 @@ except (AttributeError, TypeError, OSError):
42
48
  THIS_DIR = os.path.dirname(os.path.abspath(__file__)) # .qoder/scripts
43
49
  sys.path.insert(0, THIS_DIR)
44
50
  # BASE = repo root = scripts 的上两级 (.qoder/scripts -> .qoder -> repo)
45
- BASE = os.path.dirname(os.path.dirname(THIS_DIR))
51
+ BASE = get_repo_root()
46
52
 
47
53
  INDEX_DIR = os.path.join(BASE, 'data', 'index')
48
54
 
@@ -71,7 +77,7 @@ def bench_search():
71
77
  results['keyword_count'] = len(ki)
72
78
 
73
79
  # 2. 全扫描 (旧逻辑)
74
- from search_index import _match_keyword
80
+ from domain.kg.search.search_index import _match_keyword
75
81
  queries = ['insurance', 'attendance', 'salary', 'vehicle', 'user']
76
82
  def full_scan():
77
83
  for q in queries:
@@ -81,7 +87,7 @@ def bench_search():
81
87
  results['full_scan_5q_ms'] = measure(full_scan, repeat=5)[0] / 5
82
88
 
83
89
  # 3. 倒排索引构建 (首次)
84
- from common.search_engine import get_inverted_index, search_keywords_fast, build_inverted_index
90
+ from search_engine import get_inverted_index, search_keywords_fast, build_inverted_index
85
91
  # 清缓存测构建
86
92
  cache_path = os.path.join(INDEX_DIR, '.inverted-cache.json')
87
93
  if os.path.exists(cache_path):
@@ -109,7 +115,7 @@ def bench_search():
109
115
 
110
116
  def bench_cnmap():
111
117
  """CN_MAP 查询基准。"""
112
- from common.terms import CN_MAP, BUSINESS_PATH_MAP, get_cn_map_with_auto
118
+ from foundation.integrations.terms import CN_MAP, BUSINESS_PATH_MAP, get_cn_map_with_auto
113
119
  results = {}
114
120
  results['cn_map_static_count'] = len(CN_MAP)
115
121
  results['business_path_map_count'] = len(BUSINESS_PATH_MAP)
@@ -1,9 +1,15 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
3
3
  import os as _o, sys as _s
4
- _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
4
+ _f = _o.path.abspath(__file__)
5
+ for _ in range(10):
6
+ _f = _o.path.dirname(_f)
7
+ _cp = _o.path.join(_f, 'foundation')
8
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
9
+ break
5
10
  if _cp not in _s.path: _s.path.insert(0, _cp)
6
11
  from bootstrap import setup; setup()
12
+ from foundation.core.paths import get_repo_root
7
13
 
8
14
  """
9
15
  全自动登录: 获取验证码 → ddddocr识别 → 页面JS加密提交 → 保存token
@@ -26,7 +32,7 @@ CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
26
32
  RSA_PUBLIC_KEY_B64 = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDN/EkuOg1gq/t2/PUI8GpcJCsCFQgHkSHUEdk7ZPrEQYd8IM1hFkHmDJrCOQ+qUPqKHG9a5BAI44yE6FuPI9Vvqu2ZrCww/zryR7A+2n5jaKIG2rJG9KHDzT5mAVOJrlhBf8L2i7wZc8eWrNg0md0BlwEowQyVEEj9oIBlw/nqTwIDAQAB'
27
33
 
28
34
  # 保存路径
29
- SCRIPTS_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 子包→scripts/根
35
+ SCRIPTS_DIR = get_repo_root()
30
36
 
31
37
 
32
38
  def _read_developer():
@@ -1,7 +1,12 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
3
3
  import os as _o, sys as _s
4
- _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
4
+ _f = _o.path.abspath(__file__)
5
+ for _ in range(10):
6
+ _f = _o.path.dirname(_f)
7
+ _cp = _o.path.join(_f, 'foundation')
8
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
9
+ break
5
10
  if _cp not in _s.path: _s.path.insert(0, _cp)
6
11
  from bootstrap import setup; setup()
7
12
 
@@ -31,7 +36,7 @@ import yaml
31
36
 
32
37
  SCRIPTS = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 子包→scripts/根
33
38
  sys.path.insert(0, SCRIPTS)
34
- from common.paths import DATA_INDEX_DIR, PROJECT_ROOT
39
+ from foundation.core.paths import DATA_INDEX_DIR, PROJECT_ROOT
35
40
 
36
41
  INDEX_DIR = str(DATA_INDEX_DIR)
37
42
  CODE_DIR = str(PROJECT_ROOT / 'data' / 'code')