@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
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Read-only access to Core approval records in control.db (变更清单 §1.8).
|
|
3
|
+
|
|
4
|
+
Approvals are written only by the Runtime extension's UI confirmation path.
|
|
5
|
+
This module never writes: it opens control.db with mode=ro and re-derives
|
|
6
|
+
validity by recomputing the current pins with approval_pins.compute (the single
|
|
7
|
+
pin implementation shared with the proposal/confirm side) and comparing them
|
|
8
|
+
with the stored pins_json. The
|
|
9
|
+
file-compatibility authority has no approval channel, so every lookup there
|
|
10
|
+
fails with approval_requires_database_authority.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import sqlite3
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from tc_hash import canonicalize
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
APPROVAL_KINDS = frozenset({"execution_plan", "environment_profile", "mode_override", "human_result"})
|
|
24
|
+
REQUIRES_DATABASE = "approval_requires_database_authority"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _project_id(root: Path) -> str | None:
|
|
28
|
+
try:
|
|
29
|
+
value = json.loads((root / ".devflow" / "project.json").read_text(encoding="utf-8"))["projectId"]
|
|
30
|
+
return value if isinstance(value, str) and value else None
|
|
31
|
+
except (OSError, KeyError, TypeError, ValueError):
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def devflow_home() -> Path:
|
|
36
|
+
"""DEVFLOW_HOME rule shared with the TS writer (src/paths.ts normalizeDevflowHome) and hook_common.
|
|
37
|
+
|
|
38
|
+
Unset, empty or whitespace-only means unset -> ~/.devflow-pi. Otherwise only a leading '~' or '~/'
|
|
39
|
+
is expanded (never '~user'), then the path is made absolute with os.path.abspath (existence is not required).
|
|
40
|
+
"""
|
|
41
|
+
value = os.environ.get("DEVFLOW_HOME")
|
|
42
|
+
value = "~/.devflow-pi" if value is None or not value.strip() else value.strip()
|
|
43
|
+
if value == "~" or value.startswith("~/"):
|
|
44
|
+
value = str(Path.home()) + value[1:]
|
|
45
|
+
return Path(os.path.abspath(value))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _connect(root: Path) -> tuple[sqlite3.Connection | None, str | None]:
|
|
49
|
+
project_id = _project_id(root)
|
|
50
|
+
if project_id is None:
|
|
51
|
+
return None, None
|
|
52
|
+
database = devflow_home() / "projects" / project_id / "control.db"
|
|
53
|
+
if not database.is_file():
|
|
54
|
+
return None, project_id
|
|
55
|
+
# Path.as_uri() percent-quotes '?', '#', '%' etc., so they cannot be read as URI syntax.
|
|
56
|
+
connection = sqlite3.connect(f"{database.as_uri()}?mode=ro", uri=True)
|
|
57
|
+
connection.row_factory = sqlite3.Row
|
|
58
|
+
return connection, project_id
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _same(left: Any, right: Any) -> bool:
|
|
62
|
+
try:
|
|
63
|
+
return canonicalize(left) == canonicalize(right)
|
|
64
|
+
except ValueError:
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def latest_approval(root: Path, feature: str, kind: str, subject_key: str) -> dict[str, Any]:
|
|
69
|
+
"""The newest non-superseded approval for (feature, kind, subject_key), without validity checks."""
|
|
70
|
+
if kind not in APPROVAL_KINDS:
|
|
71
|
+
return {"ok": False, "reason": "approval_invalid", "message": f"unknown approval kind: {kind}"}
|
|
72
|
+
try:
|
|
73
|
+
connection, project_id = _connect(root)
|
|
74
|
+
if connection is None:
|
|
75
|
+
return {"ok": False, "reason": REQUIRES_DATABASE, "message": "approvals require the database control-plane authority"}
|
|
76
|
+
try:
|
|
77
|
+
authority = connection.execute("SELECT authority FROM authority_state WHERE project_id=?", (project_id,)).fetchone()
|
|
78
|
+
if not authority or authority["authority"] != "database":
|
|
79
|
+
return {"ok": False, "reason": REQUIRES_DATABASE, "message": "approvals require the database control-plane authority"}
|
|
80
|
+
row = connection.execute(
|
|
81
|
+
"""SELECT a.id, a.proposal_id, a.pins_json, a.decision_json, a.approved_via, a.approved_at, p.status AS proposal_status, p.payload_json,
|
|
82
|
+
p.kind AS proposal_kind, p.subject_key AS proposal_subject, p.feature AS proposal_feature, p.project_id AS proposal_project
|
|
83
|
+
FROM approvals a JOIN approval_proposals p ON p.id=a.proposal_id
|
|
84
|
+
WHERE a.project_id=? AND a.feature=? AND a.kind=? AND a.subject_key=? AND a.superseded_at IS NULL
|
|
85
|
+
ORDER BY a.approved_at DESC, a.rowid DESC LIMIT 1""",
|
|
86
|
+
(project_id, feature, kind, subject_key),
|
|
87
|
+
).fetchone()
|
|
88
|
+
finally:
|
|
89
|
+
connection.close()
|
|
90
|
+
except sqlite3.Error as error:
|
|
91
|
+
return {"ok": False, "reason": "approval_invalid", "message": f"approval store is unreadable: {error}"}
|
|
92
|
+
if row is None:
|
|
93
|
+
return {"ok": False, "reason": "approval_missing", "message": f"no {kind} approval for {subject_key}"}
|
|
94
|
+
if (row["approved_via"] != "runtime_ui_confirm" or row["proposal_status"] != "consumed" or row["proposal_kind"] != kind
|
|
95
|
+
or row["proposal_subject"] != subject_key or row["proposal_feature"] != feature or row["proposal_project"] != project_id):
|
|
96
|
+
return {"ok": False, "reason": "approval_invalid", "message": "approval record is not bound to a consumed UI-confirmed proposal"}
|
|
97
|
+
try:
|
|
98
|
+
pins = json.loads(row["pins_json"])
|
|
99
|
+
decision = json.loads(row["decision_json"])
|
|
100
|
+
payload = json.loads(row["payload_json"] or "{}")
|
|
101
|
+
except (TypeError, json.JSONDecodeError):
|
|
102
|
+
return {"ok": False, "reason": "approval_invalid", "message": "approval record JSON is malformed"}
|
|
103
|
+
if not isinstance(pins, dict) or not isinstance(decision, dict) or not isinstance(payload, dict):
|
|
104
|
+
return {"ok": False, "reason": "approval_invalid", "message": "approval pins, decision and payload must be objects"}
|
|
105
|
+
request = payload.get("request", {})
|
|
106
|
+
return {"ok": True, "reason": None, "approval": {"id": row["id"], "proposal_id": row["proposal_id"], "kind": kind, "subject_key": subject_key,
|
|
107
|
+
"pins": pins, "decision": decision, "request": request if isinstance(request, dict) else {},
|
|
108
|
+
"approved_at": row["approved_at"]}}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def valid_approval(root: Path, feature: str, kind: str, subject_key: str, current_pins: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
112
|
+
"""Return {"ok", "reason", "message", "approval"}.
|
|
113
|
+
|
|
114
|
+
Valid only when the newest non-superseded approval's pins equal the pins
|
|
115
|
+
approval_pins.compute derives now from the proposal request; any key given
|
|
116
|
+
in ``current_pins`` (e.g. the hash of the DSL the binding actually names)
|
|
117
|
+
must additionally equal the stored pin.
|
|
118
|
+
"""
|
|
119
|
+
found = latest_approval(root, feature, kind, subject_key)
|
|
120
|
+
if not found["ok"]:
|
|
121
|
+
return {**found, "approval": None}
|
|
122
|
+
approval = found["approval"]
|
|
123
|
+
try:
|
|
124
|
+
from approval_pins import compute
|
|
125
|
+
except ImportError as error:
|
|
126
|
+
return {"ok": False, "reason": "approval_invalid", "message": f"approval_pins is unavailable: {error}", "approval": None}
|
|
127
|
+
computed = compute(root, feature, kind, subject_key, approval["request"])
|
|
128
|
+
if not computed.get("ok"):
|
|
129
|
+
return {"ok": False, "reason": "approval_stale", "message": "current pins cannot be computed: " + "; ".join(map(str, computed.get("errors", []))), "approval": None}
|
|
130
|
+
expected = dict(computed["pins"])
|
|
131
|
+
for key, value in (current_pins or {}).items():
|
|
132
|
+
if not _same(expected.get(key), value):
|
|
133
|
+
return {"ok": False, "reason": "approval_stale", "message": f"approval does not cover the current {key}", "approval": None}
|
|
134
|
+
if not _same(approval["pins"], expected):
|
|
135
|
+
changed = sorted(key for key in set(approval["pins"]) | set(expected) if not _same(approval["pins"].get(key), expected.get(key)))
|
|
136
|
+
return {"ok": False, "reason": "approval_stale", "message": f"approval pins no longer match: {', '.join(changed)}", "approval": None}
|
|
137
|
+
return {"ok": True, "reason": None, "message": None, "approval": approval}
|
package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_assessment.rb
CHANGED
|
@@ -17,7 +17,7 @@ require_relative "runtime_context_collector"
|
|
|
17
17
|
module ArchitectureAssessment
|
|
18
18
|
SCHEMA_VERSION = 1
|
|
19
19
|
RULE_VERSION = 1
|
|
20
|
-
ROOT_DIR = File.join(".planning", "architecture-assessment")
|
|
20
|
+
ROOT_DIR = File.join(".devflow/planning", "architecture-assessment")
|
|
21
21
|
|
|
22
22
|
UNRESOLVED = {
|
|
23
23
|
DELTA_SET_BINDING_FAILED: "DELTA_SET_BINDING_FAILED",
|
package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_feasibility.rb
CHANGED
|
@@ -27,7 +27,7 @@ module ArchitectureFeasibility
|
|
|
27
27
|
|
|
28
28
|
pointer = JSON.parse(File.read(pointer_path))
|
|
29
29
|
relative = pointer.fetch("path")
|
|
30
|
-
raise ArgumentError, "static runtime-context pointer path is invalid" unless relative.is_a?(String) && relative.start_with?(".context/static-runtime-context/revisions/")
|
|
30
|
+
raise ArgumentError, "static runtime-context pointer path is invalid" unless relative.is_a?(String) && relative.start_with?(".devflow/context/static-runtime-context/revisions/")
|
|
31
31
|
|
|
32
32
|
absolute = File.expand_path(relative, root)
|
|
33
33
|
root_real = File.realpath(root)
|
|
@@ -11,7 +11,7 @@ require "time"
|
|
|
11
11
|
require "yaml"
|
|
12
12
|
require_relative "control_io"
|
|
13
13
|
|
|
14
|
-
options = { root: ".", feature: nil, canonical: nil, changed_source: [], registry: ".planning/artifacts/index.yaml", write: false, json: false }
|
|
14
|
+
options = { root: ".", feature: nil, canonical: nil, changed_source: [], registry: ".devflow/planning/artifacts/index.yaml", write: false, json: false }
|
|
15
15
|
OptionParser.new do |o|
|
|
16
16
|
o.on("--root PATH") { |v| options[:root] = v }
|
|
17
17
|
o.on("--feature NAME") { |v| options[:feature] = v }
|
|
@@ -42,7 +42,7 @@ lock = nil
|
|
|
42
42
|
begin
|
|
43
43
|
raise ArgumentError, "feature is required" if options[:feature].to_s.empty?
|
|
44
44
|
raise ArgumentError, "at least one changed source is required" if options[:changed_source].empty?
|
|
45
|
-
canonical_path = options[:canonical] || ".planning/business-model/#{options[:feature]}/canonical.json"
|
|
45
|
+
canonical_path = options[:canonical] || ".devflow/planning/business-model/#{options[:feature]}/canonical.json"
|
|
46
46
|
canonical_absolute, canonical_relative = DevflowControlIO.resolve_control_path(root, canonical_path, :artifact)
|
|
47
47
|
model = read_json(canonical_absolute)
|
|
48
48
|
changed = options[:changed_source].uniq
|
|
@@ -56,7 +56,7 @@ begin
|
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
affected_ids = affected.map { |item| item["id"].to_s }.to_set
|
|
59
|
-
wireframe_relative = ".planning/features/#{options[:feature]}/canonical.json"
|
|
59
|
+
wireframe_relative = ".devflow/planning/features/#{options[:feature]}/canonical.json"
|
|
60
60
|
wireframe_path, = DevflowControlIO.resolve_control_path(root, wireframe_relative, :artifact)
|
|
61
61
|
consumers = []
|
|
62
62
|
if File.file?(wireframe_path)
|
|
@@ -70,7 +70,7 @@ begin
|
|
|
70
70
|
if options[:write]
|
|
71
71
|
lock = DevflowControlIO.acquire_project_lock(root)
|
|
72
72
|
timestamp = Time.now.utc.iso8601
|
|
73
|
-
approval_relative = ".planning/features/#{options[:feature]}/approval.json"
|
|
73
|
+
approval_relative = ".devflow/planning/features/#{options[:feature]}/approval.json"
|
|
74
74
|
approval_path, = DevflowControlIO.resolve_control_path(root, approval_relative, :artifact)
|
|
75
75
|
if !consumers.empty? && File.file?(approval_path)
|
|
76
76
|
approval = read_json(approval_path)
|
|
@@ -93,7 +93,7 @@ begin
|
|
|
93
93
|
DevflowControlIO.atomic_write(registry_path, registry.to_yaml.sub(/\A---\n/, ""))
|
|
94
94
|
report["registry_path"] = registry_relative
|
|
95
95
|
end
|
|
96
|
-
report_path = File.join(root, ".planning", "business-model", options[:feature], "invalidation.json")
|
|
96
|
+
report_path = File.join(root, ".devflow/planning", "business-model", options[:feature], "invalidation.json")
|
|
97
97
|
DevflowControlIO.atomic_write(report_path, JSON.pretty_generate(report) + "\n")
|
|
98
98
|
report["written"] = true
|
|
99
99
|
else
|
|
@@ -43,7 +43,7 @@ module BusinessModelCommit
|
|
|
43
43
|
raise ArgumentError, "invalid feature" unless valid_feature?(feature)
|
|
44
44
|
|
|
45
45
|
root_real = File.realpath(root)
|
|
46
|
-
relative = File.join(".planning", "business-model", feature)
|
|
46
|
+
relative = File.join(".devflow/planning", "business-model", feature)
|
|
47
47
|
absolute = File.expand_path(relative, root_real)
|
|
48
48
|
raise ArgumentError, "business model path escapes project root" unless absolute.start_with?("#{root_real}#{File::SEPARATOR}")
|
|
49
49
|
|
|
@@ -67,7 +67,7 @@ module BusinessModelCommit
|
|
|
67
67
|
raise ArgumentError, "proposal path contains control characters" if value.match?(/[\x00-\x1F\x7F]/)
|
|
68
68
|
|
|
69
69
|
normalized = Pathname.new(value).cleanpath.to_s.tr("\\", "/")
|
|
70
|
-
prefix = File.join(".planning", "business-model", feature, "proposals").tr("\\", "/") + "/"
|
|
70
|
+
prefix = File.join(".devflow/planning", "business-model", feature, "proposals").tr("\\", "/") + "/"
|
|
71
71
|
raise ArgumentError, "proposal path is outside the feature proposal directory" unless normalized.start_with?(prefix)
|
|
72
72
|
absolute = File.expand_path(normalized, root_real)
|
|
73
73
|
raise ArgumentError, "proposal path escapes project root" unless absolute.start_with?("#{root_real}#{File::SEPARATOR}")
|
|
@@ -255,7 +255,7 @@ module BusinessModelCommit
|
|
|
255
255
|
def load_delta(root, feature, raw)
|
|
256
256
|
raise ArgumentError, "candidate delta path is required" if raw.to_s.empty?
|
|
257
257
|
path = File.expand_path(raw, root)
|
|
258
|
-
feature_root = File.expand_path(File.join(".planning", "business-model", feature, "candidates"), root)
|
|
258
|
+
feature_root = File.expand_path(File.join(".devflow/planning", "business-model", feature, "candidates"), root)
|
|
259
259
|
raise ArgumentError, "candidate delta is missing" unless File.file?(path)
|
|
260
260
|
path = File.realpath(path)
|
|
261
261
|
feature_root = File.realpath(feature_root)
|
|
@@ -290,7 +290,7 @@ module BusinessModelCommit
|
|
|
290
290
|
def source_registry_fingerprint(root, feature, aggregate)
|
|
291
291
|
registry_ref = aggregate.dig("merged", "source_registry_ref").to_s
|
|
292
292
|
return "" if registry_ref.empty?
|
|
293
|
-
path = if registry_ref.start_with?(".planning/")
|
|
293
|
+
path = if registry_ref.start_with?(".devflow/planning/")
|
|
294
294
|
File.expand_path(registry_ref, root)
|
|
295
295
|
else
|
|
296
296
|
File.expand_path(registry_ref, feature_dir(root, feature))
|
package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_model_validate.py
CHANGED
|
@@ -606,7 +606,7 @@ def load_json(path: Path) -> Any:
|
|
|
606
606
|
|
|
607
607
|
|
|
608
608
|
def requirement_nodes(root: Path, feature: str, violations: list[dict[str, Any]]) -> set[str]:
|
|
609
|
-
requirements_root = (root / ".planning" / "requirements").resolve()
|
|
609
|
+
requirements_root = (root / ".devflow/planning" / "requirements").resolve()
|
|
610
610
|
path = (requirements_root / feature / "governance.json").resolve()
|
|
611
611
|
if not path.is_relative_to(requirements_root):
|
|
612
612
|
add_violation(violations, "BUSINESS_REQUIREMENT_GRAPH_INVALID", feature, path=str(path), message="feature escapes requirements directory")
|
package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_source_resolver.rb
CHANGED
|
@@ -101,7 +101,7 @@ module BusinessSourceResolver
|
|
|
101
101
|
output = command(root, "git", "status", "--porcelain=v1", "--untracked-files=all")
|
|
102
102
|
lines = output.lines.map(&:rstrip).reject do |line|
|
|
103
103
|
path = line.length > 3 ? line[3..].to_s.strip : ""
|
|
104
|
-
path.start_with?(".planning/", ".context/")
|
|
104
|
+
path.start_with?(".devflow/planning/", ".devflow/context/")
|
|
105
105
|
end
|
|
106
106
|
"sha256:#{Digest::SHA256.hexdigest(lines.join("\n"))}"
|
|
107
107
|
end
|
|
@@ -237,7 +237,7 @@ module BusinessSourceResolver
|
|
|
237
237
|
current_signature = worktree_signature(root)
|
|
238
238
|
add(errors, "BUSINESS_WORKTREE_SIGNATURE_MISMATCH", "worktree signature is stale", source_id: id) unless source["worktree_signature"].to_s == current_signature
|
|
239
239
|
|
|
240
|
-
snapshot_path = File.join(root, ".planning", "code-index", "SNAPSHOT.json")
|
|
240
|
+
snapshot_path = File.join(root, ".devflow/planning", "code-index", "SNAPSHOT.json")
|
|
241
241
|
unless File.file?(snapshot_path)
|
|
242
242
|
add(errors, "BUSINESS_CODE_INDEX_STALE", "code index snapshot is missing", source_id: id, path: snapshot_path)
|
|
243
243
|
return
|
|
@@ -258,7 +258,7 @@ module BusinessSourceResolver
|
|
|
258
258
|
end
|
|
259
259
|
return if symbol.empty?
|
|
260
260
|
|
|
261
|
-
symbols_path = File.join(root, ".planning", "code-index", "symbols.jsonl")
|
|
261
|
+
symbols_path = File.join(root, ".devflow/planning", "code-index", "symbols.jsonl")
|
|
262
262
|
unless File.file?(symbols_path)
|
|
263
263
|
add(errors, "BUSINESS_CODE_SYMBOL_NOT_FOUND", "symbols index is missing", source_id: id, path: symbols_path)
|
|
264
264
|
return
|
|
@@ -318,7 +318,7 @@ module BusinessSourceResolver
|
|
|
318
318
|
raise ArgumentError, "invalid feature" unless valid_feature?(feature)
|
|
319
319
|
root = File.realpath(root)
|
|
320
320
|
schema = load_yaml(REGISTRY_SCHEMA)
|
|
321
|
-
raw_registry = registry_path || File.join(".planning", "business-model", feature, "source-registry.yaml")
|
|
321
|
+
raw_registry = registry_path || File.join(".devflow/planning", "business-model", feature, "source-registry.yaml")
|
|
322
322
|
registry_absolute, registry_relative = safe_path(root, raw_registry, "source registry")
|
|
323
323
|
registry = load_yaml(registry_absolute)
|
|
324
324
|
errors = []
|
|
@@ -60,22 +60,22 @@ module CheckerAdapter
|
|
|
60
60
|
when "cross-track-consistency-check"
|
|
61
61
|
["ruby", File.join(scripts, "cross_track_consistency_check.rb"), "--feature", feature, "--json"]
|
|
62
62
|
when "business-cross-model-check"
|
|
63
|
-
business = ".planning/business-model/#{feature}/canonical.json"
|
|
63
|
+
business = ".devflow/planning/business-model/#{feature}/canonical.json"
|
|
64
64
|
return nil unless File.file?(File.join(root, business))
|
|
65
65
|
command = ["python3", File.join(scripts, "business_cross_model_check.py"), "--business-canonical", business]
|
|
66
|
-
wireframe = ".planning/features/#{feature}/canonical.json"
|
|
67
|
-
claims = ".planning/features/#{feature}/business-claims.yaml"
|
|
68
|
-
traceability = ".planning/requirements/#{feature}/traceability.json"
|
|
66
|
+
wireframe = ".devflow/planning/features/#{feature}/canonical.json"
|
|
67
|
+
claims = ".devflow/planning/features/#{feature}/business-claims.yaml"
|
|
68
|
+
traceability = ".devflow/planning/requirements/#{feature}/traceability.json"
|
|
69
69
|
command += ["--wireframe", wireframe] if File.file?(File.join(root, wireframe))
|
|
70
70
|
command += ["--claims", claims] if File.file?(File.join(root, claims))
|
|
71
71
|
command += ["--traceability", traceability] if File.file?(File.join(root, traceability))
|
|
72
72
|
command + ["--json"]
|
|
73
73
|
when "business-claim-reference-check"
|
|
74
|
-
claims = ".planning/features/#{feature}/business-claims.yaml"
|
|
75
|
-
business = ".planning/business-model/#{feature}/canonical.json"
|
|
74
|
+
claims = ".devflow/planning/features/#{feature}/business-claims.yaml"
|
|
75
|
+
business = ".devflow/planning/business-model/#{feature}/canonical.json"
|
|
76
76
|
return nil unless File.file?(File.join(root, claims)) && File.file?(File.join(root, business))
|
|
77
77
|
command = ["python3", File.join(scripts, "business_claim_validate.py"), "--claims", claims, "--business-canonical", business]
|
|
78
|
-
traceability = ".planning/requirements/#{feature}/traceability.json"
|
|
78
|
+
traceability = ".devflow/planning/requirements/#{feature}/traceability.json"
|
|
79
79
|
command += ["--traceability", traceability] if File.file?(File.join(root, traceability))
|
|
80
80
|
command
|
|
81
81
|
end
|
|
@@ -5,6 +5,7 @@ require "fileutils"
|
|
|
5
5
|
require "json"
|
|
6
6
|
require "pathname"
|
|
7
7
|
require "securerandom"
|
|
8
|
+
require "time"
|
|
8
9
|
|
|
9
10
|
# Shared durability primitives for every writer in the Devflow control plane.
|
|
10
11
|
# The project lock serializes state, workflow, registry, and next-intent updates;
|
|
@@ -14,10 +15,52 @@ module DevflowControlIO
|
|
|
14
15
|
|
|
15
16
|
module_function
|
|
16
17
|
|
|
18
|
+
def assert_layout_ready(root)
|
|
19
|
+
root_path = File.realpath(root)
|
|
20
|
+
legacy_planning = File.join(root_path, ".planning")
|
|
21
|
+
legacy_context = File.join(root_path, ".context")
|
|
22
|
+
planning_markers = %w[STATE.md PROJECT.md .devflow.lock navigation/INDEX.md code-index/INDEX.md workflows contracts evidence review-runs requirements features handoffs artifacts phase-ownership.yaml]
|
|
23
|
+
context_markers = %w[spec/index.md index.md project/index.md knowledge/index.md]
|
|
24
|
+
legacy_markers = planning_markers.map { |item| File.join(legacy_planning, item) } + context_markers.map { |item| File.join(legacy_context, item) }
|
|
25
|
+
return true unless legacy_markers.any? { |item| File.exist?(item) }
|
|
26
|
+
|
|
27
|
+
manifest = JSON.parse(File.read(File.join(root_path, ".devflow", "project.json")))
|
|
28
|
+
valid_manifest = manifest["schemaVersion"] == 1 &&
|
|
29
|
+
manifest["projectId"].to_s.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i) &&
|
|
30
|
+
!manifest["name"].to_s.strip.empty? &&
|
|
31
|
+
!Time.iso8601(manifest["createdAt"].to_s).nil? &&
|
|
32
|
+
[nil, 1, 2].include?(manifest["layoutVersion"])
|
|
33
|
+
raise InvalidControlPath, "DEVFLOW_LEGACY_LAYOUT_CONFIRMATION_REQUIRED: valid project manifest required" unless valid_manifest
|
|
34
|
+
|
|
35
|
+
marker = JSON.parse(File.read(File.join(root_path, ".devflow", "layout-migration.json"))) rescue nil
|
|
36
|
+
migration_complete = marker.is_a?(Hash) && marker["schemaVersion"] == 1 && marker["status"] == "complete" && marker["migrated"].is_a?(Array)
|
|
37
|
+
active = %w[STATE.md workflows contracts evidence review-runs requirements features handoffs artifacts phase-ownership.yaml].find do |item|
|
|
38
|
+
File.exist?(File.join(legacy_planning, item))
|
|
39
|
+
end
|
|
40
|
+
raise InvalidControlPath, "DEVFLOW_ACTIVE_LEGACY_LAYOUT: .planning/#{active} requires dedicated control-plane migration" if active && !migration_complete
|
|
41
|
+
canonical_exists = File.exist?(File.join(root_path, ".devflow", "planning")) || File.exist?(File.join(root_path, ".devflow", "context"))
|
|
42
|
+
if manifest["layoutVersion"] == 2 && !migration_complete
|
|
43
|
+
raise InvalidControlPath, "DEVFLOW_LEGACY_REINTRODUCTION: v2 project cannot import legacy layout"
|
|
44
|
+
end
|
|
45
|
+
if manifest["layoutVersion"] == 2 && migration_complete
|
|
46
|
+
planning_legacy = planning_markers.any? { |item| File.exist?(File.join(legacy_planning, item)) }
|
|
47
|
+
context_legacy = context_markers.any? { |item| File.exist?(File.join(legacy_context, item)) }
|
|
48
|
+
missing_canonical = (planning_legacy && !File.exist?(File.join(root_path, ".devflow", "planning"))) ||
|
|
49
|
+
(context_legacy && !File.exist?(File.join(root_path, ".devflow", "context")))
|
|
50
|
+
raise InvalidControlPath, "DEVFLOW_LEGACY_REINTRODUCTION: v2 project will not backfill a missing canonical directory" if missing_canonical
|
|
51
|
+
end
|
|
52
|
+
raise InvalidControlPath, "DEVFLOW_LAYOUT_CONFLICT: canonical and legacy layouts coexist without migration marker" if canonical_exists && !migration_complete
|
|
53
|
+
raise InvalidControlPath, "DEVFLOW_LAYOUT_MIGRATION_REQUIRED: run project initialization" unless canonical_exists
|
|
54
|
+
true
|
|
55
|
+
rescue Errno::ENOENT, JSON::ParserError, ArgumentError
|
|
56
|
+
raise InvalidControlPath, "DEVFLOW_LEGACY_LAYOUT_CONFIRMATION_REQUIRED: valid project manifest required"
|
|
57
|
+
end
|
|
58
|
+
|
|
17
59
|
# Control-plane files are not general-purpose CLI file arguments. Keeping
|
|
18
60
|
# their location fixed prevents a crafted STATE/workflow/registry reference
|
|
19
61
|
# from turning recovery or synchronization into an out-of-project writer.
|
|
20
62
|
def resolve_control_path(root, raw, kind)
|
|
63
|
+
assert_layout_ready(root)
|
|
21
64
|
root_path = File.realpath(root)
|
|
22
65
|
value = raw.to_s
|
|
23
66
|
raise InvalidControlPath, "#{kind} path is required" if value.empty?
|
|
@@ -27,14 +70,14 @@ module DevflowControlIO
|
|
|
27
70
|
normalized = Pathname.new(value).cleanpath.to_s.tr("\\", "/")
|
|
28
71
|
raise InvalidControlPath, "#{kind} path cannot traverse parent directories" if normalized == ".." || normalized.start_with?("../")
|
|
29
72
|
allowed = case kind
|
|
30
|
-
when :state then normalized == ".planning/STATE.md"
|
|
31
|
-
when :workflow then normalized.match?(%r{\A\.planning/workflows/[A-Za-z0-9][A-Za-z0-9_-]*\.yaml\z})
|
|
32
|
-
when :registry then normalized == ".planning/artifacts/index.yaml"
|
|
33
|
-
when :artifact then normalized.start_with?(".planning/") || normalized.start_with?(".context/")
|
|
73
|
+
when :state then normalized == ".devflow/planning/STATE.md"
|
|
74
|
+
when :workflow then normalized.match?(%r{\A\.devflow/planning/workflows/[A-Za-z0-9][A-Za-z0-9_-]*\.yaml\z})
|
|
75
|
+
when :registry then normalized == ".devflow/planning/artifacts/index.yaml"
|
|
76
|
+
when :artifact then normalized.start_with?(".devflow/planning/") || normalized.start_with?(".devflow/context/")
|
|
34
77
|
# Feature names are user-facing and may be Unicode. `cleanpath`
|
|
35
78
|
# and the parent-traversal check above own containment; this
|
|
36
79
|
# matcher only fixes the handoff namespace and immutable name.
|
|
37
|
-
when :handoff then normalized.match?(%r{\A\.planning/handoffs/[^/]+/ROL-[0-9a-f-]+\.json\z})
|
|
80
|
+
when :handoff then normalized.match?(%r{\A\.devflow/planning/handoffs/[^/]+/ROL-[0-9a-f-]+\.json\z})
|
|
38
81
|
else false
|
|
39
82
|
end
|
|
40
83
|
raise InvalidControlPath, "invalid #{kind} control path" unless allowed
|
|
@@ -58,7 +101,8 @@ module DevflowControlIO
|
|
|
58
101
|
end
|
|
59
102
|
|
|
60
103
|
def acquire_project_lock(root)
|
|
61
|
-
|
|
104
|
+
assert_layout_ready(root)
|
|
105
|
+
planning_dir = File.join(root, ".devflow/planning")
|
|
62
106
|
FileUtils.mkdir_p(planning_dir)
|
|
63
107
|
handle = File.open(File.join(planning_dir, ".devflow.lock"), File::RDWR | File::CREAT, 0o644)
|
|
64
108
|
handle.flock(File::LOCK_EX)
|
|
@@ -122,7 +166,7 @@ module DevflowControlIO
|
|
|
122
166
|
# 独立受控工件登记:与 sync_artifacts 的 workflow-outputs registry 分开,
|
|
123
167
|
# 避免被 workflow 重建覆盖。按 path 去重,last-write-wins。
|
|
124
168
|
def register_control_artifact(root, entry)
|
|
125
|
-
path = File.join(root, ".planning", "artifacts", "control-artifacts.json")
|
|
169
|
+
path = File.join(root, ".devflow/planning", "artifacts", "control-artifacts.json")
|
|
126
170
|
existing = File.exist?(path) ? JSON.parse(File.read(path)) : { "schema_version" => 1, "artifacts" => [] }
|
|
127
171
|
existing["artifacts"] ||= []
|
|
128
172
|
existing["artifacts"].reject! { |item| item["path"] == entry["path"] }
|
package/plugins/devflow-core/skills/workflow-orchestrator/scripts/cross_track_consistency_check.rb
CHANGED
|
@@ -16,7 +16,7 @@ feature = options[:feature].to_s
|
|
|
16
16
|
raise ArgumentError, "feature contains path separators" if feature.include?("/") || feature.include?("\\")
|
|
17
17
|
|
|
18
18
|
root = Dir.pwd
|
|
19
|
-
base = File.join(root, ".planning", "features", feature)
|
|
19
|
+
base = File.join(root, ".devflow/planning", "features", feature)
|
|
20
20
|
files = {
|
|
21
21
|
"api_contract" => File.join(base, "api-contract.md"),
|
|
22
22
|
"api_call_guide" => File.join(base, "api-call-guide.md"),
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Structural coverage of a frozen TC by its v1 DSL (方案 §6.1, first layer).
|
|
3
|
+
|
|
4
|
+
Every frozen precondition and step must be covered by at least one DSL step;
|
|
5
|
+
the oracle must be covered by at least one non-weak assertion. This only
|
|
6
|
+
detects omissions and obviously weak assertions; semantic adequacy remains the
|
|
7
|
+
job of the human-approved execution plan.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from dsl_v1 import UI_ASSERTIONS, UI_INTERACTIONS, all_steps
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_assertion(step: dict[str, Any]) -> bool:
|
|
17
|
+
if step.get("driver") == "ui":
|
|
18
|
+
return step.get("action") in UI_ASSERTIONS
|
|
19
|
+
return step.get("driver") == "api" and (bool(step.get("assertions")) or "expect_status" in step)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def weak_reason(step: dict[str, Any], preceding: list[dict[str, Any]]) -> str | None:
|
|
23
|
+
"""MVP weak assertions: assert_visible of a static entry element; 2xx-only API checks."""
|
|
24
|
+
if step.get("driver") == "api":
|
|
25
|
+
status = step.get("expect_status")
|
|
26
|
+
if not step.get("assertions") and (status is None or 200 <= status <= 299):
|
|
27
|
+
return "API assertion only checks a 2xx status without response body assertions"
|
|
28
|
+
return None
|
|
29
|
+
if step.get("action") != "assert_visible":
|
|
30
|
+
return None
|
|
31
|
+
interactions = [item for item in preceding if (item.get("driver") == "ui" and item.get("action") in UI_INTERACTIONS)
|
|
32
|
+
or (item.get("driver") == "api" and item.get("method") != "GET")]
|
|
33
|
+
if not interactions:
|
|
34
|
+
return "assert_visible before any interaction only proves a static entry element is present"
|
|
35
|
+
if any(item.get("target") == step.get("target") for item in interactions):
|
|
36
|
+
return "assert_visible on an element the flow itself interacted with does not verify an outcome"
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _count(data: dict[str, Any], key: str) -> int:
|
|
41
|
+
value = data.get(key)
|
|
42
|
+
if isinstance(value, list):
|
|
43
|
+
return len(value)
|
|
44
|
+
return 1 if isinstance(value, str) and value.strip() else 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def check(tc_data: dict[str, Any], dsl: dict[str, Any]) -> list[str]:
|
|
48
|
+
errors: list[str] = []
|
|
49
|
+
counts = {"preconditions": _count(tc_data, "preconditions"), "steps": _count(tc_data, "steps")}
|
|
50
|
+
covered: dict[str, set[int]] = {"preconditions": set(), "steps": set()}
|
|
51
|
+
oracle = tc_data.get("oracle")
|
|
52
|
+
has_oracle = (isinstance(oracle, str) and bool(oracle.strip())) or (isinstance(oracle, list) and bool(oracle))
|
|
53
|
+
oracle_assertions: list[tuple[str, dict[str, Any], str | None]] = []
|
|
54
|
+
main: list[dict[str, Any]] = []
|
|
55
|
+
for phase, path, step in all_steps(dsl):
|
|
56
|
+
covers = step.get("covers") if isinstance(step.get("covers"), dict) else {}
|
|
57
|
+
if phase == "cleanup" and covers:
|
|
58
|
+
errors.append(f"{path}.covers is not allowed on cleanup steps")
|
|
59
|
+
continue
|
|
60
|
+
for key in ("preconditions", "steps"):
|
|
61
|
+
for index in covers.get(key, []):
|
|
62
|
+
if not isinstance(index, int) or isinstance(index, bool) or not 1 <= index <= counts[key]:
|
|
63
|
+
errors.append(f"{path}.covers.{key} index {index} is outside the frozen TC {key} (1..{counts[key]})")
|
|
64
|
+
else:
|
|
65
|
+
covered[key].add(index)
|
|
66
|
+
if covers.get("oracle") is True:
|
|
67
|
+
if not is_assertion(step):
|
|
68
|
+
errors.append(f"{path} covers the oracle but {step.get('action')} is not an assertion")
|
|
69
|
+
elif phase != "main":
|
|
70
|
+
errors.append(f"{path} covers the oracle outside the main steps")
|
|
71
|
+
else:
|
|
72
|
+
oracle_assertions.append((path, step, weak_reason(step, main)))
|
|
73
|
+
if phase == "main":
|
|
74
|
+
main.append(step)
|
|
75
|
+
for key in ("preconditions", "steps"):
|
|
76
|
+
missing = sorted(set(range(1, counts[key] + 1)) - covered[key])
|
|
77
|
+
if missing:
|
|
78
|
+
errors.append(f"frozen TC {key} not covered by any DSL step: {', '.join(map(str, missing))}")
|
|
79
|
+
if has_oracle and not oracle_assertions:
|
|
80
|
+
errors.append("frozen TC oracle is not covered by any assertion step")
|
|
81
|
+
elif oracle_assertions and all(reason for _path, _step, reason in oracle_assertions):
|
|
82
|
+
errors.extend(f"{path}: weak oracle assertion ({reason})" for path, _step, reason in oracle_assertions)
|
|
83
|
+
errors.append("frozen TC oracle is covered only by weak assertions")
|
|
84
|
+
if tc_data.get("execution_surface") == "ui" or (tc_data.get("execution_surface") is None and tc_data.get("test_type") == "page"):
|
|
85
|
+
if oracle_assertions and not any(step.get("driver") == "ui" and not reason for _path, step, reason in oracle_assertions):
|
|
86
|
+
errors.append("UI TC oracle must be covered by a non-weak page assertion; API checks cannot replace it")
|
|
87
|
+
return errors
|