@guanlin4924/devflow-pi 0.1.94 → 0.1.95

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 (238) hide show
  1. package/README.md +4 -4
  2. package/node_modules/@playwright/test/LICENSE +202 -0
  3. package/node_modules/@playwright/test/NOTICE +5 -0
  4. package/node_modules/@playwright/test/README.md +318 -0
  5. package/node_modules/@playwright/test/cli.js +19 -0
  6. package/node_modules/@playwright/test/index.d.ts +18 -0
  7. package/node_modules/@playwright/test/index.js +17 -0
  8. package/node_modules/@playwright/test/index.mjs +18 -0
  9. package/node_modules/@playwright/test/package.json +35 -0
  10. package/node_modules/@playwright/test/reporter.d.ts +17 -0
  11. package/node_modules/@playwright/test/reporter.js +17 -0
  12. package/node_modules/@playwright/test/reporter.mjs +17 -0
  13. package/node_modules/playwright/LICENSE +202 -0
  14. package/node_modules/playwright/NOTICE +5 -0
  15. package/node_modules/playwright/README.md +318 -0
  16. package/node_modules/playwright/ThirdPartyNotices.txt +14 -0
  17. package/node_modules/playwright/cli.js +19 -0
  18. package/node_modules/playwright/index.d.ts +17 -0
  19. package/node_modules/playwright/index.js +17 -0
  20. package/node_modules/playwright/index.mjs +18 -0
  21. package/node_modules/playwright/jsx-runtime.js +42 -0
  22. package/node_modules/playwright/jsx-runtime.mjs +21 -0
  23. package/node_modules/playwright/lib/agents/agentParser.js +89 -0
  24. package/node_modules/playwright/lib/agents/copilot-setup-steps.yml +34 -0
  25. package/node_modules/playwright/lib/agents/generateAgents.js +395 -0
  26. package/node_modules/playwright/lib/agents/playwright-test-coverage.prompt.md +31 -0
  27. package/node_modules/playwright/lib/agents/playwright-test-generate.prompt.md +8 -0
  28. package/node_modules/playwright/lib/agents/playwright-test-generator.agent.md +88 -0
  29. package/node_modules/playwright/lib/agents/playwright-test-heal.prompt.md +6 -0
  30. package/node_modules/playwright/lib/agents/playwright-test-healer.agent.md +56 -0
  31. package/node_modules/playwright/lib/agents/playwright-test-plan.prompt.md +9 -0
  32. package/node_modules/playwright/lib/agents/playwright-test-planner.agent.md +74 -0
  33. package/node_modules/playwright/lib/cli/reportActions.js +78 -0
  34. package/node_modules/playwright/lib/cli/testActions.js +214 -0
  35. package/node_modules/playwright/lib/common/index.js +2923 -0
  36. package/node_modules/playwright/lib/common/index.js.txt +34 -0
  37. package/node_modules/playwright/lib/errorContext.js +129 -0
  38. package/node_modules/playwright/lib/globals.js +58 -0
  39. package/node_modules/playwright/lib/index.js +815 -0
  40. package/node_modules/playwright/lib/isomorphic.js +260 -0
  41. package/node_modules/playwright/lib/isomorphic.js.txt +9 -0
  42. package/node_modules/playwright/lib/loader/loaderProcessEntry.js +34 -0
  43. package/node_modules/playwright/lib/loader/loaderProcessEntry.js.txt +9 -0
  44. package/node_modules/playwright/lib/matchers/expect.js +13490 -0
  45. package/node_modules/playwright/lib/matchers/expect.js.LICENSE +640 -0
  46. package/node_modules/playwright/lib/matchers/expect.js.txt +67 -0
  47. package/node_modules/playwright/lib/mcp/test/browserBackend.js +135 -0
  48. package/node_modules/playwright/lib/mcp/test/generatorTools.js +122 -0
  49. package/node_modules/playwright/lib/mcp/test/plannerTools.js +150 -0
  50. package/node_modules/playwright/lib/mcp/test/seed.js +82 -0
  51. package/node_modules/playwright/lib/mcp/test/streams.js +44 -0
  52. package/node_modules/playwright/lib/mcp/test/testBackend.js +99 -0
  53. package/node_modules/playwright/lib/mcp/test/testContext.js +306 -0
  54. package/node_modules/playwright/lib/mcp/test/testTool.js +30 -0
  55. package/node_modules/playwright/lib/mcp/test/testTools.js +98 -0
  56. package/node_modules/playwright/lib/package.js +47 -0
  57. package/node_modules/playwright/lib/program.js +241 -0
  58. package/node_modules/playwright/lib/runner/index.js +8527 -0
  59. package/node_modules/playwright/lib/runner/index.js.txt +61 -0
  60. package/node_modules/playwright/lib/transform/babelBundle.js +72274 -0
  61. package/node_modules/playwright/lib/transform/babelBundle.js.LICENSE +2384 -0
  62. package/node_modules/playwright/lib/transform/babelBundle.js.txt +325 -0
  63. package/node_modules/playwright/lib/transform/esmLoader.js +8054 -0
  64. package/node_modules/playwright/lib/transform/esmLoader.js.LICENSE +355 -0
  65. package/node_modules/playwright/lib/transform/esmLoader.js.txt +60 -0
  66. package/node_modules/playwright/lib/util.js +373 -0
  67. package/node_modules/playwright/lib/worker/workerProcessEntry.js +1861 -0
  68. package/node_modules/playwright/lib/worker/workerProcessEntry.js.txt +19 -0
  69. package/node_modules/playwright/package.json +55 -0
  70. package/node_modules/playwright/test.d.ts +18 -0
  71. package/node_modules/playwright/test.js +24 -0
  72. package/node_modules/playwright/test.mjs +35 -0
  73. package/node_modules/playwright/types/test.d.ts +11074 -0
  74. package/node_modules/playwright/types/testReporter.d.ts +950 -0
  75. package/node_modules/playwright-core/LICENSE +202 -0
  76. package/node_modules/playwright-core/NOTICE +5 -0
  77. package/node_modules/playwright-core/README.md +3 -0
  78. package/node_modules/playwright-core/ThirdPartyNotices.txt +13 -0
  79. package/node_modules/playwright-core/bin/install_media_pack.ps1 +5 -0
  80. package/node_modules/playwright-core/bin/install_webkit_wsl.ps1 +33 -0
  81. package/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh +42 -0
  82. package/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh +13 -0
  83. package/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 +24 -0
  84. package/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh +42 -0
  85. package/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh +12 -0
  86. package/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 +24 -0
  87. package/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh +48 -0
  88. package/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh +11 -0
  89. package/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 +23 -0
  90. package/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh +48 -0
  91. package/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh +11 -0
  92. package/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 +23 -0
  93. package/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh +48 -0
  94. package/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh +11 -0
  95. package/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 +24 -0
  96. package/node_modules/playwright-core/browsers.json +52 -0
  97. package/node_modules/playwright-core/cli.js +21 -0
  98. package/node_modules/playwright-core/index.d.ts +17 -0
  99. package/node_modules/playwright-core/index.js +17 -0
  100. package/node_modules/playwright-core/index.mjs +28 -0
  101. package/node_modules/playwright-core/lib/bootstrap.js +88 -0
  102. package/node_modules/playwright-core/lib/coreBundle.js +75813 -0
  103. package/node_modules/playwright-core/lib/entry/cliDaemon.js +5 -0
  104. package/node_modules/playwright-core/lib/entry/dashboardApp.js +3 -0
  105. package/node_modules/playwright-core/lib/entry/mcp.js +10 -0
  106. package/node_modules/playwright-core/lib/entry/oopBrowserDownload.js +3 -0
  107. package/node_modules/playwright-core/lib/package.js +50 -0
  108. package/node_modules/playwright-core/lib/server/chromium/appIcon.png +0 -0
  109. package/node_modules/playwright-core/lib/server/electron/loader.js +116 -0
  110. package/node_modules/playwright-core/lib/serverRegistry.js +1939 -0
  111. package/node_modules/playwright-core/lib/serverRegistry.js.LICENSE +65 -0
  112. package/node_modules/playwright-core/lib/tools/cli-client/channelSessions.js +141 -0
  113. package/node_modules/playwright-core/lib/tools/cli-client/cli.js +6 -0
  114. package/node_modules/playwright-core/lib/tools/cli-client/help.json +721 -0
  115. package/node_modules/playwright-core/lib/tools/cli-client/minimist.js +128 -0
  116. package/node_modules/playwright-core/lib/tools/cli-client/output.js +351 -0
  117. package/node_modules/playwright-core/lib/tools/cli-client/program.js +418 -0
  118. package/node_modules/playwright-core/lib/tools/cli-client/registry.js +180 -0
  119. package/node_modules/playwright-core/lib/tools/cli-client/session.js +259 -0
  120. package/node_modules/playwright-core/lib/tools/dashboard/appIcon.png +0 -0
  121. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/SKILL.md +425 -0
  122. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/element-attributes.md +23 -0
  123. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/playwright-tests.md +39 -0
  124. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/request-mocking.md +87 -0
  125. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/running-code.md +241 -0
  126. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/session-management.md +225 -0
  127. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/storage-state.md +275 -0
  128. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/test-generation.md +433 -0
  129. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/tracing.md +139 -0
  130. package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/video-recording.md +143 -0
  131. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/SKILL.md +136 -0
  132. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
  133. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/migration.md +89 -0
  134. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/react.md +69 -0
  135. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/typing.md +173 -0
  136. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/vue.md +77 -0
  137. package/node_modules/playwright-core/lib/tools/skills/playwright-trace/SKILL.md +174 -0
  138. package/node_modules/playwright-core/lib/tools/utils/extension.js +112 -0
  139. package/node_modules/playwright-core/lib/tools/utils/socketConnection.js +108 -0
  140. package/node_modules/playwright-core/lib/utilsBundle.js +85357 -0
  141. package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +1890 -0
  142. package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule--QdMvsKi.css +1 -0
  143. package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule-CQ8RZGBm.js +32 -0
  144. package/node_modules/playwright-core/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
  145. package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +1 -0
  146. package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +1 -0
  147. package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +1 -0
  148. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-AwgwcYie.css +1 -0
  149. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-hRvO4yU2.js +12 -0
  150. package/node_modules/playwright-core/lib/vite/dashboard/assets/safari-na3_-uQk.svg +1 -0
  151. package/node_modules/playwright-core/lib/vite/dashboard/index.html +29 -0
  152. package/node_modules/playwright-core/lib/vite/dashboard/playwright-logo.svg +24 -0
  153. package/node_modules/playwright-core/lib/vite/htmlReport/index.html +16 -0
  154. package/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -0
  155. package/node_modules/playwright-core/lib/vite/htmlReport/report.js +32 -0
  156. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
  157. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-CVQWJZAA.js +32 -0
  158. package/node_modules/playwright-core/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
  159. package/node_modules/playwright-core/lib/vite/recorder/assets/index-BZpYJZ-P.css +1 -0
  160. package/node_modules/playwright-core/lib/vite/recorder/assets/index-hrlLqLtq.js +129 -0
  161. package/node_modules/playwright-core/lib/vite/recorder/index.html +29 -0
  162. package/node_modules/playwright-core/lib/vite/recorder/playwright-logo.svg +9 -0
  163. package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-BbkfBe3n.js +32 -0
  164. package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Ds6CBOo0.js +182 -0
  165. package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
  166. package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-DywYcAf8.js +7 -0
  167. package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
  168. package/node_modules/playwright-core/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
  169. package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.Bqk9acqE.css +1 -0
  170. package/node_modules/playwright-core/lib/vite/traceViewer/index.B4cLoZK3.js +1 -0
  171. package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
  172. package/node_modules/playwright-core/lib/vite/traceViewer/index.html +44 -0
  173. package/node_modules/playwright-core/lib/vite/traceViewer/manifest.webmanifest +16 -0
  174. package/node_modules/playwright-core/lib/vite/traceViewer/playwright-logo.svg +9 -0
  175. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
  176. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +10 -0
  177. package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +4 -0
  178. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CU5KtEkS.js +5 -0
  179. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CyMfwkXJ.css +1 -0
  180. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +18 -0
  181. package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
  182. package/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
  183. package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
  184. package/node_modules/playwright-core/lib/xdg-open +1267 -0
  185. package/node_modules/playwright-core/package.json +34 -0
  186. package/node_modules/playwright-core/types/protocol.d.ts +24645 -0
  187. package/node_modules/playwright-core/types/structs.d.ts +52 -0
  188. package/node_modules/playwright-core/types/types.d.ts +26632 -0
  189. package/package.json +12 -2
  190. package/plugins/devflow-core/extensions/devflow-core.ts +431 -7
  191. package/plugins/devflow-core/runtime/hooks/hook_common.py +269 -0
  192. package/plugins/devflow-core/runtime/hooks/index_gate.py +29 -16
  193. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/acceptance_test_runner.py +407 -71
  194. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +51 -0
  195. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/approval_pins.py +433 -0
  196. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/approval_store.py +137 -0
  197. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dsl_coverage.py +87 -0
  198. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dsl_v1.py +318 -0
  199. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/environment_profile.py +134 -0
  200. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +1 -0
  201. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_surface.py +138 -0
  202. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/playwright_adapter.py +515 -0
  203. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/tc_hash.py +132 -0
  204. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_command_policy.py +102 -8
  205. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +13 -0
  206. package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +13 -34
  207. package/plugins/devflow-core/skills/workflow-pipeline/SKILL.md +3 -1
  208. package/plugins/devflow-core/skills/workflow-pipeline/references/pipeline-flow.md +68 -140
  209. package/plugins/devflow-core/skills/workflow-project-context/scripts/validate_initialization_database.py +1 -1
  210. package/plugins/devflow-implementation/skills/workflow-execute-phase/references/execute-phase.md +3 -1
  211. package/plugins/devflow-intel/skills/workflow-code-index/scripts/code_index.py +23 -4
  212. package/plugins/devflow-quality/runtime/playwright/devflow-test-dsl-v1.schema.json +872 -0
  213. package/plugins/devflow-quality/runtime/playwright/playwright.config.mjs +46 -0
  214. package/plugins/devflow-quality/runtime/playwright/reporter.mjs +311 -0
  215. package/plugins/devflow-quality/runtime/playwright/runner.spec.mjs +1308 -0
  216. package/plugins/devflow-quality/runtime/playwright/runtime-manifest.json +11 -0
  217. package/plugins/devflow-quality/skills/workflow-acceptance-test-design/SKILL.md +3 -0
  218. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +71 -58
  219. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/api-mainflow-rules.md +20 -15
  220. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/codebase-discovery.md +3 -1
  221. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/intermediate-format.md +115 -192
  222. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/mainflow-selection.md +27 -23
  223. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/project-dsl-contract.md +78 -153
  224. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/ui-mainflow-rules.md +36 -83
  225. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/compile_dsl.py +497 -556
  226. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/export_legacy_testagent.py +647 -0
  227. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/normalize_case_payloads.py +10 -3
  228. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/validate_case_payloads.py +165 -275
  229. package/plugins/devflow-quality/skills/workflow-human-acceptance/SKILL.md +61 -0
  230. package/plugins/devflow-quality/skills/workflow-release-closeout/SKILL.md +4 -4
  231. package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/SKILL.md +15 -9
  232. package/src/approval-store.ts +340 -0
  233. package/src/constants.ts +1 -1
  234. package/src/doctor.ts +259 -2
  235. package/src/initialization-store.ts +48 -1
  236. package/src/paths.ts +12 -1
  237. package/src/project-context.ts +24 -1
  238. package/templates/omp/hook-config.json +6 -0
@@ -0,0 +1,647 @@
1
+ #!/usr/bin/env python3
2
+ """LEGACY: export case descriptions as TestAgent import payloads (project_id + dsl_json).
3
+
4
+ This exporter is NOT part of the Devflow acceptance gate. Its output uses the
5
+ old TestAgent DSL (structured_first + Midscene fallback, ai_only verification,
6
+ xpath/near_text targets) and can never be bound to ``devflow_playwright``,
7
+ approved as an execution plan, or counted as an acceptance result. The gate
8
+ uses ``compile_dsl.py`` (devflow-test-dsl/v1, one file per frozen TC).
9
+
10
+ Run it only when a developer explicitly asks for a TestAgent import file:
11
+
12
+ python3 export_legacy_testagent.py legacy-case-descriptions.json \
13
+ --output .devflow/planning/generated-cases/<feature>/legacy-testagent-import.json
14
+
15
+ The input is the legacy simplified format (``text:`` / ``placeholder:`` /
16
+ ``near:`` locators, ``verify`` steps, optional ``auth``), not the v1
17
+ ``case-descriptions.json`` consumed by ``compile_dsl.py``.
18
+ """Compile AI-friendly case descriptions into strict DSL JSON.
19
+
20
+ AI 产出简化的用例描述(locator 用 "type:value" 字符串格式),
21
+ 本脚本负责编译成符合系统 DslTarget 严格格式的 DSL JSON。
22
+ 这是硬约束层 —— 所有 target key、strategy、assertion 由脚本决定,AI 无法编造。
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import argparse
28
+ import json
29
+ import re
30
+ from copy import deepcopy
31
+ from datetime import datetime, timezone
32
+ from pathlib import Path
33
+ from typing import Any
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # 硬约束常量 — 唯一权威定义
37
+ # ---------------------------------------------------------------------------
38
+
39
+ VALID_TARGET_KEYS = {"css", "xpath", "label", "placeholder", "role", "name", "near_text", "exact"}
40
+ EXACT_LOCATOR_KEYS = {"css", "xpath", "role"} # 精确匹配,必须有 source evidence
41
+ SEMANTIC_LOCATOR_KEYS = {"label", "placeholder", "near_text"} # 语义匹配,需检查唯一性
42
+
43
+ # 高风险 label — 表格行常见操作文本,用 label 定位时几乎一定不唯一
44
+ HIGH_RISK_LABELS = {
45
+ "编辑", "删除", "查看", "详情", "启用", "禁用", "停用",
46
+ "新增", "添加", "修改", "复制", "移动", "导出", "导入", "下载",
47
+ "上传", "设置", "配置", "操作", "更多", "取消", "确定", "保存",
48
+ "提交", "重置", "刷新", "搜索", "查询", "筛选", "排序",
49
+ }
50
+
51
+ MIN_CSS_LENGTH = 10
52
+
53
+ DEFAULT_UI_STRATEGY = {"mode": "structured_first", "fallback": "midscene"}
54
+ AI_ONLY_STRATEGY = {"mode": "ai_only"}
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # Locator 字符串解析器
58
+ # ---------------------------------------------------------------------------
59
+
60
+ LOCATOR_RE = re.compile(
61
+ r"^(?P<type>text|placeholder|css|xpath|role|near)"
62
+ r":(?P<value>.+)$"
63
+ )
64
+ ROLE_LOCATOR_RE = re.compile(r"^role:(?P<role>[^:]+):(?P<name>.+)$")
65
+ SCOPE_RE = re.compile(r"^row:(?P<row>\d+)$")
66
+
67
+
68
+ def parse_locator(raw: str) -> dict[str, str]:
69
+ """Parse 'type:value' string into flat target dict.
70
+
71
+ Returns {'label': '...'} for text:, {'placeholder': '...'} for placeholder:, etc.
72
+ Raises ValueError on unrecognized format.
73
+ """
74
+ raw = raw.strip()
75
+
76
+ # role:button:确定
77
+ m = ROLE_LOCATOR_RE.match(raw)
78
+ if m:
79
+ return {"role": m.group("role"), "name": m.group("name")}
80
+
81
+ m = LOCATOR_RE.match(raw)
82
+ if not m:
83
+ raise ValueError(
84
+ f"Invalid locator format: '{raw}'. "
85
+ f"Expected: text:<text> | placeholder:<text> | css:<selector> | "
86
+ f"xpath:<expr> | role:<role>:<name> | near:<text>"
87
+ )
88
+
89
+ loc_type = m.group("type")
90
+ value = m.group("value").strip()
91
+
92
+ if not value:
93
+ raise ValueError(f"Locator '{raw}' has empty value.")
94
+
95
+ if loc_type == "text":
96
+ return {"label": value}
97
+ if loc_type == "placeholder":
98
+ return {"placeholder": value}
99
+ if loc_type == "css":
100
+ return {"css": value}
101
+ if loc_type == "xpath":
102
+ return {"xpath": value}
103
+ if loc_type == "near":
104
+ return {"near_text": value}
105
+
106
+ raise ValueError(f"Unknown locator type: {loc_type}")
107
+
108
+
109
+ # ---------------------------------------------------------------------------
110
+ # CSS 编造检测 — 零容忍
111
+ # ---------------------------------------------------------------------------
112
+
113
+ # 明显是 AI 编造的 CSS class 特征
114
+ HALLUCINATED_CSS_PATTERNS: list[tuple[str, str]] = [
115
+ # 包含 % 的伪 Tailwind class(w-100%, h-50% 等)
116
+ (r"\b[a-z]+-\d+%", "contains invented utility class with % (e.g. w-100%)"),
117
+ # 裸的百分比值
118
+ (r"%", "contains '%' — not valid in CSS selector without escaping"),
119
+ # 纯表单容器通用类,不指定具体字段
120
+ (r"^\.el-form-item(__content)?$", "matches all form items, not a specific field"),
121
+ # 明显编造的随机 class 名模式
122
+ (r"\b[a-z]{1,3}-\d{3,}\b", "looks like a made-up class (random letters+numbers)"),
123
+ ]
124
+
125
+ # fill/select 动作在非 placeholder 情况下,CSS 禁止匹配纯容器类
126
+ GENERIC_FORM_CLASSES = [
127
+ ".el-form-item",
128
+ ".el-form-item__content",
129
+ ".el-form-item__label",
130
+ ".ant-form-item",
131
+ ".ant-form-item-control",
132
+ ".form-item",
133
+ ".form-group",
134
+ ]
135
+
136
+
137
+ def _validate_css_not_hallucinated(css_value: str, step_name: str) -> None:
138
+ """Reject CSS selectors that show signs of AI hallucination. Hard gate."""
139
+ errors: list[str] = []
140
+
141
+ # Check against hallucination patterns
142
+ for pattern, description in HALLUCINATED_CSS_PATTERNS:
143
+ if re.search(pattern, css_value):
144
+ errors.append(
145
+ f"CSS selector '{css_value}' rejected: {description}. "
146
+ f"Step: {step_name}"
147
+ )
148
+
149
+ # Check if CSS is purely generic form classes (no specific target)
150
+ stripped = css_value.strip()
151
+ parts = [p.strip() for p in stripped.split() if p.strip()]
152
+ if parts and all(
153
+ any(p == g or p.startswith(g) for g in GENERIC_FORM_CLASSES)
154
+ for p in parts
155
+ ):
156
+ errors.append(
157
+ f"CSS selector '{css_value}' only matches generic form containers, "
158
+ f"not a specific field. Use 'near:<label文本>' instead. "
159
+ f"Step: {step_name}"
160
+ )
161
+
162
+ # Too short = probably made up
163
+ if len(css_value) < MIN_CSS_LENGTH:
164
+ errors.append(
165
+ f"CSS selector '{css_value}' is too generic ({len(css_value)} chars, min {MIN_CSS_LENGTH}). "
166
+ f"Step: {step_name}"
167
+ )
168
+
169
+ if errors:
170
+ raise ValueError("CSS HALLUCINATION DETECTED:\n" + "\n".join(f" - {e}" for e in errors))
171
+
172
+
173
+ # ---------------------------------------------------------------------------
174
+ # Fill 动作 locator 硬决策链
175
+ # ---------------------------------------------------------------------------
176
+
177
+ FILL_ALLOWED_LOCATORS = {"placeholder", "near"} # fill 只能用这两个
178
+
179
+
180
+ def _validate_fill_locator(step_name: str, locator_raw: str) -> None:
181
+ """Hard decision chain for fill actions: placeholder → near → ai_only. CSS/xpath forbidden."""
182
+ if not locator_raw:
183
+ raise ValueError(
184
+ f"Fill step '{step_name}' has no locator. "
185
+ f"Use 'placeholder:<text>', 'near:<label文本>', or change action to 'act' with ai_only."
186
+ )
187
+
188
+ loc_type = locator_raw.split(":", 1)[0].strip()
189
+ if loc_type not in FILL_ALLOWED_LOCATORS:
190
+ raise ValueError(
191
+ f"Fill step '{step_name}' uses locator type '{loc_type}', which is forbidden for fill actions. "
192
+ f"Fill hard chain: placeholder → near → ai_only. "
193
+ f"Got '{locator_raw}'. "
194
+ f"Use 'placeholder:<text>' if the field has a placeholder, "
195
+ f"'near:<label文本>' if there's a label next to the field, "
196
+ f"or change action to 'act' with ai_only strategy."
197
+ )
198
+
199
+
200
+ # ---------------------------------------------------------------------------
201
+ # Target 编译 — 硬约束规则
202
+ # ---------------------------------------------------------------------------
203
+
204
+ def compile_target(
205
+ locator_raw: str,
206
+ scope: str | None = None,
207
+ step_name: str = "",
208
+ ) -> dict[str, Any]:
209
+ """Compile a locator string + scope into a valid DslTarget dict.
210
+
211
+ Rules (in priority order):
212
+ 1. text:<unique_button> + page scope → label + exact
213
+ 2. text:<common_action> + row:N scope → label + css row selector
214
+ 3. text:<text> + dialog scope → label (dialog内默认唯一)
215
+ 4. placeholder:<text> → placeholder
216
+ 5. css:<selector> → css (validated for specificity)
217
+ 6. role:<role>:<name> → role + name
218
+ 7. near:<text> → near_text
219
+ 8. xpath:<expr> → xpath (last resort)
220
+ """
221
+ target = parse_locator(locator_raw)
222
+ errors: list[str] = []
223
+
224
+ # text: → label
225
+ if "label" in target:
226
+ label_value = target["label"]
227
+
228
+ # Determine scope
229
+ if scope:
230
+ scope_match = SCOPE_RE.match(scope)
231
+ if scope_match:
232
+ # row scope: label + css row selector
233
+ row_n = int(scope_match.group("row"))
234
+ target["css"] = (
235
+ f".el-table__body tr:nth-child({row_n}) "
236
+ f"td:last-child .el-button--small"
237
+ )
238
+ elif scope == "dialog":
239
+ # dialog scope: label within dialog, no extra css needed
240
+ pass
241
+ # other scopes just use label as-is
242
+
243
+ # exact match for page-unique items
244
+ if label_value not in HIGH_RISK_LABELS:
245
+ target["exact"] = True
246
+
247
+ # High-risk label without row scope → warn but don't block
248
+ if label_value in HIGH_RISK_LABELS and not (scope and SCOPE_RE.match(scope)):
249
+ # Still produce valid target, but record warning
250
+ pass
251
+
252
+ # css: → hard hallucination check
253
+ if "css" in target:
254
+ css_value = target["css"]
255
+ _validate_css_not_hallucinated(css_value, step_name)
256
+
257
+ # role: → ensure name is present (already handled by parse_locator)
258
+
259
+ if errors:
260
+ raise ValueError("\n".join(errors))
261
+
262
+ return target
263
+
264
+
265
+ # ---------------------------------------------------------------------------
266
+ # Strategy 决策 — 硬约束
267
+ # ---------------------------------------------------------------------------
268
+
269
+ VERIFICATION_ACTIONS = {"verify", "assert_text", "assert_visible", "assert_url_contains", "assert_any_visible"}
270
+
271
+
272
+ def compile_strategy(action: str) -> dict[str, Any]:
273
+ """Decide strategy based on action type. Hard rule: verification → ai_only."""
274
+ if action in VERIFICATION_ACTIONS:
275
+ return deepcopy(AI_ONLY_STRATEGY)
276
+ if action == "act":
277
+ return deepcopy(AI_ONLY_STRATEGY)
278
+ return deepcopy(DEFAULT_UI_STRATEGY)
279
+
280
+
281
+ # ---------------------------------------------------------------------------
282
+ # Step 编译
283
+ # ---------------------------------------------------------------------------
284
+
285
+ def compile_step(step: dict[str, Any], index: int) -> dict[str, Any]:
286
+ """Compile one simplified step into strict DSL step."""
287
+ name = str(step.get("name") or f"Step {index}")
288
+ action = str(step.get("action") or "").lower().strip()
289
+
290
+ compiled: dict[str, Any] = {
291
+ "id": f"s{index:02d}",
292
+ "name": name,
293
+ "depends_on": [],
294
+ "timeout_ms": int(step.get("timeout_ms", 30_000)),
295
+ "on_failure": step.get("on_failure", "stop"),
296
+ }
297
+
298
+ # --- Navigate → web/goto ---
299
+ if action == "navigate":
300
+ compiled["driver"] = "web"
301
+ compiled["action"] = "goto"
302
+ compiled["params"] = {"url": step.get("url", "")}
303
+ return compiled
304
+
305
+ # --- Wait ---
306
+ if action == "wait":
307
+ compiled["driver"] = "web"
308
+ compiled["action"] = "wait"
309
+ compiled["params"] = {"wait_for": step.get("wait_for", "load")}
310
+ compiled["timeout_ms"] = int(step.get("timeout_ms", 15_000))
311
+ return compiled
312
+
313
+ # --- Inject token ---
314
+ if action == "inject_token":
315
+ compiled["driver"] = "web"
316
+ compiled["action"] = "evaluate_js"
317
+ compiled["params"] = {"script": step.get("script", "")}
318
+ return compiled
319
+
320
+ # --- API step ---
321
+ if action in {"get", "post", "put", "patch", "delete"}:
322
+ compiled["driver"] = "api"
323
+ compiled["action"] = action
324
+ compiled["params"] = deepcopy(step.get("params", {}))
325
+ compiled["assertions"] = _compile_api_assertions(step.get("assertions", []))
326
+ compiled["extract_vars"] = deepcopy(step.get("extract_vars", {}))
327
+ return compiled
328
+
329
+ # --- UI action steps ---
330
+ if action in {"click", "fill", "select", "hover"}:
331
+ compiled["driver"] = "ui"
332
+ compiled["action"] = action
333
+ locator_raw = step.get("locator", "")
334
+ if locator_raw:
335
+ compiled["target"] = compile_target(
336
+ locator_raw,
337
+ scope=step.get("scope"),
338
+ step_name=name,
339
+ )
340
+ compiled["strategy"] = compile_strategy(action)
341
+ if action == "fill":
342
+ compiled["params"] = {"value": step.get("value", "")}
343
+ _validate_fill_locator(name, locator_raw)
344
+ return compiled
345
+
346
+ # --- UI verification step ---
347
+ if action == "verify":
348
+ compiled["driver"] = "ui"
349
+ compiled["action"] = "act"
350
+ locator_raw = step.get("locator", "")
351
+ if locator_raw:
352
+ compiled["target"] = compile_target(locator_raw, scope=step.get("scope"), step_name=name)
353
+ compiled["strategy"] = deepcopy(AI_ONLY_STRATEGY)
354
+ expect = step.get("expect", "")
355
+ compiled["prompt"] = _build_verify_prompt(name, expect, step)
356
+ compiled["timeout_ms"] = int(step.get("timeout_ms", 15_000))
357
+ return compiled
358
+
359
+ # --- AI act step (with prompt) ---
360
+ if action == "act":
361
+ compiled["driver"] = "ui"
362
+ compiled["action"] = "act"
363
+ locator_raw = step.get("locator", "")
364
+ if locator_raw:
365
+ compiled["target"] = compile_target(locator_raw, scope=step.get("scope"), step_name=name)
366
+ compiled["strategy"] = deepcopy(AI_ONLY_STRATEGY)
367
+ compiled["prompt"] = step.get("prompt", "")
368
+ compiled["timeout_ms"] = int(step.get("timeout_ms", 15_000))
369
+ return compiled
370
+
371
+ # Fallback
372
+ raise ValueError(f"Unknown action '{action}' in step '{name}'")
373
+
374
+
375
+ def _compile_api_assertions(raw_assertions: list[dict[str, Any]]) -> list[dict[str, Any]]:
376
+ """Compile simplified API assertions into strict format."""
377
+ compiled: list[dict[str, Any]] = []
378
+ for a in raw_assertions:
379
+ atype = str(a.get("type", "status_code"))
380
+ compiled.append({
381
+ "type": atype,
382
+ "expected": a.get("expected"),
383
+ "path": a.get("path"),
384
+ })
385
+ return compiled
386
+
387
+
388
+ def _build_verify_prompt(step_name: str, expect: str, step: dict[str, Any]) -> str:
389
+ """Build AI prompt for verification step."""
390
+ if step.get("prompt"):
391
+ return step["prompt"]
392
+ if expect.startswith("text:"):
393
+ text = expect[5:]
394
+ return f"检查页面是否出现'{text}'的提示消息,确认操作已成功完成"
395
+ if expect == "dialog_visible":
396
+ return "检查对话框是否已打开并显示"
397
+ if expect == "dialog_closed":
398
+ return "检查对话框是否已关闭"
399
+ return f"验证:{expect}"
400
+
401
+
402
+ # ---------------------------------------------------------------------------
403
+ # Auth setup 编译
404
+ # ---------------------------------------------------------------------------
405
+
406
+ def compile_auth_steps(auth: dict[str, Any] | None, base_url: str = "${base_url}") -> list[dict[str, Any]]:
407
+ """Compile auth config into API login + web inject steps."""
408
+ if not auth:
409
+ return []
410
+
411
+ method = str(auth.get("method", "")).lower()
412
+ steps: list[dict[str, Any]] = []
413
+
414
+ if method == "api_login":
415
+ login_url = auth.get("login_url", f"{base_url}/auth/login_by_id")
416
+ login_body = auth.get("login_body", {})
417
+ token_path = auth.get("token_path", "headers.X-Jwt-Token")
418
+ app_id = auth.get("app_id")
419
+
420
+ steps.append({
421
+ "id": "api_auth_login",
422
+ "name": "获取JWT令牌",
423
+ "driver": "api",
424
+ "action": "post",
425
+ "params": {"url": login_url, "json": login_body},
426
+ "assertions": [
427
+ {"type": "status_code", "expected": 200},
428
+ {"type": "json_path_exists", "path": token_path},
429
+ ],
430
+ "extract_vars": {"jwt_token": {"path": token_path, "source": "jsonpath"}},
431
+ "depends_on": [],
432
+ "timeout_ms": 15_000,
433
+ "on_failure": "stop",
434
+ })
435
+
436
+ if app_id:
437
+ steps.append({
438
+ "id": "api_app_token",
439
+ "name": "获取应用访问Token",
440
+ "driver": "api",
441
+ "action": "post",
442
+ "params": {
443
+ "url": auth.get("app_token_url", f"{base_url}/admin/app/access"),
444
+ "json": {"id": app_id},
445
+ "headers": {"Authorization": "Bearer ${jwt_token}"},
446
+ },
447
+ "assertions": [{"type": "status_code", "expected": 200}],
448
+ "extract_vars": {"token_url": {"path": "body.data.url", "source": "jsonpath"}},
449
+ "depends_on": ["api_auth_login"],
450
+ "timeout_ms": 15_000,
451
+ "on_failure": "stop",
452
+ })
453
+
454
+ steps.append({
455
+ "id": "web_auth_inject",
456
+ "name": "Token注入",
457
+ "driver": "web",
458
+ "action": "goto",
459
+ "params": {"url": "${token_url}"},
460
+ "depends_on": ["api_app_token"],
461
+ "timeout_ms": 30_000,
462
+ "on_failure": "stop",
463
+ })
464
+
465
+ return steps
466
+
467
+
468
+ # ---------------------------------------------------------------------------
469
+ # Main compile entry
470
+ # ---------------------------------------------------------------------------
471
+
472
+ def _check_terminology(steps: list[dict[str, Any]]) -> list[str]:
473
+ """Check for terminology hallucination in step names vs locator values."""
474
+ # Commonly hallucinated term pairs: (AI-invented, source-code-actual)
475
+ TERM_PAIRS: list[tuple[str, str]] = [
476
+ ("搜索", "查询"),
477
+ ("搜索", "筛选"),
478
+ ("新增", "添加"),
479
+ ("编辑", "修改"),
480
+ ("删除", "移除"),
481
+ ("保存", "提交"),
482
+ ("关闭", "取消"),
483
+ ]
484
+ warnings: list[str] = []
485
+ for step in steps:
486
+ name = str(step.get("name", ""))
487
+ locator_raw = str(step.get("_locator_raw", ""))
488
+ for ai_term, src_term in TERM_PAIRS:
489
+ if ai_term in name and src_term in locator_raw:
490
+ warnings.append(
491
+ f"Step '{name}' uses '{ai_term}' but locator contains '{src_term}'. "
492
+ f"Verify the button text in source code — use the exact source text."
493
+ )
494
+ return warnings
495
+
496
+
497
+ def compile_case(case_desc: dict[str, Any]) -> dict[str, Any]:
498
+ """Compile one simplified case description into strict DSL."""
499
+ name = str(case_desc.get("name") or "Unnamed Case")
500
+ tags = list(case_desc.get("tags") or ["ui-mainflow"])
501
+ variables = dict(case_desc.get("variables") or {})
502
+ base_url = variables.get("base_url", "${base_url}")
503
+
504
+ # Compile auth
505
+ auth_steps = compile_auth_steps(case_desc.get("auth"), base_url)
506
+
507
+ # Compile business steps
508
+ raw_steps = list(case_desc.get("steps") or [])
509
+ business_steps: list[dict[str, Any]] = []
510
+ for i, raw_step in enumerate(raw_steps):
511
+ compiled_step = compile_step(raw_step, i + 1)
512
+ # Preserve original locator string for terminology checking
513
+ compiled_step["_locator_raw"] = raw_step.get("locator", "")
514
+ business_steps.append(compiled_step)
515
+
516
+ # Terminology check (warning, not error)
517
+ term_warnings = _check_terminology(business_steps)
518
+ if term_warnings:
519
+ for w in term_warnings:
520
+ print(f"TERMINOLOGY WARNING: {w}", file=__import__("sys").stderr)
521
+
522
+ # Merge: auth steps first, then business steps
523
+ all_steps = auth_steps + business_steps
524
+
525
+ # Wire dependencies: auth steps are already wired; business steps depend on previous
526
+ first_business_idx = len(auth_steps)
527
+ for i, step in enumerate(all_steps):
528
+ if i >= first_business_idx and i > 0:
529
+ prev_id = all_steps[i - 1]["id"]
530
+ if not step.get("depends_on"):
531
+ step["depends_on"] = [prev_id]
532
+
533
+ dsl = {
534
+ "name": name,
535
+ "description": case_desc.get("description", ""),
536
+ "tags": tags,
537
+ "variables": variables,
538
+ "steps": all_steps,
539
+ }
540
+
541
+ return {
542
+ "project_id": int(case_desc.get("project_id", 1)),
543
+ "name": name,
544
+ "description": case_desc.get("description", ""),
545
+ "tags": tags,
546
+ "dsl_json": dsl,
547
+ }
548
+
549
+
550
+ def compile_all(payload: dict[str, Any], frozen_mainflow_tc_ids: set[str] | None = None) -> dict[str, Any]:
551
+ """Compile all cases in the payload."""
552
+ metadata = dict(payload.get("metadata") or {})
553
+ descriptions = list(payload.get("cases", []))
554
+ tc_ids: list[str] = []
555
+ if frozen_mainflow_tc_ids is not None:
556
+ for index, description in enumerate(descriptions):
557
+ tc_id = description.get("tc_id") if isinstance(description, dict) else None
558
+ if not isinstance(tc_id, str) or tc_id not in frozen_mainflow_tc_ids:
559
+ raise ValueError(f"cases[{index}].tc_id must reference a frozen api/page TC that is positive")
560
+ tc_ids.append(tc_id)
561
+ if len(tc_ids) != len(set(tc_ids)):
562
+ raise ValueError("each frozen positive api/page TC may generate at most one mainflow case")
563
+ cases = [compile_case(c) for c in descriptions]
564
+
565
+ return {
566
+ "metadata": {
567
+ **metadata,
568
+ **({"frozen_tc_ids": tc_ids} if frozen_mainflow_tc_ids is not None else {}),
569
+ "compiled_at": datetime.now(timezone.utc).isoformat(),
570
+ "compiled_by": "export_legacy_testagent.py (legacy, not an acceptance gate)",
571
+ "legacy_export": True,
572
+ },
573
+ "cases": cases,
574
+ }
575
+
576
+
577
+ def frozen_mainflow_tc_ids_from_cwd(cwd: Path) -> set[str] | None:
578
+ """Return the frozen API/page suite for new workflows, else preserve legacy compilation."""
579
+ state_path = cwd / ".devflow/planning" / "STATE.md"
580
+ if not state_path.is_file():
581
+ return None
582
+ match = re.search(r"^current_feature:\s*['\"]?([^'\"\n]+)", state_path.read_text(encoding="utf-8"), re.MULTILINE)
583
+ if not match:
584
+ return None
585
+ suite_path = cwd / ".devflow/planning" / "requirements" / match.group(1).strip() / "tests.json"
586
+ if not suite_path.is_file():
587
+ return None
588
+ suite = json.loads(suite_path.read_text(encoding="utf-8"))
589
+ if suite.get("frozen") is not True:
590
+ raise ValueError("current TC suite exists but is not frozen and fresh")
591
+ eligible = {
592
+ node.get("id")
593
+ for node in suite.get("nodes", [])
594
+ if isinstance(node, dict)
595
+ and node.get("kind") == "test_case"
596
+ and node.get("status") == "confirmed"
597
+ and isinstance(node.get("data"), dict)
598
+ and node["data"].get("test_type") in {"api", "page"}
599
+ # A generated API TC with an error-code oracle is a negative/exception
600
+ # test. It belongs to run-acceptance-tests, never to the positive
601
+ # mainflow importer even though its broad test_type is "api".
602
+ and not (
603
+ isinstance(node["data"].get("generated_case_key"), str)
604
+ and isinstance(node["data"].get("execution"), dict)
605
+ and isinstance(node["data"]["execution"].get("expect"), dict)
606
+ and "error_code" in node["data"]["execution"]["expect"]
607
+ )
608
+ }
609
+ return {value for value in eligible if isinstance(value, str)}
610
+
611
+
612
+ # ---------------------------------------------------------------------------
613
+ # CLI
614
+ # ---------------------------------------------------------------------------
615
+
616
+ def main() -> int:
617
+ parser = argparse.ArgumentParser(
618
+ allow_abbrev=False,
619
+ description="LEGACY (not a gate): export case descriptions as TestAgent import payloads."
620
+ )
621
+ parser.add_argument("input", help="Legacy simplified case descriptions JSON")
622
+ parser.add_argument("--output", required=True, help="Output path for the legacy TestAgent import payload")
623
+ args = parser.parse_args()
624
+
625
+ input_path = Path(args.input)
626
+ output_path = Path(args.output)
627
+
628
+ raw = json.loads(input_path.read_text(encoding="utf-8"))
629
+
630
+ # Accept both array and {cases: [...]} format
631
+ if isinstance(raw, list):
632
+ raw = {"metadata": {}, "cases": raw}
633
+
634
+ try:
635
+ result = compile_all(raw, frozen_mainflow_tc_ids_from_cwd(Path.cwd()))
636
+ except ValueError as exc:
637
+ print(f"COMPILE ERROR: {exc}", file=__import__("sys").stderr)
638
+ return 1
639
+
640
+ output_path.parent.mkdir(parents=True, exist_ok=True)
641
+ output_path.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
642
+ print(json.dumps({"output": str(output_path), "case_count": len(result["cases"])}, ensure_ascii=False))
643
+ return 0
644
+
645
+
646
+ if __name__ == "__main__":
647
+ raise SystemExit(main())
@@ -1,5 +1,12 @@
1
1
  #!/usr/bin/env python3
2
- """Normalize draft mainflow case payloads into import payloads."""
2
+ """LEGACY: normalize draft TestAgent case payloads into a TestAgent import payload.
3
+
4
+ Not part of the Devflow acceptance gate and not used by the workflow. The
5
+ output (``dsl_json`` with structured_first/Midscene strategies) is the legacy
6
+ TestAgent import format: it can never be bound to ``devflow_playwright``,
7
+ approved as an execution plan, or counted as an acceptance result. The gate
8
+ DSL is produced by ``compile_dsl.py`` (devflow-test-dsl/v1).
9
+ """
3
10
 
4
11
  from __future__ import annotations
5
12
 
@@ -140,9 +147,9 @@ def normalize_case(case: dict[str, Any], metadata: dict[str, Any]) -> tuple[dict
140
147
 
141
148
 
142
149
  def main() -> int:
143
- parser = argparse.ArgumentParser()
150
+ parser = argparse.ArgumentParser(allow_abbrev=False)
144
151
  parser.add_argument("input", help="legacy raw case payload JSON")
145
- parser.add_argument("--output", required=True, help="case-import-payloads.json")
152
+ parser.add_argument("--output", required=True, help="legacy TestAgent import payload path")
146
153
  args = parser.parse_args()
147
154
 
148
155
  draft_path = Path(args.input)