@guanlin4924/devflow-pi 0.1.93 → 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 (369) hide show
  1. package/README.md +10 -10
  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 +518 -40
  191. package/plugins/devflow-core/runtime/hooks/context_sync_gate.py +3 -3
  192. package/plugins/devflow-core/runtime/hooks/hook_common.py +334 -11
  193. package/plugins/devflow-core/runtime/hooks/index_gate.py +29 -16
  194. package/plugins/devflow-core/runtime/hooks/start.py +13 -13
  195. package/plugins/devflow-core/skills/wireframe-proposal/SKILL.md +2 -2
  196. package/plugins/devflow-core/skills/wireframe-validate/SKILL.md +4 -4
  197. package/plugins/devflow-core/skills/workflow-orchestrator/SKILL.md +19 -19
  198. package/plugins/devflow-core/skills/workflow-orchestrator/agents/openai.yaml +2 -2
  199. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/acceptance_test_runner.py +412 -76
  200. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +60 -9
  201. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/api_test_matrix.py +2 -2
  202. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/approval_pins.py +433 -0
  203. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/approval_store.py +137 -0
  204. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_assessment.rb +1 -1
  205. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_brief.rb +1 -1
  206. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_feasibility.rb +1 -1
  207. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_invalidation.rb +5 -5
  208. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_model_commit.rb +4 -4
  209. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_model_validate.py +1 -1
  210. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_source_resolver.rb +4 -4
  211. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/checker_adapter.rb +7 -7
  212. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/control_io.rb +51 -7
  213. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/cross_track_consistency_check.rb +1 -1
  214. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dsl_coverage.py +87 -0
  215. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dsl_v1.py +318 -0
  216. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/environment_profile.py +134 -0
  217. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +7 -6
  218. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_surface.py +138 -0
  219. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/gate_runtime.rb +3 -3
  220. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_state.rb +5 -5
  221. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_workflow.rb +5 -5
  222. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/inspect_state.rb +13 -13
  223. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/machine_evidence.rb +3 -3
  224. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/manage_lessons.rb +7 -7
  225. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/parallel_recover.rb +2 -2
  226. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/parallel_run.rb +3 -3
  227. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/phase_ownership.rb +6 -6
  228. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/playwright_adapter.py +515 -0
  229. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/repair_control_plane.rb +1 -1
  230. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/requirement_coverage.rb +1 -1
  231. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/requirement_graph.rb +3 -3
  232. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/review_preflight.py +3 -3
  233. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/review_run.py +18 -18
  234. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/runtime_context_collector.rb +3 -3
  235. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/session_rollover.rb +4 -4
  236. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/static_runtime_context_collector.rb +2 -2
  237. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/sync_artifacts.rb +5 -5
  238. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/tc_hash.py +132 -0
  239. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/tdd_evidence.py +3 -3
  240. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_command_policy.py +104 -10
  241. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_task_binding_check.py +3 -3
  242. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/update_triage_route.rb +3 -3
  243. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +19 -6
  244. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/wireframe_commit.rb +5 -5
  245. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/wireframe_validate.py +1 -1
  246. package/plugins/devflow-core/skills/workflow-orchestrator/templates/acceptance-remediation.yaml +12 -12
  247. package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +201 -222
  248. package/plugins/devflow-core/skills/workflow-pipeline/SKILL.md +6 -4
  249. package/plugins/devflow-core/skills/workflow-pipeline/references/pipeline-flow.md +68 -140
  250. package/plugins/devflow-core/skills/workflow-project-context/SKILL.md +14 -14
  251. package/plugins/devflow-core/skills/workflow-project-context/assets/agents-md-template.md +7 -7
  252. package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-backend-spec-template.md +4 -4
  253. package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-frontend-spec-template.md +4 -4
  254. package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-navigation-template.md +1 -1
  255. package/plugins/devflow-core/skills/workflow-project-context/assets/context-domain-index-template.md +4 -4
  256. package/plugins/devflow-core/skills/workflow-project-context/assets/context-spec-index-template.md +10 -10
  257. package/plugins/devflow-core/skills/workflow-project-context/assets/execution-contract-template.md +2 -2
  258. package/plugins/devflow-core/skills/workflow-project-context/assets/navigation-index-template.md +18 -18
  259. package/plugins/devflow-core/skills/workflow-project-context/assets/project-initialization-template.md +32 -32
  260. package/plugins/devflow-core/skills/workflow-project-context/assets/rules-index-template.md +30 -30
  261. package/plugins/devflow-core/skills/workflow-project-context/assets/state-template.md +7 -7
  262. package/plugins/devflow-core/skills/workflow-project-context/references/navigation-guide.md +37 -37
  263. package/plugins/devflow-core/skills/workflow-project-context/scripts/validate_initialization.py +16 -16
  264. package/plugins/devflow-core/skills/workflow-project-context/scripts/validate_initialization_database.py +1 -1
  265. package/plugins/devflow-design/skills/workflow-api-call-guide/SKILL.md +4 -4
  266. package/plugins/devflow-design/skills/workflow-api-contract/SKILL.md +8 -8
  267. package/plugins/devflow-design/skills/workflow-api-supplement/SKILL.md +5 -5
  268. package/plugins/devflow-design/skills/workflow-architecture-feasibility/SKILL.md +1 -1
  269. package/plugins/devflow-design/skills/workflow-backend-tech-spec/SKILL.md +39 -39
  270. package/plugins/devflow-design/skills/workflow-backend-tech-spec/assets/backend-spec-template.md +10 -10
  271. package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/affected-module-exploration.md +2 -2
  272. package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/code-explore-guide.md +1 -1
  273. package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/project-rule-contract.md +20 -20
  274. package/plugins/devflow-design/skills/workflow-design-rule-pack/SKILL.md +8 -8
  275. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/index.md +3 -3
  276. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/accessibility.md +2 -2
  277. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/component-library.md +2 -2
  278. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/design-systems.md +1 -1
  279. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/responsive-grid.md +2 -2
  280. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/stack-guidelines.md +6 -6
  281. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/ux-patterns.md +2 -2
  282. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tokens/project-tokens.md +1 -1
  283. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/SKILL.md +12 -12
  284. package/plugins/devflow-design/skills/workflow-design-rule-pack/scripts/install.py +2 -2
  285. package/plugins/devflow-design/skills/workflow-feature-breakdown/SKILL.md +30 -30
  286. package/plugins/devflow-design/skills/workflow-feature-breakdown/assets/output-template.md +2 -2
  287. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/SKILL.md +41 -41
  288. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/assets/frontend-spec-template.md +15 -15
  289. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/affected-module-exploration.md +2 -2
  290. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/project-rule-contract.md +25 -25
  291. package/plugins/devflow-design/skills/workflow-integration-verify/SKILL.md +13 -13
  292. package/plugins/devflow-design/skills/workflow-ui-spec/SKILL.md +23 -23
  293. package/plugins/devflow-design/skills/workflow-ui-spec/assets/ui-spec-template.md +3 -3
  294. package/plugins/devflow-implementation/agents/implementation-worker.md +2 -2
  295. package/plugins/devflow-implementation/skills/workflow-bug-fix/SKILL.md +9 -9
  296. package/plugins/devflow-implementation/skills/workflow-bug-fix/references/lesson-format.md +2 -2
  297. package/plugins/devflow-implementation/skills/workflow-bug-fix/references/root-cause-ladder.md +1 -1
  298. package/plugins/devflow-implementation/skills/workflow-change-plan/SKILL.md +17 -17
  299. package/plugins/devflow-implementation/skills/workflow-change-plan/references/code-conventions.md +1 -1
  300. package/plugins/devflow-implementation/skills/workflow-change-plan/references/output-template.md +3 -3
  301. package/plugins/devflow-implementation/skills/workflow-change-plan/references/project-rule-contract.md +16 -16
  302. package/plugins/devflow-implementation/skills/workflow-execute-phase/SKILL.md +38 -38
  303. package/plugins/devflow-implementation/skills/workflow-execute-phase/references/execute-phase.md +4 -2
  304. package/plugins/devflow-intel/agents/project-scout.md +1 -1
  305. package/plugins/devflow-intel/extensions/devflow-intel.ts +20 -19
  306. package/plugins/devflow-intel/skills/code-index-query/SKILL.md +6 -6
  307. package/plugins/devflow-intel/skills/workflow-code-index/SKILL.md +12 -12
  308. package/plugins/devflow-intel/skills/workflow-code-index/references/discovery.md +2 -2
  309. package/plugins/devflow-intel/skills/workflow-code-index/references/query-guide.md +1 -1
  310. package/plugins/devflow-intel/skills/workflow-code-index/scripts/code_index.py +31 -12
  311. package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/discovery.py +2 -2
  312. package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/scanner.py +4 -1
  313. package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/project_binding_check.py +4 -4
  314. package/plugins/devflow-intel/skills/workflow-context-sync/SKILL.md +14 -14
  315. package/plugins/devflow-intel/skills/workflow-context-sync/scripts/context_sync_cursor.py +2 -2
  316. package/plugins/devflow-intel/skills/workflow-update-navigation/SKILL.md +26 -26
  317. package/plugins/devflow-intel/skills/workflow-update-navigation/references/incremental-update-guide.md +2 -2
  318. package/plugins/devflow-quality/runtime/playwright/devflow-test-dsl-v1.schema.json +872 -0
  319. package/plugins/devflow-quality/runtime/playwright/playwright.config.mjs +46 -0
  320. package/plugins/devflow-quality/runtime/playwright/reporter.mjs +311 -0
  321. package/plugins/devflow-quality/runtime/playwright/runner.spec.mjs +1308 -0
  322. package/plugins/devflow-quality/runtime/playwright/runtime-manifest.json +11 -0
  323. package/plugins/devflow-quality/skills/code-review/SKILL.md +12 -12
  324. package/plugins/devflow-quality/skills/code-review/references/spec-checklist.md +1 -1
  325. package/plugins/devflow-quality/skills/workflow-acceptance-test-design/SKILL.md +11 -8
  326. package/plugins/devflow-quality/skills/workflow-gen-test-guide/SKILL.md +6 -6
  327. package/plugins/devflow-quality/skills/workflow-gen-test-guide/legacy/SKILL.md +14 -14
  328. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +82 -69
  329. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/api-mainflow-rules.md +20 -15
  330. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/codebase-discovery.md +12 -10
  331. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/intermediate-format.md +115 -192
  332. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/mainflow-selection.md +27 -23
  333. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/project-dsl-contract.md +78 -153
  334. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/ui-mainflow-rules.md +36 -83
  335. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/compile_dsl.py +497 -556
  336. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/discover_codebases.py +2 -2
  337. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/export_legacy_testagent.py +647 -0
  338. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/normalize_case_payloads.py +10 -3
  339. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/validate_case_payloads.py +165 -275
  340. package/plugins/devflow-quality/skills/workflow-human-acceptance/SKILL.md +61 -0
  341. package/plugins/devflow-quality/skills/workflow-release-closeout/SKILL.md +18 -18
  342. package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/SKILL.md +20 -14
  343. package/plugins/devflow-requirements/skills/requirement-feasibility-review/SKILL.md +1 -1
  344. package/plugins/devflow-requirements/skills/workflow-demand-clarify/SKILL.md +10 -10
  345. package/plugins/devflow-requirements/skills/workflow-domain-modeling/SKILL.md +25 -25
  346. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/SKILL.md +49 -49
  347. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/assets/baseline-template.md +22 -22
  348. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/antipatterns.md +3 -3
  349. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/challenge-review-guide.md +3 -3
  350. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/project-context-guide.md +11 -11
  351. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/questioning.md +1 -1
  352. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/requirement-review-checklist.md +4 -4
  353. package/plugins/devflow-requirements/skills/workflow-requirement-triage/SKILL.md +16 -16
  354. package/plugins/devflow-requirements/skills/workflow-wayfinder/SKILL.md +6 -6
  355. package/src/approval-store.ts +340 -0
  356. package/src/cli.ts +15 -0
  357. package/src/constants.ts +1 -1
  358. package/src/doctor.ts +259 -2
  359. package/src/initialization-store.ts +51 -3
  360. package/src/paths.ts +12 -1
  361. package/src/project-context.ts +60 -15
  362. package/src/project-documents.ts +61 -61
  363. package/src/project-initializer.ts +9 -8
  364. package/src/project-layout.ts +217 -0
  365. package/src/runtime.ts +3 -3
  366. package/templates/commands/start.md +4 -4
  367. package/templates/omp/AGENTS.md +1 -1
  368. package/templates/omp/hook-config.json +13 -7
  369. package/templates/omp/index-policy.json +8 -8
@@ -0,0 +1,515 @@
1
+ #!/usr/bin/env python3
2
+ """Core devflow_playwright adapter: trust contract between Core and the fixed Runtime.
3
+
4
+ Core alone decides what runs: it resolves the packaged runner/config/CLI,
5
+ checks approvals and the runtime manifest, creates a fresh run directory with
6
+ an execution id + nonce, launches Node in its own process group with a
7
+ sanitized environment, and then verifies result.json and every artifact before
8
+ normalizing the Runtime's local verdict (方案 §8, 变更清单 §1.3–§1.6).
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import hashlib
13
+ import json
14
+ import os
15
+ import re
16
+ import secrets
17
+ import shutil
18
+ import signal
19
+ import subprocess
20
+ from datetime import datetime, timezone
21
+ from pathlib import Path
22
+ from typing import Any
23
+
24
+ from approval_store import REQUIRES_DATABASE, valid_approval
25
+ from dsl_v1 import runtime_dir_candidates
26
+ from environment_profile import load as load_profile
27
+ from tc_hash import tc_sha256
28
+
29
+
30
+ ADAPTER_KIND = "devflow_playwright"
31
+ ADAPTER_KEYS = frozenset({"kind", "dsl_file", "environment_profile", "timeout_seconds", "artifacts"})
32
+ FORBIDDEN_ADAPTER_KEYS = frozenset({"argv", "env", "runner", "config", "result_file", "spec"})
33
+ ARTIFACT_KEYS = frozenset({"trace", "screenshots", "max_total_bytes"})
34
+ ARTIFACT_MODES = ("on_failure", "off")
35
+ DEFAULT_TIMEOUT_SECONDS, MIN_TIMEOUT_SECONDS, MAX_TIMEOUT_SECONDS = 600, 30, 1800
36
+ DEFAULT_MAX_ARTIFACT_BYTES, MAX_ARTIFACT_BYTES = 104857600, 1073741824
37
+ MAX_DSL_BYTES = 2 * 1024 * 1024
38
+ RUNTIME_FILES = ("runner.spec.mjs", "playwright.config.mjs", "reporter.mjs", "devflow-test-dsl-v1.schema.json")
39
+ PASSTHROUGH_ENV = ("PATH", "HOME", "TMPDIR", "LANG", "PLAYWRIGHT_BROWSERS_PATH")
40
+ RESULT_ORIGIN = "devflow_playwright_runtime"
41
+ RESULT_STATUSES = frozenset({"passed", "failed", "blocked", "invalid"})
42
+ ARTIFACT_KINDS = frozenset({"trace", "screenshot", "network"})
43
+ EXECUTION_ID_RE = re.compile(r"^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{8}$")
44
+ SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
45
+ KILL_GRACE_SECONDS = 5
46
+ STEP_SUMMARY_KEYS = ("id", "phase", "driver", "action", "status", "duration_ms", "locator", "match_count", "http_status", "assertion", "error")
47
+
48
+
49
+ def utc_now() -> str:
50
+ return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
51
+
52
+
53
+ def sha256_bytes(value: bytes) -> str:
54
+ return hashlib.sha256(value).hexdigest()
55
+
56
+
57
+ def generated_cases_dir(root: Path, feature: str) -> Path:
58
+ return root / ".devflow/planning" / "generated-cases" / feature
59
+
60
+
61
+ def dsl_path(root: Path, feature: str, raw: Any) -> tuple[Path | None, str | None]:
62
+ """dsl_file must be a regular JSON file under .devflow/planning/generated-cases/<feature>/."""
63
+ if not isinstance(raw, str) or not raw.strip() or "\x00" in raw:
64
+ return None, "dsl_file must be a project-relative path"
65
+ candidate = Path(raw)
66
+ if candidate.is_absolute() or ".." in candidate.parts or candidate.suffix != ".json":
67
+ return None, f"dsl_file must be a relative .json path inside the project: {raw}"
68
+ root_real = root.resolve()
69
+ resolved = (root_real / candidate).resolve()
70
+ try:
71
+ resolved.relative_to(generated_cases_dir(root_real, feature).resolve())
72
+ except ValueError:
73
+ return None, f"dsl_file must be under .devflow/planning/generated-cases/{feature}/: {raw}"
74
+ return resolved, None
75
+
76
+
77
+ def adapter_settings(adapter: dict[str, Any]) -> dict[str, Any]:
78
+ artifacts = adapter.get("artifacts") if isinstance(adapter.get("artifacts"), dict) else {}
79
+ return {"timeout_seconds": adapter.get("timeout_seconds", DEFAULT_TIMEOUT_SECONDS),
80
+ "trace": artifacts.get("trace", "on_failure"), "screenshots": artifacts.get("screenshots", "on_failure"),
81
+ "max_total_bytes": artifacts.get("max_total_bytes", DEFAULT_MAX_ARTIFACT_BYTES)}
82
+
83
+
84
+ def adapter_errors(root: Path, feature: str, adapter: dict[str, Any]) -> list[str]:
85
+ """Binding-shape errors only; the binding can never name a runner, argv, env, result path, or hash."""
86
+ errors: list[str] = []
87
+ keys = set(adapter)
88
+ forbidden = sorted(key for key in keys if key in FORBIDDEN_ADAPTER_KEYS or key.endswith("_sha256"))
89
+ if forbidden:
90
+ errors.append(f"devflow_playwright adapter must not provide {', '.join(forbidden)} (Core resolves runner, environment, results and trusted hashes)")
91
+ unknown = sorted(keys - ADAPTER_KEYS - set(forbidden))
92
+ if unknown:
93
+ errors.append(f"devflow_playwright adapter has unsupported keys: {', '.join(unknown)}")
94
+ _path, path_error = dsl_path(root, feature, adapter.get("dsl_file"))
95
+ if path_error:
96
+ errors.append(path_error)
97
+ profile = adapter.get("environment_profile")
98
+ if not isinstance(profile, str) or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_-]{0,63}", profile):
99
+ errors.append("devflow_playwright adapter requires a valid environment_profile name")
100
+ timeout = adapter.get("timeout_seconds", DEFAULT_TIMEOUT_SECONDS)
101
+ if not isinstance(timeout, int) or isinstance(timeout, bool) or not MIN_TIMEOUT_SECONDS <= timeout <= MAX_TIMEOUT_SECONDS:
102
+ errors.append(f"timeout_seconds must be an integer in [{MIN_TIMEOUT_SECONDS}, {MAX_TIMEOUT_SECONDS}]")
103
+ artifacts = adapter.get("artifacts", {})
104
+ if not isinstance(artifacts, dict):
105
+ errors.append("artifacts must be an object")
106
+ else:
107
+ bad = sorted(key for key in artifacts if key not in ARTIFACT_KEYS)
108
+ if bad:
109
+ errors.append(f"artifacts has unsupported keys: {', '.join(bad)}")
110
+ for key in ("trace", "screenshots"):
111
+ if key in artifacts and artifacts[key] not in ARTIFACT_MODES:
112
+ errors.append(f"artifacts.{key} must be one of {', '.join(ARTIFACT_MODES)}")
113
+ limit = artifacts.get("max_total_bytes", DEFAULT_MAX_ARTIFACT_BYTES)
114
+ if not isinstance(limit, int) or isinstance(limit, bool) or not 1 <= limit <= MAX_ARTIFACT_BYTES:
115
+ errors.append(f"artifacts.max_total_bytes must be an integer in [1, {MAX_ARTIFACT_BYTES}]")
116
+ return errors
117
+
118
+
119
+ def read_dsl(path: Path) -> tuple[bytes | None, Any, str | None]:
120
+ try:
121
+ if path.is_symlink() or not path.is_file():
122
+ return None, None, "DSL file is missing or not a regular file"
123
+ raw = path.read_bytes()
124
+ except OSError as error:
125
+ return None, None, f"DSL file is unreadable: {error}"
126
+ if len(raw) > MAX_DSL_BYTES:
127
+ return None, None, f"DSL file exceeds {MAX_DSL_BYTES} bytes"
128
+ try:
129
+ return raw, json.loads(raw.decode("utf-8")), None
130
+ except (UnicodeDecodeError, json.JSONDecodeError) as error:
131
+ return raw, None, f"DSL file is not valid JSON: {error}"
132
+
133
+
134
+ # ---- Runtime location and integrity (A03) ---------------------------------------------------
135
+
136
+ def locate_runtime() -> Path | None:
137
+ return next((candidate for candidate in runtime_dir_candidates() if (candidate / "runtime-manifest.json").is_file()), None)
138
+
139
+
140
+ def verify_manifest(runtime: Path) -> tuple[dict[str, Any] | None, str | None, list[str]]:
141
+ """Return (manifest, manifest sha256, errors) after re-hashing every pinned runtime file."""
142
+ try:
143
+ raw = (runtime / "runtime-manifest.json").read_bytes()
144
+ manifest = json.loads(raw.decode("utf-8"))
145
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
146
+ return None, None, [f"runtime manifest is unreadable: {error}"]
147
+ errors: list[str] = []
148
+ files = manifest.get("files") if isinstance(manifest, dict) else None
149
+ if not isinstance(manifest, dict) or manifest.get("schema_version") != 1 or not isinstance(files, dict):
150
+ return None, None, ["runtime manifest must have schema_version 1 and a files map"]
151
+ if not isinstance(manifest.get("runtime_version"), str) or not manifest["runtime_version"]:
152
+ errors.append("runtime manifest runtime_version is missing")
153
+ for name in RUNTIME_FILES:
154
+ expected = files.get(name)
155
+ path = runtime / name
156
+ if not isinstance(expected, str) or not SHA256_RE.fullmatch(expected):
157
+ errors.append(f"runtime manifest does not pin {name}")
158
+ elif path.is_symlink() or not path.is_file():
159
+ errors.append(f"runtime file is missing: {name}")
160
+ elif sha256_bytes(path.read_bytes()) != expected:
161
+ errors.append(f"runtime file hash mismatch: {name}")
162
+ return (manifest if not errors else None), sha256_bytes(raw), errors
163
+
164
+
165
+ def sanitized_base_env() -> dict[str, str]:
166
+ return {key: os.environ[key] for key in PASSTHROUGH_ENV if os.environ.get(key)}
167
+
168
+
169
+ def package_root(runtime: Path) -> Path:
170
+ return runtime.resolve().parents[3]
171
+
172
+
173
+ def resolve_playwright_cli(runtime: Path, project_root: Path, expected_version: str | None) -> tuple[Path | None, str | None]:
174
+ """Resolve @playwright/test/cli from the Runtime directory with Node's resolver, never the project's."""
175
+ env = sanitized_base_env()
176
+ node = shutil.which("node", path=env.get("PATH"))
177
+ if node is None:
178
+ return None, "node is not available on PATH"
179
+ script = "process.stdout.write(require.resolve('@playwright/test/cli', {paths: [process.argv[1]]}))"
180
+ try:
181
+ completed = subprocess.run([node, "-e", script, str(runtime.resolve())], cwd=runtime, env=env, text=True,
182
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30, check=False)
183
+ except (OSError, subprocess.TimeoutExpired) as error:
184
+ return None, f"cannot resolve the Runtime Playwright CLI: {error}"
185
+ if completed.returncode != 0 or not completed.stdout.strip():
186
+ return None, "the Runtime Playwright CLI (@playwright/test/cli) is not installed"
187
+ cli = Path(completed.stdout.strip()).resolve()
188
+ project_real = project_root.resolve()
189
+ owner = package_root(runtime)
190
+ inside_project = cli.is_relative_to(project_real)
191
+ if inside_project and not (owner.is_relative_to(project_real) and cli.is_relative_to(owner)):
192
+ return None, f"Playwright CLI resolved into the project under test: {cli}"
193
+ package_json = next((parent / "package.json" for parent in cli.parents if parent.name == "test" and parent.parent.name == "@playwright"), None)
194
+ if expected_version:
195
+ try:
196
+ version = json.loads(package_json.read_text(encoding="utf-8")).get("version") if package_json else None
197
+ except (OSError, ValueError):
198
+ version = None
199
+ if version != expected_version:
200
+ return None, f"Runtime Playwright version {version!r} does not match manifest {expected_version!r}"
201
+ return cli, None
202
+
203
+
204
+ def launch_argv(runtime: Path, project_root: Path, manifest: dict[str, Any]) -> tuple[list[str] | None, str | None]:
205
+ cli, error = resolve_playwright_cli(runtime, project_root, manifest.get("playwright_version"))
206
+ if cli is None:
207
+ return None, error
208
+ node = shutil.which("node", path=sanitized_base_env().get("PATH"))
209
+ return [str(node), str(cli), "test", "--config", str(runtime.resolve() / "playwright.config.mjs")], None
210
+
211
+
212
+ # ---- Execution (A02) ------------------------------------------------------------------------
213
+
214
+ def new_execution_id() -> str:
215
+ return f"{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}-{secrets.token_hex(4)}"
216
+
217
+
218
+ def runs_dir(root: Path, feature: str) -> Path:
219
+ return root / ".devflow" / "runs" / feature
220
+
221
+
222
+ def blocked(error_code: str, message: str, **extra: Any) -> dict[str, Any]:
223
+ return {"status": "blocked", "error_code": error_code, "error": message, **extra}
224
+
225
+
226
+ def terminate_group(process: subprocess.Popen[Any]) -> None:
227
+ try:
228
+ os.killpg(process.pid, signal.SIGTERM)
229
+ except (ProcessLookupError, PermissionError):
230
+ return
231
+ try:
232
+ process.wait(timeout=KILL_GRACE_SECONDS)
233
+ except subprocess.TimeoutExpired:
234
+ pass
235
+ try:
236
+ os.killpg(process.pid, signal.SIGKILL)
237
+ except (ProcessLookupError, PermissionError):
238
+ pass
239
+ process.wait()
240
+
241
+
242
+ def _identity_errors(result: Any, expected: dict[str, Any]) -> list[str]:
243
+ if not isinstance(result, dict):
244
+ return ["result.json root must be an object"]
245
+ errors = [f"result.json {key} does not match" for key, value in expected.items() if result.get(key) != value]
246
+ if result.get("status") not in RESULT_STATUSES:
247
+ errors.append("result.json status is invalid")
248
+ for key in ("started_at", "finished_at"):
249
+ if not isinstance(result.get(key), str) or not result[key]:
250
+ errors.append(f"result.json {key} is required")
251
+ if not isinstance(result.get("steps"), list) or not all(isinstance(step, dict) for step in result["steps"]):
252
+ errors.append("result.json steps must be an array of objects")
253
+ if not isinstance(result.get("artifacts", []), list):
254
+ errors.append("result.json artifacts must be an array")
255
+ cleanup = result.get("cleanup", {})
256
+ if not isinstance(cleanup, dict):
257
+ errors.append("result.json cleanup must be an object")
258
+ if not errors and result["status"] == "passed":
259
+ if any(step.get("status") in {"failed", "blocked"} for step in result["steps"]):
260
+ errors.append("result.json reports passed with failed or blocked steps")
261
+ if cleanup.get("status") == "failed":
262
+ errors.append("result.json reports passed although cleanup failed")
263
+ return errors
264
+
265
+
266
+ def verify_artifacts(root: Path, run_dir: Path, entries: list[Any], max_total_bytes: int) -> tuple[list[dict[str, Any]], list[str]]:
267
+ run_real = run_dir.resolve()
268
+ root_real = root.resolve()
269
+ verified: list[dict[str, Any]] = []
270
+ errors: list[str] = []
271
+ total = 0
272
+ for index, entry in enumerate(entries):
273
+ label = f"artifacts[{index}]"
274
+ if not isinstance(entry, dict) or entry.get("kind") not in ARTIFACT_KINDS or not isinstance(entry.get("path"), str):
275
+ errors.append(f"{label} must declare kind and path")
276
+ continue
277
+ raw = entry["path"]
278
+ candidate = Path(raw)
279
+ if not raw or "\x00" in raw or candidate.is_absolute() or ".." in candidate.parts:
280
+ errors.append(f"{label} path escapes the run directory: {raw}")
281
+ continue
282
+ unresolved = run_real / candidate
283
+ resolved = unresolved.resolve()
284
+ if not resolved.is_relative_to(run_real) or any(parent.is_symlink() for parent in [unresolved, *unresolved.parents] if parent.is_relative_to(run_real) and parent != run_real):
285
+ errors.append(f"{label} path escapes the run directory: {raw}")
286
+ continue
287
+ if not resolved.is_file():
288
+ errors.append(f"{label} file is missing: {raw}")
289
+ continue
290
+ content = resolved.read_bytes()
291
+ digest = sha256_bytes(content)
292
+ if entry.get("bytes") is not None and entry.get("bytes") != len(content):
293
+ errors.append(f"{label} size does not match result.json")
294
+ if entry.get("sha256") is not None and entry.get("sha256") != digest:
295
+ errors.append(f"{label} sha256 does not match result.json")
296
+ total += len(content)
297
+ verified.append({"path": resolved.relative_to(root_real).as_posix(), "kind": entry["kind"], "bytes": len(content), "sha256": digest})
298
+ if total > max_total_bytes:
299
+ errors.append(f"artifacts total {total} bytes exceed max_total_bytes {max_total_bytes}")
300
+ return verified, errors
301
+
302
+
303
+ def normalize(result_status: str | None, exit_code: int | None, runtime_error_code: Any) -> tuple[str, str | None, list[str]]:
304
+ """方案 §8.3: the Runtime status is a local verdict; the exit code is only a consistency check."""
305
+ if result_status == "passed":
306
+ return ("passed", None, []) if exit_code == 0 else ("blocked", "runtime_integrity_error", [])
307
+ if result_status == "failed":
308
+ return "failed", runtime_error_code if isinstance(runtime_error_code, str) else None, (["result_exit_conflict"] if exit_code == 0 else [])
309
+ if result_status in {"blocked", "invalid"}:
310
+ return "blocked", runtime_error_code if isinstance(runtime_error_code, str) and runtime_error_code else f"runtime_{result_status}", []
311
+ return "blocked", "runtime_result_invalid", []
312
+
313
+
314
+ def resolve_trust(root: Path, feature: str, tc_id: str, tc_data: dict[str, Any], suite: dict[str, Any], adapter: dict[str, Any]) -> dict[str, Any]:
315
+ """Re-derive, from current files and approvals, everything Core trusts before a run.
316
+
317
+ Shared by execute() (run time) and acceptance_test_runner.validate_results() (gate time) so both
318
+ apply exactly the same DSL/TC identity, execution_plan approval and environment_profile checks.
319
+ Returns {"base", "failure", "profile", "profile_name", "raw_profile", "raw_dsl", "dsl_sha", "tc_sha"};
320
+ ``failure`` is a complete Core-normalized blocked result (or None when everything is valid).
321
+ """
322
+ meta = suite.get("view_meta") if isinstance(suite.get("view_meta"), dict) else {}
323
+ current_tc = tc_sha256(tc_data)
324
+ trust: dict[str, Any] = {"base": {}, "failure": None, "profile": None, "profile_name": None, "raw_profile": None, "raw_dsl": None, "dsl_sha": None, "tc_sha": current_tc}
325
+ path, path_error = dsl_path(root, feature, adapter.get("dsl_file"))
326
+ raw_dsl, dsl, dsl_error = read_dsl(path) if path else (None, None, path_error)
327
+ if raw_dsl is None or dsl is None:
328
+ trust["failure"] = blocked("runtime_result_invalid", dsl_error or "DSL is unavailable")
329
+ return trust
330
+ dsl_sha = sha256_bytes(raw_dsl)
331
+ base = {"tc_sha256": current_tc, "dsl_sha256": dsl_sha}
332
+ trust.update({"base": base, "raw_dsl": raw_dsl, "dsl_sha": dsl_sha})
333
+ if not isinstance(dsl, dict) or dsl.get("tc_id") != tc_id or dsl.get("tc_sha256") != current_tc:
334
+ trust["failure"] = blocked("dsl_not_approved", "DSL no longer matches the frozen TC", **base)
335
+ return trust
336
+
337
+ # Pins are recomputed by approval_pins from the approved request; the DSL the
338
+ # binding names, the frozen TC and the suite must be exactly what was approved.
339
+ plan = valid_approval(root, feature, "execution_plan", tc_id, {"suite_graph_revision": suite.get("graph_revision"), "suite_fingerprint": meta.get("fingerprint"),
340
+ "tc_sha256": current_tc, "dsl_sha256": dsl_sha})
341
+ if not plan["ok"]:
342
+ code = REQUIRES_DATABASE if plan["reason"] == REQUIRES_DATABASE else "dsl_not_approved"
343
+ trust["failure"] = blocked(code, f"execution plan approval is not valid: {plan['message']}", dsl_approval="none", **base)
344
+ return trust
345
+ base["dsl_approval"] = {"status": "approved", "approval_id": plan["approval"]["id"], "approved_at": plan["approval"]["approved_at"]}
346
+
347
+ profile_name = adapter["environment_profile"]
348
+ profile, profile_sha, profile_errors = load_profile(root, profile_name)
349
+ if profile is None or profile_sha is None:
350
+ trust["failure"] = blocked("environment_profile_not_approved", "; ".join(profile_errors), **base)
351
+ return trust
352
+ profile_approval = valid_approval(root, feature, "environment_profile", profile_name, {"profile_sha256": profile_sha})
353
+ if not profile_approval["ok"]:
354
+ code = REQUIRES_DATABASE if profile_approval["reason"] == REQUIRES_DATABASE else "environment_profile_not_approved"
355
+ trust["failure"] = blocked(code, f"environment profile approval is not valid: {profile_approval['message']}", **base)
356
+ return trust
357
+ base["environment_profile"] = {"name": profile_name, "sha256": profile_sha, "approval_id": profile_approval["approval"]["id"]}
358
+ raw_profile = (root / ".devflow/planning/test-environments" / f"{profile_name}.json").read_bytes()
359
+ if sha256_bytes(raw_profile) != profile_sha:
360
+ trust["failure"] = blocked("environment_profile_not_approved", "environment profile changed while starting the run", **base)
361
+ return trust
362
+ trust.update({"profile": profile, "profile_name": profile_name, "raw_profile": raw_profile})
363
+ return trust
364
+
365
+
366
+ def recorded_run_errors(root: Path, feature: str, tc_id: str, entry: dict[str, Any], run_dir_expired: bool = False) -> list[str]:
367
+ """Re-verify the on-disk evidence a recorded result entry points at (result.json + artifacts).
368
+
369
+ Missing/changed evidence is an error. When the whole run directory is gone and ``run_dir_expired``
370
+ is true (the feature is past its post-closeout retention window, so GC legitimately removed it)
371
+ the file checks are skipped; every other field is still verified by the caller.
372
+ """
373
+ execution_id = entry.get("execution_id")
374
+ if not isinstance(execution_id, str) or not EXECUTION_ID_RE.fullmatch(execution_id):
375
+ return ["execution_id is missing or malformed"]
376
+ root_real = root.resolve()
377
+ run_dir = runs_dir(root_real, feature) / execution_id
378
+ expected_relative = run_dir.relative_to(root_real).as_posix()
379
+ if entry.get("run_dir") != expected_relative:
380
+ return [f"run_dir {entry.get('run_dir')!r} does not match execution_id (expected {expected_relative})"]
381
+ if run_dir.is_symlink() or not run_dir.is_dir():
382
+ return [] if run_dir_expired and not run_dir.is_symlink() and not run_dir.exists() else [f"run directory is missing: {expected_relative}"]
383
+ errors: list[str] = []
384
+ recorded_sha = entry.get("result_sha256")
385
+ result_file = run_dir / "result.json"
386
+ if result_file.is_symlink() or not result_file.is_file():
387
+ errors.append(f"result.json is missing: {expected_relative}/result.json")
388
+ else:
389
+ raw = result_file.read_bytes()
390
+ if sha256_bytes(raw) != recorded_sha:
391
+ errors.append("result.json sha256 differs from the recorded result_sha256 (evidence changed)")
392
+ else:
393
+ try:
394
+ result = json.loads(raw.decode("utf-8"))
395
+ except (UnicodeDecodeError, ValueError):
396
+ result = None
397
+ expected = {"origin": RESULT_ORIGIN, "execution_id": execution_id, "tc_id": tc_id, "tc_sha256": entry.get("tc_sha256"), "dsl_sha256": entry.get("dsl_sha256")}
398
+ if not isinstance(result, dict):
399
+ errors.append("result.json is not a JSON object")
400
+ else:
401
+ errors.extend(f"result.json {key} does not match the recorded entry" for key, value in expected.items() if result.get(key) != value)
402
+ artifacts = entry.get("artifacts")
403
+ if not isinstance(artifacts, list):
404
+ errors.append("artifacts must be a recorded array")
405
+ return errors
406
+ for index, artifact in enumerate(artifacts):
407
+ raw_path = artifact.get("path") if isinstance(artifact, dict) else None
408
+ if not isinstance(raw_path, str) or not raw_path or not isinstance(artifact.get("sha256"), str):
409
+ errors.append(f"artifacts[{index}] must record path and sha256")
410
+ continue
411
+ candidate = Path(raw_path)
412
+ resolved = (root_real / candidate).resolve()
413
+ if candidate.is_absolute() or ".." in candidate.parts or not resolved.is_relative_to(run_dir.resolve()):
414
+ errors.append(f"artifacts[{index}] path is outside the run directory: {raw_path}")
415
+ elif not resolved.is_file():
416
+ errors.append(f"artifact is missing: {raw_path}")
417
+ elif sha256_bytes(resolved.read_bytes()) != artifact["sha256"]:
418
+ errors.append(f"artifact sha256 differs from the recorded value (evidence changed): {raw_path}")
419
+ return errors
420
+
421
+
422
+ def execute(root: Path, feature: str, tc_id: str, tc_data: dict[str, Any], suite: dict[str, Any], adapter: dict[str, Any]) -> dict[str, Any]:
423
+ """Run one bound TC through the Runtime and return the Core-normalized result fields."""
424
+ settings = adapter_settings(adapter)
425
+ trust = resolve_trust(root, feature, tc_id, tc_data, suite, adapter)
426
+ if trust["failure"] is not None:
427
+ return trust["failure"]
428
+ base, profile, raw_profile, raw_dsl, dsl_sha, current_tc = trust["base"], trust["profile"], trust["raw_profile"], trust["raw_dsl"], trust["dsl_sha"], trust["tc_sha"]
429
+
430
+ secret_env: dict[str, str] = {}
431
+ for key, env_name in profile.get("secret_env", {}).items():
432
+ if not os.environ.get(env_name):
433
+ return blocked("environment_secret_missing", f"secret environment variable for '{key}' is unset: {env_name}", **base)
434
+ secret_env[env_name] = os.environ[env_name]
435
+
436
+ runtime = locate_runtime()
437
+ if runtime is None:
438
+ return blocked("runtime_integrity_error", "Devflow Playwright Runtime is not installed (runtime-manifest.json not found)", **base)
439
+ manifest, manifest_sha, manifest_errors = verify_manifest(runtime)
440
+ if manifest is None:
441
+ return blocked("runtime_integrity_error", "; ".join(manifest_errors), **base)
442
+ base["runtime"] = {"version": manifest["runtime_version"], "playwright_version": manifest.get("playwright_version"), "manifest_sha256": manifest_sha}
443
+ argv, launch_error = launch_argv(runtime, root, manifest)
444
+ if argv is None:
445
+ return blocked("runtime_integrity_error", launch_error or "Runtime launcher is unavailable", **base)
446
+
447
+ execution_id = new_execution_id()
448
+ nonce = secrets.token_hex(16)
449
+ run_dir = runs_dir(root, feature) / execution_id
450
+ try:
451
+ run_dir.parent.mkdir(parents=True, exist_ok=True)
452
+ run_dir.mkdir(mode=0o700)
453
+ (run_dir / "artifacts").mkdir(mode=0o700)
454
+ except OSError as error:
455
+ return blocked("runtime_result_invalid", f"cannot create a fresh run directory: {error}", **base)
456
+ base["execution_id"] = execution_id
457
+ base["run_dir"] = run_dir.relative_to(root.resolve() if run_dir.is_relative_to(root.resolve()) else root).as_posix()
458
+ dsl_copy = run_dir / "dsl.json"
459
+ environment_copy = run_dir / "environment.json"
460
+ dsl_copy.write_bytes(raw_dsl)
461
+ environment_copy.write_bytes(raw_profile)
462
+ result_file = run_dir / "result.json"
463
+ env = {**sanitized_base_env(), **secret_env,
464
+ "DEVFLOW_DSL_FILE": str(dsl_copy.resolve()), "DEVFLOW_DSL_SHA256": dsl_sha,
465
+ "DEVFLOW_TC_ID": tc_id, "DEVFLOW_TC_SHA256": current_tc,
466
+ "DEVFLOW_EXECUTION_ID": execution_id, "DEVFLOW_EXECUTION_NONCE": nonce,
467
+ "DEVFLOW_RESULT_FILE": str(result_file.resolve()), "DEVFLOW_ARTIFACT_DIR": str((run_dir / "artifacts").resolve()),
468
+ "DEVFLOW_ENVIRONMENT_FILE": str(environment_copy.resolve())}
469
+ started_at = utc_now()
470
+ exit_code: int | None = None
471
+ timed_out = False
472
+ with open(run_dir / "runtime.log", "wb") as log:
473
+ try:
474
+ process = subprocess.Popen(argv, cwd=runtime, env=env, stdin=subprocess.DEVNULL, stdout=log, stderr=subprocess.STDOUT, start_new_session=True)
475
+ except OSError as error:
476
+ return blocked("runtime_integrity_error", f"Runtime could not start: {error}", **base)
477
+ try:
478
+ exit_code = process.wait(timeout=settings["timeout_seconds"])
479
+ except subprocess.TimeoutExpired:
480
+ timed_out = True
481
+ terminate_group(process)
482
+ finally:
483
+ try: # reap any browser left in the group after the leader exited
484
+ os.killpg(process.pid, signal.SIGKILL)
485
+ except (ProcessLookupError, PermissionError):
486
+ pass
487
+ base.update({"started_at": started_at, "finished_at": utc_now(), "exit_code": exit_code})
488
+ if timed_out:
489
+ return blocked("runtime_timeout", f"Runtime exceeded timeout_seconds={settings['timeout_seconds']}", **base)
490
+
491
+ try:
492
+ if result_file.is_symlink() or not result_file.is_file():
493
+ raise ValueError("result.json is missing")
494
+ raw_result = result_file.read_bytes()
495
+ result = json.loads(raw_result.decode("utf-8"))
496
+ except (OSError, UnicodeDecodeError, ValueError) as error:
497
+ return blocked("runtime_result_invalid", str(error), **base)
498
+ base["result_sha256"] = sha256_bytes(raw_result)
499
+ identity_errors = _identity_errors(result, {"schema_version": 1, "origin": RESULT_ORIGIN, "execution_id": execution_id, "nonce": nonce,
500
+ "tc_id": tc_id, "tc_sha256": current_tc, "dsl_sha256": dsl_sha,
501
+ "runtime_version": manifest["runtime_version"]})
502
+ if identity_errors:
503
+ return blocked("runtime_result_invalid", "; ".join(identity_errors), **base)
504
+ artifacts, artifact_errors = verify_artifacts(root, run_dir, result.get("artifacts", []), settings["max_total_bytes"])
505
+ base["artifacts"] = artifacts
506
+ base["steps"] = [{key: step[key] for key in STEP_SUMMARY_KEYS if key in step} for step in result["steps"]]
507
+ base["cleanup"] = result.get("cleanup", {})
508
+ base["runtime_status"] = result["status"]
509
+ if artifact_errors:
510
+ return blocked("runtime_artifact_invalid", "; ".join(artifact_errors), **base)
511
+ status, error_code, conflicts = normalize(result["status"], exit_code, result.get("error_code"))
512
+ base.update({"status": status, "error_code": error_code})
513
+ if conflicts:
514
+ base["conflicts"] = conflicts
515
+ return base
@@ -9,7 +9,7 @@ require_relative "control_io"
9
9
  require_relative "validate_state"
10
10
  require_relative "phase_ownership"
11
11
 
12
- options = { state: ".planning/STATE.md", expected_revision: nil, confirm: false, write: false, json: false }
12
+ options = { state: ".devflow/planning/STATE.md", expected_revision: nil, confirm: false, write: false, json: false }
13
13
  OptionParser.new do |opts|
14
14
  opts.banner = "Usage: repair_control_plane.rb --expected-revision N --confirm --write"
15
15
  opts.on("--state PATH") { |value| options[:state] = value }
@@ -14,7 +14,7 @@ module RequirementCoverage
14
14
  module_function
15
15
 
16
16
  def generate(root:, feature:, source:)
17
- state = YAML.safe_load(File.read(File.join(root, ".planning/STATE.md")).split(/^---\s*$/, 3)[1], permitted_classes: [Date], aliases: false)
17
+ state = YAML.safe_load(File.read(File.join(root, ".devflow/planning/STATE.md")).split(/^---\s*$/, 3)[1], permitted_classes: [Date], aliases: false)
18
18
  raise ArgumentError, "source generation requires the active wayfinder step" unless state["current_feature"] == feature && state["current_step"] == "requirement-wayfinder"
19
19
  source_path = File.realpath(File.expand_path(source, root))
20
20
  raise ArgumentError, "source must be a project file" unless source_path.start_with?(File.realpath(root) + File::SEPARATOR)
@@ -18,7 +18,7 @@ module RequirementGraph
18
18
  # graphs are migrated in memory and re-written only by this Core writer.
19
19
  SCHEMA_VERSION = 5
20
20
  SUPPORTED_SCHEMA_VERSIONS = [1, 2, 3, 4, SCHEMA_VERSION].freeze
21
- ROOT = ".planning/requirements"
21
+ ROOT = ".devflow/planning/requirements"
22
22
  KINDS = {
23
23
  "source" => "SRC", "code" => "CODE", "term" => "TERM", "object" => "OBJECT",
24
24
  "behavior" => "BEH", "requirement" => "REQ", "rule" => "BR", "boundary" => "BC",
@@ -56,7 +56,7 @@ module RequirementGraph
56
56
  end
57
57
 
58
58
  def active_feature(root)
59
- state_path = File.join(root, ".planning", "STATE.md")
59
+ state_path = File.join(root, ".devflow/planning", "STATE.md")
60
60
  return nil unless File.file?(state_path)
61
61
  front = File.read(state_path).match(/\A---\n(.*?)\n---/m)&.[](1)
62
62
  return nil unless front
@@ -553,7 +553,7 @@ module RequirementGraph
553
553
  end
554
554
  matrix["imported_graph_revision"] = graph["revision"]
555
555
  DevflowControlIO.atomic_write(matrix_path, JSON.pretty_generate(matrix) + "\n")
556
- template_path = File.join(root, ".planning", "features", graph["feature"], "api-acceptance-bindings.template.json")
556
+ template_path = File.join(root, ".devflow/planning", "features", graph["feature"], "api-acceptance-bindings.template.json")
557
557
  template = {
558
558
  "schema_version" => 1, "origin" => "core_api_test_matrix_template", "feature" => graph["feature"],
559
559
  "case_set_fingerprint" => matrix["case_set_fingerprint"],
@@ -81,7 +81,7 @@ def preflight(root: Path, feature: str, step: str, contract_path: str,
81
81
  return {"status": "human_confirmation", "evidence": evidence,
82
82
  "issues": [{"code": "STALE_WORKFLOW_SNAPSHOT"}]}
83
83
  workflow = load_workflow(workflow_path)
84
- state = load_state(within(root, ".planning/STATE.md"))
84
+ state = load_state(within(root, ".devflow/planning/STATE.md"))
85
85
  if state.get("revision") != current["state_revision"] or state.get("current_feature") != feature or state.get("current_step") != step:
86
86
  return {"status": "human_confirmation", "evidence": evidence,
87
87
  "issues": [{"code": "STALE_STATE_SNAPSHOT"}]}
@@ -100,7 +100,7 @@ def preflight(root: Path, feature: str, step: str, contract_path: str,
100
100
  else:
101
101
  issues.append({"code": "INVALID_WORKFLOW_INPUT", "detail": "unrecognized input obligation"})
102
102
  continue
103
- if required and relative != ".planning/execution-contract.md":
103
+ if required and relative != ".devflow/planning/execution-contract.md":
104
104
  derived.append({"path": render(relative), "producer": "existing"})
105
105
  for item in step_definition.get("outputs", []):
106
106
  if isinstance(item, dict) and item.get("required") is True and isinstance(item.get("path"), str):
@@ -113,7 +113,7 @@ def preflight(root: Path, feature: str, step: str, contract_path: str,
113
113
  "required_obligation_count": len(derived),
114
114
  "checker_ids": current.get("checker_ids", [])})
115
115
  if step == "code-review":
116
- graph_relative = f".planning/requirements/{feature}/governance.json"
116
+ graph_relative = f".devflow/planning/requirements/{feature}/governance.json"
117
117
  graph_path = within(root, graph_relative)
118
118
  if not graph_path.is_file():
119
119
  issues.append({"code": "CONFIRMED_EXPECTATIONS_UNAVAILABLE", "path": graph_relative})