@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
package/src/cli.ts CHANGED
@@ -5,6 +5,8 @@ import { readFileSync } from "node:fs";
5
5
 
6
6
  import { formatDoctor, runHostDoctor } from "./doctor.ts";
7
7
  import { initializeProject, loadProjectContext, resolveProjectRoot } from "./project-context.ts";
8
+ import { migrateLegacyProjectLayout } from "./project-layout.ts";
9
+ import { InitializationStore } from "./initialization-store.ts";
8
10
  import { aggregateUsage, renderUsage } from "./usage-stats.ts";
9
11
  import { projectSessionDir } from "./paths.ts";
10
12
 
@@ -12,6 +14,7 @@ function usage(): string {
12
14
  return [
13
15
  "Usage:",
14
16
  " devflow-pi init [project] [--scan]",
17
+ " devflow-pi migrate [project] --confirm-active",
15
18
  " devflow-pi doctor [project]",
16
19
  " devflow-pi usage [project]",
17
20
  " devflow-pi [project] [--resume [session-id]] [--prompt <text>] [--mcp] [--yolo]",
@@ -65,6 +68,18 @@ async function main(): Promise<void> {
65
68
  }
66
69
  return;
67
70
  }
71
+ if (command === "migrate") {
72
+ const projectArgument = args[1] && !args[1].startsWith("-") ? args[1] : undefined;
73
+ if (!args.includes("--confirm-active")) throw new Error("Legacy control-plane migration requires --confirm-active");
74
+ const requested = projectArgument ? path.resolve(projectArgument) : process.cwd();
75
+ const root = await resolveProjectRoot(requested, { explicit: requested, allowUninitialized: true });
76
+ const migrated = await migrateLegacyProjectLayout(root, { allowActive: true, allowUnmanifested: true });
77
+ const manifest = JSON.parse(readFileSync(path.join(root, ".devflow/project.json"), "utf8"));
78
+ const store = await InitializationStore.open({ root, manifest });
79
+ try { await store.registerWorkspace(); } finally { store.close(); }
80
+ console.log(JSON.stringify({ status: "migrated", root, migrated }, null, 2));
81
+ return;
82
+ }
68
83
  const positionalProject = command && !["doctor", "usage"].includes(command) && !command.startsWith("-") ? command : undefined;
69
84
  const requestedProject = ["doctor", "usage"].includes(command || "") ? args[1] : positionalProject;
70
85
  const root = await resolveProjectRoot(process.cwd(), { explicit: requestedProject });
package/src/constants.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export const PRODUCT_NAME = "Devflow Pi";
2
- export const PRODUCT_VERSION = "0.1.93";
2
+ export const PRODUCT_VERSION = "0.1.95";
3
3
  export const PROJECT_SCHEMA_VERSION = 1;
4
4
  export const OMP_RUNTIME_VERSION = "17.2.7";
5
5
 
package/src/doctor.ts CHANGED
@@ -1,10 +1,22 @@
1
- import { access, lstat, readFile } from "node:fs/promises";
1
+ import { access, lstat, readFile, readdir } from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
2
3
  import path from "node:path";
3
4
 
4
5
  import { DEVFLOW_PLUGIN_NAMES, OMP_RUNTIME_VERSION, PRODUCT_VERSION } from "./constants.ts";
5
6
  import { devflowAgentDir, devflowHome, ompSdkManifestPath, pluginRoots, projectSessionDir } from "./paths.ts";
6
7
  import type { ProjectContext } from "./project-context.ts";
7
8
 
9
+ const RUNTIME_FILES = ["runner.spec.mjs", "playwright.config.mjs", "reporter.mjs", "devflow-test-dsl-v1.schema.json"] as const;
10
+ const SHA256 = /^[0-9a-f]{64}$/;
11
+ const PROFILE_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
12
+ const PROFILE_FIELDS = new Set(["profile", "base_url", "api_base_url", "navigation_origins", "api_origins", "subresources", "secret_env", "trace", "screenshots"]);
13
+ const RESERVED_ENV = new Set([
14
+ "DEVFLOW_DSL_FILE", "DEVFLOW_DSL_SHA256", "DEVFLOW_TC_ID", "DEVFLOW_TC_SHA256", "DEVFLOW_EXECUTION_ID",
15
+ "DEVFLOW_EXECUTION_NONCE", "DEVFLOW_RESULT_FILE", "DEVFLOW_ARTIFACT_DIR", "DEVFLOW_ENVIRONMENT_FILE",
16
+ "PATH", "HOME", "TMPDIR", "LANG", "PLAYWRIGHT_BROWSERS_PATH", "SHELL", "USER", "PWD",
17
+ ]);
18
+ const RESERVED_ENV_PREFIXES = ["NODE_", "LD_", "DYLD_", "PLAYWRIGHT_", "PYTHON", "BUN_", "NPM_", "DEVFLOW_HOME", "DEVFLOW_KIT", "DEVFLOW_SOFT"];
19
+
8
20
  export interface DoctorCheck {
9
21
  id: string;
10
22
  ok: boolean;
@@ -30,11 +42,200 @@ function bunVersionOk(version: string): boolean {
30
42
  return major > 1 || (major === 1 && (minor > 3 || (minor === 3 && patch >= 14)));
31
43
  }
32
44
 
33
- export async function runHostDoctor(project: ProjectContext): Promise<HostDoctorReport> {
45
+ function majorVersion(version: string): number {
46
+ return Number((version.match(/v?(\d+)/) || [])[1] || 0);
47
+ }
48
+
49
+ export interface ProbeResult {
50
+ ok: boolean;
51
+ timedOut: boolean;
52
+ exitCode: number | null;
53
+ stdout: string;
54
+ stderr: string;
55
+ /** Human-readable reason when ok is false. */
56
+ error?: string;
57
+ }
58
+
59
+ export interface ProbeOptions {
60
+ timeoutMs: number;
61
+ cwd?: string;
62
+ }
63
+
64
+ /** Runs one external command; must never throw and must always settle. */
65
+ export type ProbeRunner = (argv: string[], options: ProbeOptions) => Promise<ProbeResult>;
66
+
67
+ export interface DoctorOptions {
68
+ probe?: ProbeRunner;
69
+ /** Hard limits. A probe that exceeds them is reported as a failed check, never a pass. */
70
+ timeouts?: { browserLaunchMs?: number; versionMs?: number };
71
+ }
72
+
73
+ const DEFAULT_BROWSER_LAUNCH_TIMEOUT_MS = 20_000;
74
+ const DEFAULT_VERSION_TIMEOUT_MS = 10_000;
75
+ const PIPE_DRAIN_MS = 500;
76
+
77
+ function settleWithin<T>(promise: Promise<T>, ms: number, fallback: T): Promise<T> {
78
+ return new Promise(resolve => {
79
+ const timer = setTimeout(() => resolve(fallback), ms);
80
+ promise.then(value => { clearTimeout(timer); resolve(value); }, () => { clearTimeout(timer); resolve(fallback); });
81
+ });
82
+ }
83
+
84
+ /** Async spawn with a hard timeout: the child is SIGKILLed on expiry and the result is marked failed. */
85
+ export const spawnProbe: ProbeRunner = async (argv, options) => {
86
+ let child: ReturnType<typeof Bun.spawn>;
87
+ try {
88
+ child = Bun.spawn(argv, { stdout: "pipe", stderr: "pipe", stdin: "ignore", cwd: options.cwd });
89
+ } catch (error) {
90
+ return { ok: false, timedOut: false, exitCode: null, stdout: "", stderr: "", error: error instanceof Error ? error.message : String(error) };
91
+ }
92
+ let timedOut = false;
93
+ const timer = setTimeout(() => {
94
+ timedOut = true;
95
+ try { child.kill("SIGKILL"); } catch { /* already gone */ }
96
+ }, options.timeoutMs);
97
+ const stdoutText = new Response(child.stdout as ReadableStream).text();
98
+ const stderrText = new Response(child.stderr as ReadableStream).text();
99
+ try {
100
+ const exitCode = await child.exited;
101
+ // A grandchild may keep the pipes open after the kill; never wait on them indefinitely.
102
+ const [stdout, stderr] = await Promise.all([
103
+ settleWithin(stdoutText, timedOut ? PIPE_DRAIN_MS : 5_000, ""),
104
+ settleWithin(stderrText, timedOut ? PIPE_DRAIN_MS : 5_000, ""),
105
+ ]);
106
+ if (timedOut) {
107
+ return { ok: false, timedOut: true, exitCode: null, stdout: stdout.trim(), stderr: stderr.trim(), error: `timed out after ${options.timeoutMs} ms and was killed` };
108
+ }
109
+ return { ok: exitCode === 0, timedOut: false, exitCode, stdout: stdout.trim(), stderr: stderr.trim(), error: exitCode === 0 ? undefined : `exit code ${exitCode}` };
110
+ } catch (error) {
111
+ return { ok: false, timedOut, exitCode: null, stdout: "", stderr: "", error: error instanceof Error ? error.message : String(error) };
112
+ } finally {
113
+ clearTimeout(timer);
114
+ }
115
+ };
116
+
117
+ /** Wraps a probe so that even a misbehaving runner cannot block the caller past its deadline. */
118
+ async function guardedProbe(probe: ProbeRunner, argv: string[], options: ProbeOptions): Promise<ProbeResult> {
119
+ const deadline = options.timeoutMs + Math.min(2_000, Math.max(50, options.timeoutMs));
120
+ const fallback: ProbeResult = { ok: false, timedOut: true, exitCode: null, stdout: "", stderr: "", error: `timed out after ${options.timeoutMs} ms` };
121
+ try {
122
+ const result = await settleWithin(probe(argv, options), deadline, fallback);
123
+ // A timed-out probe can never count as a pass, whatever the runner claims.
124
+ return result.timedOut ? { ...result, ok: false } : result;
125
+ } catch (error) {
126
+ return { ...fallback, timedOut: false, error: error instanceof Error ? error.message : String(error) };
127
+ }
128
+ }
129
+
130
+ function probeFailure(result: ProbeResult): string {
131
+ const detail = (result.stderr || result.stdout).split("\n")[0]?.trim();
132
+ return [result.error, detail].filter(Boolean).join(": ") || "probe failed";
133
+ }
134
+
135
+ export async function verifyTestRuntime(runtimeRoot: string): Promise<{ ok: boolean; message: string; playwrightVersion?: string }> {
136
+ const manifestPath = path.join(runtimeRoot, "runtime-manifest.json");
137
+ try {
138
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as Record<string, unknown>;
139
+ const files = manifest.files && typeof manifest.files === "object" ? manifest.files as Record<string, unknown> : {};
140
+ const errors: string[] = [];
141
+ if (manifest.schema_version !== 1) errors.push("schema_version must be 1");
142
+ if (typeof manifest.runtime_version !== "string" || !manifest.runtime_version) errors.push("runtime_version is missing");
143
+ if (typeof manifest.playwright_version !== "string" || !/^\d+\.\d+\.\d+$/.test(manifest.playwright_version)) errors.push("playwright_version is invalid");
144
+ for (const name of RUNTIME_FILES) {
145
+ const expected = files[name];
146
+ const target = path.join(runtimeRoot, name);
147
+ const stat = await lstat(target).catch(() => null);
148
+ if (typeof expected !== "string" || !SHA256.test(expected)) errors.push(`manifest does not pin ${name}`);
149
+ else if (!stat?.isFile() || stat.isSymbolicLink()) errors.push(`runtime file is missing or unsafe: ${name}`);
150
+ else if (createHash("sha256").update(await readFile(target)).digest("hex") !== expected) errors.push(`runtime file hash mismatch: ${name}`);
151
+ }
152
+ if (!errors.length) {
153
+ const reporter = await readFile(path.join(runtimeRoot, "reporter.mjs"), "utf8");
154
+ const embedded = /RUNTIME_VERSION\s*=\s*["']([^"']+)["']/.exec(reporter)?.[1];
155
+ if (embedded !== manifest.runtime_version) errors.push(`runtime_version ${String(manifest.runtime_version)} does not match reporter ${String(embedded)}`);
156
+ }
157
+ return { ok: !errors.length, message: errors.length ? errors.join("; ") : manifestPath, playwrightVersion: typeof manifest.playwright_version === "string" ? manifest.playwright_version : undefined };
158
+ } catch (error) {
159
+ return { ok: false, message: `invalid manifest: ${error instanceof Error ? error.message : String(error)}` };
160
+ }
161
+ }
162
+
163
+ export function playwrightVersionMatches(installed: string, manifest: string | undefined): boolean {
164
+ return /^\d+\.\d+\.\d+$/.test(installed) && installed === manifest;
165
+ }
166
+
167
+ function exactOrigin(value: unknown): string | null {
168
+ if (typeof value !== "string") return null;
169
+ try {
170
+ const url = new URL(value);
171
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) return null;
172
+ return `${url.protocol}//${url.host}` === value ? value : null;
173
+ } catch { return null; }
174
+ }
175
+
176
+ export async function validateEnvironmentProfile(file: string): Promise<string[]> {
177
+ try {
178
+ const stat = await lstat(file);
179
+ if (!stat.isFile() || stat.isSymbolicLink()) return ["profile must be a regular non-symlink file"];
180
+ const profile = JSON.parse(await readFile(file, "utf8")) as Record<string, unknown>;
181
+ const name = path.basename(file, ".json");
182
+ const errors: string[] = [];
183
+ if (!profile || typeof profile !== "object" || Array.isArray(profile)) return ["profile root must be an object"];
184
+ if (!PROFILE_NAME.test(name)) errors.push(`invalid environment profile name: ${name}`);
185
+ for (const key of Object.keys(profile)) if (!PROFILE_FIELDS.has(key)) errors.push(`profile field is not allowed: ${key}`);
186
+ if (profile.profile !== name) errors.push("profile field must equal file name");
187
+ for (const key of ["navigation_origins", "api_origins"] as const) {
188
+ const values = key === "api_origins" && !(key in profile) ? [] : profile[key];
189
+ if (!Array.isArray(values) || (key === "navigation_origins" && !values.length)) errors.push(`${key} must be ${key === "navigation_origins" ? "a non-empty " : "an "}array`);
190
+ else {
191
+ if (values.some(value => !exactOrigin(value))) errors.push(`${key} contains an invalid origin`);
192
+ if (new Set(values).size !== values.length) errors.push(`${key} contains duplicate origins`);
193
+ }
194
+ }
195
+ const navigation = Array.isArray(profile.navigation_origins) ? profile.navigation_origins : [];
196
+ const api = Array.isArray(profile.api_origins) ? profile.api_origins : [];
197
+ for (const [key, allowed] of [["base_url", navigation], ["api_base_url", api]] as const) {
198
+ if (key === "api_base_url" && !(key in profile)) continue;
199
+ if (typeof profile[key] !== "string") errors.push(`${key} must be an absolute http(s) URL`);
200
+ else {
201
+ try { if (!allowed.includes(new URL(profile[key]).origin)) errors.push(`${key} origin is not declared`); }
202
+ catch { errors.push(`${key} must be an absolute http(s) URL`); }
203
+ }
204
+ }
205
+ const subresources = profile.subresources ?? { policy: "same-site-plus-declared", allowed_origins: [] };
206
+ if (!subresources || typeof subresources !== "object" || Array.isArray(subresources)) errors.push("subresources must be an object");
207
+ else {
208
+ const value = subresources as Record<string, unknown>;
209
+ if (Object.keys(value).some(key => !["policy", "allowed_origins"].includes(key))) errors.push("subresources contains an unsupported field");
210
+ if (!["same-site-plus-declared", "declared-only"].includes(String(value.policy))) errors.push("subresources.policy is invalid");
211
+ const allowed = value.allowed_origins;
212
+ if (!Array.isArray(allowed) || allowed.some(origin => !exactOrigin(origin))) errors.push("subresources.allowed_origins contains an invalid origin");
213
+ }
214
+ const secrets = profile.secret_env ?? {};
215
+ if (!secrets || typeof secrets !== "object" || Array.isArray(secrets)) errors.push("secret_env must be an object");
216
+ else for (const [key, value] of Object.entries(secrets)) {
217
+ if (!/^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(key)) errors.push(`secret_env key is invalid: ${key}`);
218
+ if (typeof value !== "string" || !/^[A-Z_][A-Z0-9_]{0,127}$/.test(value)) errors.push(`secret_env.${key} must name an upper-case environment variable`);
219
+ else if (RESERVED_ENV.has(value) || RESERVED_ENV_PREFIXES.some(prefix => value.startsWith(prefix))) errors.push(`secret_env.${key} uses a reserved environment variable`);
220
+ }
221
+ for (const key of ["trace", "screenshots"] as const) if (key in profile && !["on_failure", "off"].includes(String(profile[key]))) errors.push(`${key} is invalid`);
222
+ return errors;
223
+ } catch (error) {
224
+ return [`invalid profile: ${error instanceof Error ? error.message : String(error)}`];
225
+ }
226
+ }
227
+
228
+ export async function runHostDoctor(project: ProjectContext, options: DoctorOptions = {}): Promise<HostDoctorReport> {
34
229
  const checks: DoctorCheck[] = [];
35
230
  const add = (id: string, ok: boolean, message: string) => checks.push({ id, ok, message });
231
+ const probe = options.probe ?? spawnProbe;
232
+ const versionMs = options.timeouts?.versionMs ?? DEFAULT_VERSION_TIMEOUT_MS;
233
+ const browserMs = options.timeouts?.browserLaunchMs ?? DEFAULT_BROWSER_LAUNCH_TIMEOUT_MS;
36
234
  add("branch-runtime", true, `devflow-pi ${PRODUCT_VERSION}; OMP SDK ${OMP_RUNTIME_VERSION}`);
37
235
  add("bun", bunVersionOk(Bun.version), bunVersionOk(Bun.version) ? Bun.version : `${Bun.version}; requires >=1.3.14`);
236
+ const node = await guardedProbe(probe, ["node", "--version"], { timeoutMs: versionMs });
237
+ const nodeOk = node.ok && majorVersion(node.stdout) >= 20;
238
+ add("node", nodeOk, node.ok ? `${node.stdout}; requires >=20` : `missing or unusable: ${probeFailure(node)}`);
38
239
  add("project-root", path.isAbsolute(project.root), project.root);
39
240
  add("project-id", Boolean(project.manifest.projectId), project.manifest.projectId);
40
241
  const planningStat = await lstat(project.planningRoot).catch(() => null);
@@ -45,6 +246,62 @@ export async function runHostDoctor(project: ProjectContext): Promise<HostDoctor
45
246
  const root = roots[index];
46
247
  add(`plugin:${plugin}`, await exists(path.join(root, "skills")), root);
47
248
  }
249
+ const qualityRoot = roots[DEVFLOW_PLUGIN_NAMES.indexOf("devflow-quality")];
250
+ // Never fall back to a relative path: verifyTestRuntime("") would read ./runtime-manifest.json from the cwd.
251
+ const runtimeRoot = qualityRoot ? path.join(qualityRoot, "runtime", "playwright") : null;
252
+ const runtime = runtimeRoot
253
+ ? await verifyTestRuntime(runtimeRoot)
254
+ : { ok: false, message: "devflow-quality plugin root is unknown", playwrightVersion: undefined };
255
+ add("test-runtime-manifest", runtime.ok, runtime.message);
256
+ let installedPlaywright: ProbeResult | null = null;
257
+ if (runtime.ok && runtimeRoot) {
258
+ installedPlaywright = await guardedProbe(
259
+ probe,
260
+ ["node", "-e", "process.stdout.write(require(require.resolve('@playwright/test/package.json',{paths:[process.argv[1]]})).version)", runtimeRoot],
261
+ { timeoutMs: versionMs },
262
+ );
263
+ }
264
+ const versionOk = Boolean(installedPlaywright?.ok && playwrightVersionMatches(installedPlaywright.stdout, runtime.playwrightVersion));
265
+ add("test-runtime-playwright-version", versionOk,
266
+ !installedPlaywright ? "skipped: Runtime manifest is invalid"
267
+ : !installedPlaywright.ok ? `Playwright version probe failed: ${probeFailure(installedPlaywright)}`
268
+ : `${installedPlaywright.stdout}; manifest ${runtime.playwrightVersion}${versionOk ? "" : " (mismatch)"}`);
269
+ let chromiumOk = false;
270
+ let chromiumMessage = "not checked: Node >=20 and a valid Runtime manifest are required";
271
+ if (nodeOk && runtime.ok && runtimeRoot) {
272
+ const launch = await guardedProbe(
273
+ probe,
274
+ ["node", "-e", `const {chromium}=require(require.resolve('@playwright/test',{paths:[process.argv[1]]}));chromium.launch({timeout:${Math.max(1000, browserMs - 2000)}}).then(b=>b.close()).then(()=>process.exit(0),e=>{console.error(e.message);process.exit(1)})`, runtimeRoot],
275
+ { timeoutMs: browserMs },
276
+ );
277
+ chromiumOk = launch.ok && !launch.timedOut;
278
+ chromiumMessage = chromiumOk ? "launchable"
279
+ : launch.timedOut ? `Chromium launch timed out after ${browserMs} ms and was killed; the browser is not launchable`
280
+ : `Chromium is not launchable: ${probeFailure(launch)}`;
281
+ }
282
+ add("test-runtime-chromium", chromiumOk, chromiumMessage);
283
+ const environmentRoot = path.join(project.planningRoot, "test-environments");
284
+ const environmentStat = await lstat(environmentRoot).catch(() => null);
285
+ let profiles: string[] = [];
286
+ if (environmentStat?.isDirectory() && !environmentStat.isSymbolicLink()) {
287
+ profiles = (await readdir(environmentRoot)).filter(name => name.endsWith(".json") && !name.endsWith(".example.json"));
288
+ }
289
+ const profileErrors: string[] = [];
290
+ for (const profile of profiles) for (const error of await validateEnvironmentProfile(path.join(environmentRoot, profile))) profileErrors.push(`${profile}: ${error}`);
291
+ add("test-environments", profiles.length > 0 && !profileErrors.length,
292
+ !profiles.length ? `${environmentRoot}: configure a profile from local.example.json` : profileErrors.length ? profileErrors.join("; ") : `${environmentRoot}: ${profiles.join(", ")}`);
293
+ const runsRoot = path.join(project.root, ".devflow", "runs");
294
+ const runsStat = await lstat(runsRoot).catch(() => null);
295
+ if (!runsStat?.isDirectory() || runsStat.isSymbolicLink()) {
296
+ add("test-runs-root", false, `${runsRoot}: missing, not a directory, or a symlink`);
297
+ } else {
298
+ // Run directories hold traces, screenshots and result files and must never enter source control.
299
+ const ignored = await guardedProbe(probe, ["git", "-C", project.root, "check-ignore", "-q", "--no-index", ".devflow/runs/doctor-probe/x"], { timeoutMs: versionMs });
300
+ if (ignored.ok) add("test-runs-root", true, `${runsRoot} (git-ignored)`);
301
+ else if (ignored.exitCode === 1) add("test-runs-root", false, `${runsRoot} is not git-ignored; add .devflow/runs/ to .gitignore (run artifacts may contain secrets)`);
302
+ else if (ignored.exitCode === 128 && /not a git repository/i.test(ignored.stderr)) add("test-runs-root", true, `${runsRoot} (project is not a git repository; ignore rule not applicable)`);
303
+ else add("test-runs-root", false, `cannot verify that ${runsRoot} is git-ignored: ${probeFailure(ignored)}`);
304
+ }
48
305
  const sdkManifest = ompSdkManifestPath();
49
306
  let installedVersion = "missing";
50
307
  if (await exists(sdkManifest)) {
@@ -5,8 +5,9 @@ import path from "node:path";
5
5
  import { Database } from "bun:sqlite";
6
6
 
7
7
  import { projectControlDatabasePath, projectDataDir } from "./paths.ts";
8
+ import { DEVFLOW_SCAN_EXCLUDES, PLANNING_DIRECTORY } from "./project-layout.ts";
8
9
 
9
- export const INITIALIZATION_SCHEMA_VERSION = 2;
10
+ export const INITIALIZATION_SCHEMA_VERSION = 3;
10
11
  export const FACT_KINDS = [
11
12
  "asset", "technology", "api", "page", "data_model", "integration",
12
13
  "rule", "toolchain", "evidence", "capability",
@@ -198,6 +199,48 @@ WHEN EXISTS(SELECT 1 FROM initialization_runs r WHERE r.id=OLD.run_id AND r.stat
198
199
  BEGIN SELECT RAISE(ABORT, 'published facts are immutable'); END;
199
200
  `;
200
201
 
202
+ // Human approval records (schema v3). The column contract is shared with the
203
+ // Python reader side; the triggers below make the records append-only except
204
+ // for the Core-owned proposal lifecycle and approval supersession.
205
+ export const APPROVAL_SCHEMA = `
206
+ CREATE TABLE IF NOT EXISTS approval_proposals(
207
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL, feature TEXT NOT NULL,
208
+ kind TEXT NOT NULL CHECK(kind IN ('execution_plan','environment_profile','mode_override','human_result')),
209
+ subject_key TEXT NOT NULL, payload_json TEXT NOT NULL, pins_json TEXT NOT NULL,
210
+ state_revision INTEGER NOT NULL, status TEXT NOT NULL CHECK(status IN ('proposed','consumed','rejected','expired')),
211
+ created_at TEXT NOT NULL, resolved_at TEXT);
212
+ CREATE TABLE IF NOT EXISTS approvals(
213
+ id TEXT PRIMARY KEY, proposal_id TEXT NOT NULL UNIQUE REFERENCES approval_proposals(id),
214
+ project_id TEXT NOT NULL, feature TEXT NOT NULL, kind TEXT NOT NULL, subject_key TEXT NOT NULL,
215
+ pins_json TEXT NOT NULL, decision_json TEXT NOT NULL, approved_via TEXT NOT NULL CHECK(approved_via='runtime_ui_confirm'),
216
+ approved_at TEXT NOT NULL, superseded_at TEXT);
217
+ CREATE INDEX IF NOT EXISTS idx_approval_proposals_subject ON approval_proposals(project_id, feature, kind, subject_key, status);
218
+ CREATE INDEX IF NOT EXISTS idx_approvals_subject ON approvals(project_id, feature, kind, subject_key, superseded_at);
219
+ CREATE TRIGGER IF NOT EXISTS approval_proposal_lifecycle BEFORE UPDATE ON approval_proposals
220
+ WHEN OLD.status!='proposed' OR NEW.status='proposed' OR NEW.resolved_at IS NULL
221
+ OR NEW.id!=OLD.id OR NEW.project_id!=OLD.project_id OR NEW.feature!=OLD.feature OR NEW.kind!=OLD.kind
222
+ OR NEW.subject_key!=OLD.subject_key OR NEW.payload_json!=OLD.payload_json OR NEW.pins_json!=OLD.pins_json
223
+ OR NEW.state_revision!=OLD.state_revision OR NEW.created_at!=OLD.created_at
224
+ BEGIN SELECT RAISE(ABORT, 'approval proposals only transition once from proposed'); END;
225
+ CREATE TRIGGER IF NOT EXISTS approval_proposal_delete BEFORE DELETE ON approval_proposals
226
+ BEGIN SELECT RAISE(ABORT, 'approval proposals are append-only'); END;
227
+ CREATE TRIGGER IF NOT EXISTS approval_requires_open_proposal BEFORE INSERT ON approvals
228
+ WHEN NOT EXISTS(
229
+ SELECT 1 FROM approval_proposals p
230
+ WHERE p.id=NEW.proposal_id AND p.status='proposed' AND p.project_id=NEW.project_id AND p.feature=NEW.feature
231
+ AND p.kind=NEW.kind AND p.subject_key=NEW.subject_key AND p.pins_json=NEW.pins_json
232
+ )
233
+ BEGIN SELECT RAISE(ABORT, 'approval does not match an open proposal'); END;
234
+ CREATE TRIGGER IF NOT EXISTS approval_supersede_only BEFORE UPDATE ON approvals
235
+ WHEN OLD.superseded_at IS NOT NULL OR NEW.superseded_at IS NULL
236
+ OR NEW.id!=OLD.id OR NEW.proposal_id!=OLD.proposal_id OR NEW.project_id!=OLD.project_id OR NEW.feature!=OLD.feature
237
+ OR NEW.kind!=OLD.kind OR NEW.subject_key!=OLD.subject_key OR NEW.pins_json!=OLD.pins_json
238
+ OR NEW.decision_json!=OLD.decision_json OR NEW.approved_via!=OLD.approved_via OR NEW.approved_at!=OLD.approved_at
239
+ BEGIN SELECT RAISE(ABORT, 'approvals are immutable except supersession'); END;
240
+ CREATE TRIGGER IF NOT EXISTS approval_delete BEFORE DELETE ON approvals
241
+ BEGIN SELECT RAISE(ABORT, 'approvals are append-only'); END;
242
+ `;
243
+
201
244
  function now(): string { return new Date().toISOString(); }
202
245
  function canonicalJson(value: unknown): string {
203
246
  if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
@@ -267,6 +310,7 @@ export class InitializationStore {
267
310
  }
268
311
  const migrate = db.transaction(() => {
269
312
  db.exec(SCHEMA);
313
+ db.exec(APPROVAL_SCHEMA);
270
314
  const ensureColumn = (table: string, column: string, definition: string) => {
271
315
  const columns = db.query(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
272
316
  if (!columns.some(item => item.name === column)) db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
@@ -286,6 +330,10 @@ export class InitializationStore {
286
330
  db.exec("CREATE INDEX IF NOT EXISTS idx_document_jobs_status ON document_jobs(status, updated_at)");
287
331
  version = 2;
288
332
  }
333
+ if (version < 3) {
334
+ // v3 only adds the approval tables created above; existing rows are untouched.
335
+ version = 3;
336
+ }
289
337
  db.query("UPDATE schema_meta SET version=?").run(version);
290
338
  }
291
339
  const verified = db.query("SELECT version FROM schema_meta LIMIT 1").get() as { version: number };
@@ -323,7 +371,7 @@ export class InitializationStore {
323
371
  }));
324
372
  const requiredKinds = input.requiredKinds ?? [...FACT_KINDS];
325
373
  if (input.mode === "fast" && requiredKinds.length === FACT_KINDS.length) throw new Error("Fast mode must explicitly limit required fact kinds");
326
- const config = { ...input, repositories, requiredKinds, includes: input.includes ?? ["**/*"], excludes: input.excludes ?? [".git/**", ".planning/**", ".devflow/**"], adapterVersions: input.adapterVersions ?? {} };
374
+ const config = { ...input, repositories, requiredKinds, includes: input.includes ?? ["**/*"], excludes: [...new Set([...DEVFLOW_SCAN_EXCLUDES, ...(input.excludes ?? [])])], adapterVersions: input.adapterVersions ?? {} };
327
375
  const configJson = canonicalJson(config);
328
376
  const contentHash = hash(configJson);
329
377
  const existing = this.db.query("SELECT id, revision FROM scan_configs WHERE project_id=? AND content_hash=?").get(this.context.manifest.projectId, contentHash) as { id: string; revision: number } | null;
@@ -510,7 +558,7 @@ export class InitializationStore {
510
558
  if (options.indexRef.status === "complete" && (typeof options.indexRef.generation !== "string" || typeof options.indexRef.snapshotHash !== "string")) throw new Error("Complete index references require generation and snapshot hash");
511
559
  if (options.indexRef.status === "complete") {
512
560
  if (typeof options.indexRef.contentManifestHash !== "string" || typeof options.indexRef.repositoryRevision !== "string") throw new Error("Complete index references require content manifest and repository revision");
513
- const indexRoot = path.resolve(this.context.root, String(options.indexRef.stagingPath ?? options.indexRef.path ?? ".planning/code-index"));
561
+ const indexRoot = path.resolve(this.context.root, String(options.indexRef.stagingPath ?? options.indexRef.path ?? `${PLANNING_DIRECTORY}/code-index`));
514
562
  const immutable = path.resolve(indexRoot, String(options.indexRef.immutablePath ?? `snapshots/${options.indexRef.generation}`));
515
563
  const snapshotFile = path.join(immutable, "SNAPSHOT.json");
516
564
  if (!existsSync(snapshotFile)) throw new Error("Complete index reference does not point to an immutable generation");
package/src/paths.ts CHANGED
@@ -8,8 +8,19 @@ export function packageRoot(): string {
8
8
  return path.resolve(import.meta.dir, "..");
9
9
  }
10
10
 
11
+ /**
12
+ * DEVFLOW_HOME rule (shared with the Python readers and hooks): unset, empty or whitespace-only
13
+ * falls back to ~/.devflow-pi; otherwise a leading `~` is expanded and the path made absolute
14
+ * (path.resolve semantics, existence not required).
15
+ */
16
+ export function normalizeDevflowHome(value: string | undefined): string {
17
+ if (value === undefined || value.trim() === "") return path.resolve(path.join(os.homedir(), ".devflow-pi"));
18
+ const expanded = value === "~" || value.startsWith("~/") ? os.homedir() + value.slice(1) : value;
19
+ return path.resolve(expanded);
20
+ }
21
+
11
22
  export function devflowHome(): string {
12
- return path.resolve(process.env.DEVFLOW_HOME || path.join(os.homedir(), ".devflow-pi"));
23
+ return normalizeDevflowHome(process.env.DEVFLOW_HOME);
13
24
  }
14
25
 
15
26
  export function devflowAgentDir(): string {
@@ -5,9 +5,11 @@ import path from "node:path";
5
5
  import { PROJECT_SCHEMA_VERSION } from "./constants.ts";
6
6
  import { InitializationStore } from "./initialization-store.ts";
7
7
  import { projectControlDatabasePath } from "./paths.ts";
8
+ import { DEVFLOW_MANIFEST, migrateLegacyProjectLayout, PLANNING_DIRECTORY, projectLayout } from "./project-layout.ts";
8
9
 
9
10
  export interface ProjectManifest {
10
11
  schemaVersion: number;
12
+ layoutVersion: number;
11
13
  projectId: string;
12
14
  name: string;
13
15
  createdAt: string;
@@ -23,11 +25,12 @@ export interface ProjectContext {
23
25
  }
24
26
 
25
27
  const PROJECT_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
28
+ const PROJECT_LAYOUT_VERSION = 2;
26
29
  const CONTROL_BOUNDARY_PATHS = [
27
- ".planning/STATE.md",
28
- ".planning/.devflow.lock",
29
- ".planning/workflows",
30
- ".planning/artifacts",
30
+ `${PLANNING_DIRECTORY}/STATE.md`,
31
+ `${PLANNING_DIRECTORY}/.devflow.lock`,
32
+ `${PLANNING_DIRECTORY}/workflows`,
33
+ `${PLANNING_DIRECTORY}/artifacts`,
31
34
  ];
32
35
 
33
36
  async function assertNoSymlinkDescendants(target: string): Promise<void> {
@@ -58,7 +61,7 @@ async function canonicalDirectory(candidate: string): Promise<string> {
58
61
  async function nearestManifest(start: string): Promise<string | null> {
59
62
  let current = await canonicalDirectory(start);
60
63
  while (true) {
61
- const manifest = path.join(current, ".devflow", "project.json");
64
+ const manifest = path.join(current, DEVFLOW_MANIFEST);
62
65
  if (await exists(manifest)) return current;
63
66
  const parent = path.dirname(current);
64
67
  if (parent === current) return null;
@@ -95,18 +98,21 @@ function validateManifest(value: unknown, manifestPath: string): ProjectManifest
95
98
  if (record.schemaVersion !== PROJECT_SCHEMA_VERSION) {
96
99
  throw new Error(`Unsupported project schema in ${manifestPath}: ${String(record.schemaVersion)}`);
97
100
  }
101
+ if (record.layoutVersion !== undefined && record.layoutVersion !== 1 && record.layoutVersion !== PROJECT_LAYOUT_VERSION) {
102
+ throw new Error(`Unsupported project layout in ${manifestPath}: ${String(record.layoutVersion)}`);
103
+ }
98
104
  if (typeof record.projectId !== "string" || !PROJECT_ID_PATTERN.test(record.projectId)) {
99
105
  throw new Error(`Invalid projectId in ${manifestPath}`);
100
106
  }
101
107
  if (typeof record.name !== "string" || !record.name || typeof record.createdAt !== "string") {
102
108
  throw new Error(`Incomplete project manifest: ${manifestPath}`);
103
109
  }
104
- return record as unknown as ProjectManifest;
110
+ return { ...record, layoutVersion: Number(record.layoutVersion ?? 1) } as unknown as ProjectManifest;
105
111
  }
106
112
 
107
113
  export async function assertPlanningBoundary(root: string): Promise<string> {
108
114
  const canonicalRoot = await canonicalDirectory(root);
109
- const planningRoot = path.join(canonicalRoot, ".planning");
115
+ const planningRoot = projectLayout(canonicalRoot).planningRoot;
110
116
  const stat = await lstat(planningRoot).catch(() => null);
111
117
  if (stat?.isSymbolicLink()) throw new Error(`Refusing symlinked planning root: ${planningRoot}`);
112
118
  if (stat && !stat.isDirectory()) throw new Error(`Planning root is not a directory: ${planningRoot}`);
@@ -133,8 +139,16 @@ export async function assertPlanningBoundary(root: string): Promise<string> {
133
139
 
134
140
  export async function loadProjectContext(root: string): Promise<ProjectContext> {
135
141
  const canonicalRoot = await canonicalDirectory(root);
136
- const manifestPath = path.join(canonicalRoot, ".devflow", "project.json");
137
- const manifest = validateManifest(JSON.parse(await readFile(manifestPath, "utf8")), manifestPath);
142
+ const manifestPath = projectLayout(canonicalRoot).manifestPath;
143
+ const manifestValue = JSON.parse(await readFile(manifestPath, "utf8")) as Record<string, unknown>;
144
+ validateManifest(manifestValue, manifestPath);
145
+ await migrateLegacyProjectLayout(canonicalRoot);
146
+ if (manifestValue.layoutVersion !== PROJECT_LAYOUT_VERSION) {
147
+ manifestValue.layoutVersion = PROJECT_LAYOUT_VERSION;
148
+ await atomicWriteJson(manifestPath, manifestValue);
149
+ }
150
+ const manifest = validateManifest(manifestValue, manifestPath);
151
+ await mkdir(projectLayout(canonicalRoot).planningRoot, { recursive: true });
138
152
  const planningRoot = await assertPlanningBoundary(canonicalRoot);
139
153
  if (!(await exists(projectControlDatabasePath(manifest.projectId)))) throw new Error("Project initialization database is not registered; run explicit project initialization first");
140
154
  return attachInitializationStore({ root: canonicalRoot, manifestPath, manifest, planningRoot });
@@ -158,24 +172,55 @@ async function atomicWriteJson(target: string, value: unknown): Promise<void> {
158
172
  await rename(temporary, target);
159
173
  }
160
174
 
175
+ async function ensureTestRuntimeLayout(root: string, planningRoot: string): Promise<void> {
176
+ const environments = path.join(planningRoot, "test-environments");
177
+ const runs = path.join(root, ".devflow", "runs");
178
+ await mkdir(environments, { recursive: true });
179
+ await mkdir(runs, { recursive: true });
180
+ const example = path.join(environments, "local.example.json");
181
+ if (!(await exists(example))) {
182
+ await atomicWriteJson(example, {
183
+ profile: "local",
184
+ base_url: "http://127.0.0.1:3000",
185
+ navigation_origins: ["http://127.0.0.1:3000"],
186
+ api_origins: ["http://127.0.0.1:3000"],
187
+ subresources: { policy: "same-site-plus-declared", allowed_origins: [] },
188
+ secret_env: {},
189
+ trace: "on_failure",
190
+ screenshots: "on_failure",
191
+ });
192
+ }
193
+ }
194
+
161
195
  export async function initializeProject(root: string): Promise<{ context: ProjectContext; created: boolean }> {
162
196
  const canonicalRoot = await canonicalDirectory(root);
163
- const devflowDir = path.join(canonicalRoot, ".devflow");
164
- const planningRoot = path.join(canonicalRoot, ".planning");
165
- await assertPlanningBoundary(canonicalRoot);
197
+ const { devflowRoot: devflowDir, manifestPath, planningRoot } = projectLayout(canonicalRoot);
166
198
  await mkdir(devflowDir, { recursive: true });
167
- await mkdir(planningRoot, { recursive: true });
168
- const manifestPath = path.join(devflowDir, "project.json");
199
+ await assertPlanningBoundary(canonicalRoot);
169
200
  if (await exists(manifestPath)) {
170
- return { context: await loadProjectContext(canonicalRoot), created: false };
201
+ const context = await loadProjectContext(canonicalRoot);
202
+ await ensureTestRuntimeLayout(canonicalRoot, context.planningRoot);
203
+ return { context, created: false };
171
204
  }
172
205
  const manifest: ProjectManifest = {
173
206
  schemaVersion: PROJECT_SCHEMA_VERSION,
207
+ // Establish a v1-compatible pending identity while legacy material is
208
+ // assessed; only commit v2 after migration succeeds.
209
+ layoutVersion: 1,
174
210
  projectId: randomUUID(),
175
211
  name: path.basename(canonicalRoot),
176
212
  createdAt: new Date().toISOString(),
177
213
  };
178
214
  await atomicWriteJson(manifestPath, manifest);
215
+ // Explicit initialization establishes project identity before attempting a
216
+ // legacy-layout migration. This prevents a tracked v1 project with no local
217
+ // manifest from being permanently skipped, while active/conflicting legacy
218
+ // control state still fails closed in migrateLegacyProjectLayout.
219
+ await migrateLegacyProjectLayout(canonicalRoot);
220
+ manifest.layoutVersion = PROJECT_LAYOUT_VERSION;
221
+ await atomicWriteJson(manifestPath, manifest);
222
+ await mkdir(planningRoot, { recursive: true });
223
+ await ensureTestRuntimeLayout(canonicalRoot, planningRoot);
179
224
  const bootstrapStore = await InitializationStore.open({ root: canonicalRoot, manifest });
180
225
  try { await bootstrapStore.registerWorkspace(); } finally { bootstrapStore.close(); }
181
226
  return {