@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.
- package/README.md +10 -10
- package/node_modules/@playwright/test/LICENSE +202 -0
- package/node_modules/@playwright/test/NOTICE +5 -0
- package/node_modules/@playwright/test/README.md +318 -0
- package/node_modules/@playwright/test/cli.js +19 -0
- package/node_modules/@playwright/test/index.d.ts +18 -0
- package/node_modules/@playwright/test/index.js +17 -0
- package/node_modules/@playwright/test/index.mjs +18 -0
- package/node_modules/@playwright/test/package.json +35 -0
- package/node_modules/@playwright/test/reporter.d.ts +17 -0
- package/node_modules/@playwright/test/reporter.js +17 -0
- package/node_modules/@playwright/test/reporter.mjs +17 -0
- package/node_modules/playwright/LICENSE +202 -0
- package/node_modules/playwright/NOTICE +5 -0
- package/node_modules/playwright/README.md +318 -0
- package/node_modules/playwright/ThirdPartyNotices.txt +14 -0
- package/node_modules/playwright/cli.js +19 -0
- package/node_modules/playwright/index.d.ts +17 -0
- package/node_modules/playwright/index.js +17 -0
- package/node_modules/playwright/index.mjs +18 -0
- package/node_modules/playwright/jsx-runtime.js +42 -0
- package/node_modules/playwright/jsx-runtime.mjs +21 -0
- package/node_modules/playwright/lib/agents/agentParser.js +89 -0
- package/node_modules/playwright/lib/agents/copilot-setup-steps.yml +34 -0
- package/node_modules/playwright/lib/agents/generateAgents.js +395 -0
- package/node_modules/playwright/lib/agents/playwright-test-coverage.prompt.md +31 -0
- package/node_modules/playwright/lib/agents/playwright-test-generate.prompt.md +8 -0
- package/node_modules/playwright/lib/agents/playwright-test-generator.agent.md +88 -0
- package/node_modules/playwright/lib/agents/playwright-test-heal.prompt.md +6 -0
- package/node_modules/playwright/lib/agents/playwright-test-healer.agent.md +56 -0
- package/node_modules/playwright/lib/agents/playwright-test-plan.prompt.md +9 -0
- package/node_modules/playwright/lib/agents/playwright-test-planner.agent.md +74 -0
- package/node_modules/playwright/lib/cli/reportActions.js +78 -0
- package/node_modules/playwright/lib/cli/testActions.js +214 -0
- package/node_modules/playwright/lib/common/index.js +2923 -0
- package/node_modules/playwright/lib/common/index.js.txt +34 -0
- package/node_modules/playwright/lib/errorContext.js +129 -0
- package/node_modules/playwright/lib/globals.js +58 -0
- package/node_modules/playwright/lib/index.js +815 -0
- package/node_modules/playwright/lib/isomorphic.js +260 -0
- package/node_modules/playwright/lib/isomorphic.js.txt +9 -0
- package/node_modules/playwright/lib/loader/loaderProcessEntry.js +34 -0
- package/node_modules/playwright/lib/loader/loaderProcessEntry.js.txt +9 -0
- package/node_modules/playwright/lib/matchers/expect.js +13490 -0
- package/node_modules/playwright/lib/matchers/expect.js.LICENSE +640 -0
- package/node_modules/playwright/lib/matchers/expect.js.txt +67 -0
- package/node_modules/playwright/lib/mcp/test/browserBackend.js +135 -0
- package/node_modules/playwright/lib/mcp/test/generatorTools.js +122 -0
- package/node_modules/playwright/lib/mcp/test/plannerTools.js +150 -0
- package/node_modules/playwright/lib/mcp/test/seed.js +82 -0
- package/node_modules/playwright/lib/mcp/test/streams.js +44 -0
- package/node_modules/playwright/lib/mcp/test/testBackend.js +99 -0
- package/node_modules/playwright/lib/mcp/test/testContext.js +306 -0
- package/node_modules/playwright/lib/mcp/test/testTool.js +30 -0
- package/node_modules/playwright/lib/mcp/test/testTools.js +98 -0
- package/node_modules/playwright/lib/package.js +47 -0
- package/node_modules/playwright/lib/program.js +241 -0
- package/node_modules/playwright/lib/runner/index.js +8527 -0
- package/node_modules/playwright/lib/runner/index.js.txt +61 -0
- package/node_modules/playwright/lib/transform/babelBundle.js +72274 -0
- package/node_modules/playwright/lib/transform/babelBundle.js.LICENSE +2384 -0
- package/node_modules/playwright/lib/transform/babelBundle.js.txt +325 -0
- package/node_modules/playwright/lib/transform/esmLoader.js +8054 -0
- package/node_modules/playwright/lib/transform/esmLoader.js.LICENSE +355 -0
- package/node_modules/playwright/lib/transform/esmLoader.js.txt +60 -0
- package/node_modules/playwright/lib/util.js +373 -0
- package/node_modules/playwright/lib/worker/workerProcessEntry.js +1861 -0
- package/node_modules/playwright/lib/worker/workerProcessEntry.js.txt +19 -0
- package/node_modules/playwright/package.json +55 -0
- package/node_modules/playwright/test.d.ts +18 -0
- package/node_modules/playwright/test.js +24 -0
- package/node_modules/playwright/test.mjs +35 -0
- package/node_modules/playwright/types/test.d.ts +11074 -0
- package/node_modules/playwright/types/testReporter.d.ts +950 -0
- package/node_modules/playwright-core/LICENSE +202 -0
- package/node_modules/playwright-core/NOTICE +5 -0
- package/node_modules/playwright-core/README.md +3 -0
- package/node_modules/playwright-core/ThirdPartyNotices.txt +13 -0
- package/node_modules/playwright-core/bin/install_media_pack.ps1 +5 -0
- package/node_modules/playwright-core/bin/install_webkit_wsl.ps1 +33 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh +42 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh +13 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 +24 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh +42 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh +12 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 +24 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh +48 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh +11 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 +23 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh +48 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh +11 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 +23 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh +48 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh +11 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 +24 -0
- package/node_modules/playwright-core/browsers.json +52 -0
- package/node_modules/playwright-core/cli.js +21 -0
- package/node_modules/playwright-core/index.d.ts +17 -0
- package/node_modules/playwright-core/index.js +17 -0
- package/node_modules/playwright-core/index.mjs +28 -0
- package/node_modules/playwright-core/lib/bootstrap.js +88 -0
- package/node_modules/playwright-core/lib/coreBundle.js +75813 -0
- package/node_modules/playwright-core/lib/entry/cliDaemon.js +5 -0
- package/node_modules/playwright-core/lib/entry/dashboardApp.js +3 -0
- package/node_modules/playwright-core/lib/entry/mcp.js +10 -0
- package/node_modules/playwright-core/lib/entry/oopBrowserDownload.js +3 -0
- package/node_modules/playwright-core/lib/package.js +50 -0
- package/node_modules/playwright-core/lib/server/chromium/appIcon.png +0 -0
- package/node_modules/playwright-core/lib/server/electron/loader.js +116 -0
- package/node_modules/playwright-core/lib/serverRegistry.js +1939 -0
- package/node_modules/playwright-core/lib/serverRegistry.js.LICENSE +65 -0
- package/node_modules/playwright-core/lib/tools/cli-client/channelSessions.js +141 -0
- package/node_modules/playwright-core/lib/tools/cli-client/cli.js +6 -0
- package/node_modules/playwright-core/lib/tools/cli-client/help.json +721 -0
- package/node_modules/playwright-core/lib/tools/cli-client/minimist.js +128 -0
- package/node_modules/playwright-core/lib/tools/cli-client/output.js +351 -0
- package/node_modules/playwright-core/lib/tools/cli-client/program.js +418 -0
- package/node_modules/playwright-core/lib/tools/cli-client/registry.js +180 -0
- package/node_modules/playwright-core/lib/tools/cli-client/session.js +259 -0
- package/node_modules/playwright-core/lib/tools/dashboard/appIcon.png +0 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/SKILL.md +425 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/element-attributes.md +23 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/request-mocking.md +87 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/running-code.md +241 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/session-management.md +225 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/storage-state.md +275 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/test-generation.md +433 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/tracing.md +139 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/video-recording.md +143 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/SKILL.md +136 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/migration.md +89 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/react.md +69 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/typing.md +173 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/vue.md +77 -0
- package/node_modules/playwright-core/lib/tools/skills/playwright-trace/SKILL.md +174 -0
- package/node_modules/playwright-core/lib/tools/utils/extension.js +112 -0
- package/node_modules/playwright-core/lib/tools/utils/socketConnection.js +108 -0
- package/node_modules/playwright-core/lib/utilsBundle.js +85357 -0
- package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +1890 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule--QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule-CQ8RZGBm.js +32 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-AwgwcYie.css +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-hRvO4yU2.js +12 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/safari-na3_-uQk.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/index.html +29 -0
- package/node_modules/playwright-core/lib/vite/dashboard/playwright-logo.svg +24 -0
- package/node_modules/playwright-core/lib/vite/htmlReport/index.html +16 -0
- package/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -0
- package/node_modules/playwright-core/lib/vite/htmlReport/report.js +32 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-CVQWJZAA.js +32 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-BZpYJZ-P.css +1 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-hrlLqLtq.js +129 -0
- package/node_modules/playwright-core/lib/vite/recorder/index.html +29 -0
- package/node_modules/playwright-core/lib/vite/recorder/playwright-logo.svg +9 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-BbkfBe3n.js +32 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Ds6CBOo0.js +182 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-DywYcAf8.js +7 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.Bqk9acqE.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.B4cLoZK3.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.html +44 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/manifest.webmanifest +16 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/playwright-logo.svg +9 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +10 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +4 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CU5KtEkS.js +5 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CyMfwkXJ.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +18 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
- package/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
- package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
- package/node_modules/playwright-core/lib/xdg-open +1267 -0
- package/node_modules/playwright-core/package.json +34 -0
- package/node_modules/playwright-core/types/protocol.d.ts +24645 -0
- package/node_modules/playwright-core/types/structs.d.ts +52 -0
- package/node_modules/playwright-core/types/types.d.ts +26632 -0
- package/package.json +12 -2
- package/plugins/devflow-core/extensions/devflow-core.ts +518 -40
- package/plugins/devflow-core/runtime/hooks/context_sync_gate.py +3 -3
- package/plugins/devflow-core/runtime/hooks/hook_common.py +334 -11
- package/plugins/devflow-core/runtime/hooks/index_gate.py +29 -16
- package/plugins/devflow-core/runtime/hooks/start.py +13 -13
- package/plugins/devflow-core/skills/wireframe-proposal/SKILL.md +2 -2
- package/plugins/devflow-core/skills/wireframe-validate/SKILL.md +4 -4
- package/plugins/devflow-core/skills/workflow-orchestrator/SKILL.md +19 -19
- package/plugins/devflow-core/skills/workflow-orchestrator/agents/openai.yaml +2 -2
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/acceptance_test_runner.py +412 -76
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +60 -9
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/api_test_matrix.py +2 -2
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/approval_pins.py +433 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/approval_store.py +137 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_assessment.rb +1 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_brief.rb +1 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_feasibility.rb +1 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_invalidation.rb +5 -5
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_model_commit.rb +4 -4
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_model_validate.py +1 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_source_resolver.rb +4 -4
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/checker_adapter.rb +7 -7
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/control_io.rb +51 -7
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/cross_track_consistency_check.rb +1 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dsl_coverage.py +87 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dsl_v1.py +318 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/environment_profile.py +134 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +7 -6
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_surface.py +138 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/gate_runtime.rb +3 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_state.rb +5 -5
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_workflow.rb +5 -5
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/inspect_state.rb +13 -13
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/machine_evidence.rb +3 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/manage_lessons.rb +7 -7
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/parallel_recover.rb +2 -2
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/parallel_run.rb +3 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/phase_ownership.rb +6 -6
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/playwright_adapter.py +515 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/repair_control_plane.rb +1 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/requirement_coverage.rb +1 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/requirement_graph.rb +3 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/review_preflight.py +3 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/review_run.py +18 -18
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/runtime_context_collector.rb +3 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/session_rollover.rb +4 -4
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/static_runtime_context_collector.rb +2 -2
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/sync_artifacts.rb +5 -5
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/tc_hash.py +132 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/tdd_evidence.py +3 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_command_policy.py +104 -10
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_task_binding_check.py +3 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/update_triage_route.rb +3 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +19 -6
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/wireframe_commit.rb +5 -5
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/wireframe_validate.py +1 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/acceptance-remediation.yaml +12 -12
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +201 -222
- package/plugins/devflow-core/skills/workflow-pipeline/SKILL.md +6 -4
- package/plugins/devflow-core/skills/workflow-pipeline/references/pipeline-flow.md +68 -140
- package/plugins/devflow-core/skills/workflow-project-context/SKILL.md +14 -14
- package/plugins/devflow-core/skills/workflow-project-context/assets/agents-md-template.md +7 -7
- package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-backend-spec-template.md +4 -4
- package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-frontend-spec-template.md +4 -4
- package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-navigation-template.md +1 -1
- package/plugins/devflow-core/skills/workflow-project-context/assets/context-domain-index-template.md +4 -4
- package/plugins/devflow-core/skills/workflow-project-context/assets/context-spec-index-template.md +10 -10
- package/plugins/devflow-core/skills/workflow-project-context/assets/execution-contract-template.md +2 -2
- package/plugins/devflow-core/skills/workflow-project-context/assets/navigation-index-template.md +18 -18
- package/plugins/devflow-core/skills/workflow-project-context/assets/project-initialization-template.md +32 -32
- package/plugins/devflow-core/skills/workflow-project-context/assets/rules-index-template.md +30 -30
- package/plugins/devflow-core/skills/workflow-project-context/assets/state-template.md +7 -7
- package/plugins/devflow-core/skills/workflow-project-context/references/navigation-guide.md +37 -37
- package/plugins/devflow-core/skills/workflow-project-context/scripts/validate_initialization.py +16 -16
- package/plugins/devflow-core/skills/workflow-project-context/scripts/validate_initialization_database.py +1 -1
- package/plugins/devflow-design/skills/workflow-api-call-guide/SKILL.md +4 -4
- package/plugins/devflow-design/skills/workflow-api-contract/SKILL.md +8 -8
- package/plugins/devflow-design/skills/workflow-api-supplement/SKILL.md +5 -5
- package/plugins/devflow-design/skills/workflow-architecture-feasibility/SKILL.md +1 -1
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/SKILL.md +39 -39
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/assets/backend-spec-template.md +10 -10
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/affected-module-exploration.md +2 -2
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/code-explore-guide.md +1 -1
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/project-rule-contract.md +20 -20
- package/plugins/devflow-design/skills/workflow-design-rule-pack/SKILL.md +8 -8
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/index.md +3 -3
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/accessibility.md +2 -2
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/component-library.md +2 -2
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/design-systems.md +1 -1
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/responsive-grid.md +2 -2
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/stack-guidelines.md +6 -6
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/ux-patterns.md +2 -2
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tokens/project-tokens.md +1 -1
- package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/SKILL.md +12 -12
- package/plugins/devflow-design/skills/workflow-design-rule-pack/scripts/install.py +2 -2
- package/plugins/devflow-design/skills/workflow-feature-breakdown/SKILL.md +30 -30
- package/plugins/devflow-design/skills/workflow-feature-breakdown/assets/output-template.md +2 -2
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/SKILL.md +41 -41
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/assets/frontend-spec-template.md +15 -15
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/affected-module-exploration.md +2 -2
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/project-rule-contract.md +25 -25
- package/plugins/devflow-design/skills/workflow-integration-verify/SKILL.md +13 -13
- package/plugins/devflow-design/skills/workflow-ui-spec/SKILL.md +23 -23
- package/plugins/devflow-design/skills/workflow-ui-spec/assets/ui-spec-template.md +3 -3
- package/plugins/devflow-implementation/agents/implementation-worker.md +2 -2
- package/plugins/devflow-implementation/skills/workflow-bug-fix/SKILL.md +9 -9
- package/plugins/devflow-implementation/skills/workflow-bug-fix/references/lesson-format.md +2 -2
- package/plugins/devflow-implementation/skills/workflow-bug-fix/references/root-cause-ladder.md +1 -1
- package/plugins/devflow-implementation/skills/workflow-change-plan/SKILL.md +17 -17
- package/plugins/devflow-implementation/skills/workflow-change-plan/references/code-conventions.md +1 -1
- package/plugins/devflow-implementation/skills/workflow-change-plan/references/output-template.md +3 -3
- package/plugins/devflow-implementation/skills/workflow-change-plan/references/project-rule-contract.md +16 -16
- package/plugins/devflow-implementation/skills/workflow-execute-phase/SKILL.md +38 -38
- package/plugins/devflow-implementation/skills/workflow-execute-phase/references/execute-phase.md +4 -2
- package/plugins/devflow-intel/agents/project-scout.md +1 -1
- package/plugins/devflow-intel/extensions/devflow-intel.ts +20 -19
- package/plugins/devflow-intel/skills/code-index-query/SKILL.md +6 -6
- package/plugins/devflow-intel/skills/workflow-code-index/SKILL.md +12 -12
- package/plugins/devflow-intel/skills/workflow-code-index/references/discovery.md +2 -2
- package/plugins/devflow-intel/skills/workflow-code-index/references/query-guide.md +1 -1
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/code_index.py +31 -12
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/discovery.py +2 -2
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/scanner.py +4 -1
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/project_binding_check.py +4 -4
- package/plugins/devflow-intel/skills/workflow-context-sync/SKILL.md +14 -14
- package/plugins/devflow-intel/skills/workflow-context-sync/scripts/context_sync_cursor.py +2 -2
- package/plugins/devflow-intel/skills/workflow-update-navigation/SKILL.md +26 -26
- package/plugins/devflow-intel/skills/workflow-update-navigation/references/incremental-update-guide.md +2 -2
- package/plugins/devflow-quality/runtime/playwright/devflow-test-dsl-v1.schema.json +872 -0
- package/plugins/devflow-quality/runtime/playwright/playwright.config.mjs +46 -0
- package/plugins/devflow-quality/runtime/playwright/reporter.mjs +311 -0
- package/plugins/devflow-quality/runtime/playwright/runner.spec.mjs +1308 -0
- package/plugins/devflow-quality/runtime/playwright/runtime-manifest.json +11 -0
- package/plugins/devflow-quality/skills/code-review/SKILL.md +12 -12
- package/plugins/devflow-quality/skills/code-review/references/spec-checklist.md +1 -1
- package/plugins/devflow-quality/skills/workflow-acceptance-test-design/SKILL.md +11 -8
- package/plugins/devflow-quality/skills/workflow-gen-test-guide/SKILL.md +6 -6
- package/plugins/devflow-quality/skills/workflow-gen-test-guide/legacy/SKILL.md +14 -14
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +82 -69
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/api-mainflow-rules.md +20 -15
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/codebase-discovery.md +12 -10
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/intermediate-format.md +115 -192
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/mainflow-selection.md +27 -23
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/project-dsl-contract.md +78 -153
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/ui-mainflow-rules.md +36 -83
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/compile_dsl.py +497 -556
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/discover_codebases.py +2 -2
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/export_legacy_testagent.py +647 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/normalize_case_payloads.py +10 -3
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/validate_case_payloads.py +165 -275
- package/plugins/devflow-quality/skills/workflow-human-acceptance/SKILL.md +61 -0
- package/plugins/devflow-quality/skills/workflow-release-closeout/SKILL.md +18 -18
- package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/SKILL.md +20 -14
- package/plugins/devflow-requirements/skills/requirement-feasibility-review/SKILL.md +1 -1
- package/plugins/devflow-requirements/skills/workflow-demand-clarify/SKILL.md +10 -10
- package/plugins/devflow-requirements/skills/workflow-domain-modeling/SKILL.md +25 -25
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/SKILL.md +49 -49
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/assets/baseline-template.md +22 -22
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/antipatterns.md +3 -3
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/challenge-review-guide.md +3 -3
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/project-context-guide.md +11 -11
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/questioning.md +1 -1
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/requirement-review-checklist.md +4 -4
- package/plugins/devflow-requirements/skills/workflow-requirement-triage/SKILL.md +16 -16
- package/plugins/devflow-requirements/skills/workflow-wayfinder/SKILL.md +6 -6
- package/src/approval-store.ts +340 -0
- package/src/cli.ts +15 -0
- package/src/constants.ts +1 -1
- package/src/doctor.ts +259 -2
- package/src/initialization-store.ts +51 -3
- package/src/paths.ts +12 -1
- package/src/project-context.ts +60 -15
- package/src/project-documents.ts +61 -61
- package/src/project-initializer.ts +9 -8
- package/src/project-layout.ts +217 -0
- package/src/runtime.ts +3 -3
- package/templates/commands/start.md +4 -4
- package/templates/omp/AGENTS.md +1 -1
- package/templates/omp/hook-config.json +13 -7
- 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
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
|
-
|
|
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 =
|
|
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 ?? [
|
|
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 ??
|
|
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
|
|
23
|
+
return normalizeDevflowHome(process.env.DEVFLOW_HOME);
|
|
13
24
|
}
|
|
14
25
|
|
|
15
26
|
export function devflowAgentDir(): string {
|
package/src/project-context.ts
CHANGED
|
@@ -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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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,
|
|
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 =
|
|
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 =
|
|
137
|
-
const
|
|
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 =
|
|
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
|
|
168
|
-
const manifestPath = path.join(devflowDir, "project.json");
|
|
199
|
+
await assertPlanningBoundary(canonicalRoot);
|
|
169
200
|
if (await exists(manifestPath)) {
|
|
170
|
-
|
|
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 {
|