@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
|
@@ -59,7 +59,7 @@ OptionParser.new do |opts|
|
|
|
59
59
|
end.parse!
|
|
60
60
|
|
|
61
61
|
root = Dir.pwd
|
|
62
|
-
state_path, = DevflowControlIO.resolve_control_path(root, options[:state] || ".planning/STATE.md", :state)
|
|
62
|
+
state_path, = DevflowControlIO.resolve_control_path(root, options[:state] || ".devflow/planning/STATE.md", :state)
|
|
63
63
|
|
|
64
64
|
def front_matter(markdown)
|
|
65
65
|
match = markdown.match(/\A---\n(.*?)\n---\n/m)
|
|
@@ -71,7 +71,7 @@ def load_yaml_file(path)
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def workflow_path_for(root, state)
|
|
74
|
-
state["workflow_config"] || File.join(".planning", "workflows", "#{state.fetch("current_flow")}.yaml")
|
|
74
|
+
state["workflow_config"] || File.join(".devflow/planning", "workflows", "#{state.fetch("current_flow")}.yaml")
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def load_workflow(root, state)
|
|
@@ -384,7 +384,7 @@ def checkpoint_label(workflow, checkpoint_id)
|
|
|
384
384
|
end
|
|
385
385
|
|
|
386
386
|
def next_intent_path(root, workflow)
|
|
387
|
-
workflow.dig("resume_rules", "continue_keyword", "next_intent_path") || ".planning/.next-intent"
|
|
387
|
+
workflow.dig("resume_rules", "continue_keyword", "next_intent_path") || ".devflow/planning/.next-intent"
|
|
388
388
|
end
|
|
389
389
|
|
|
390
390
|
def write_next_intent(root, workflow)
|
|
@@ -558,6 +558,13 @@ HARD_FAILURE_REASONS = %w[
|
|
|
558
558
|
cross_track_consistency_failed
|
|
559
559
|
business_cross_model_check_failed
|
|
560
560
|
gate_boundary_rejected
|
|
561
|
+
acceptance_results_not_passed
|
|
562
|
+
execution_surface_mismatch
|
|
563
|
+
approval_invalid
|
|
564
|
+
human_result_unapproved
|
|
565
|
+
dsl_not_approved
|
|
566
|
+
environment_profile_not_approved
|
|
567
|
+
runtime_integrity_error
|
|
561
568
|
].freeze
|
|
562
569
|
$devflow_advisories = []
|
|
563
570
|
|
|
@@ -588,6 +595,32 @@ rescue JSON::ParserError => error
|
|
|
588
595
|
{ "ok" => false, "reason" => "test_task_binding_#{stage}_check_failed", "errors" => ["checker returned invalid JSON: #{error.message}"] }
|
|
589
596
|
end
|
|
590
597
|
|
|
598
|
+
# Acceptance results are a hard gate even in the default soft mode: Core itself
|
|
599
|
+
# re-validates the frozen-suite results instead of trusting a checker summary.
|
|
600
|
+
ACCEPTANCE_RESULT_GATED_STEPS = %w[run-acceptance-tests release-closeout].freeze
|
|
601
|
+
|
|
602
|
+
def acceptance_results_check(root, feature)
|
|
603
|
+
script = File.join(__dir__, "acceptance_test_runner.py")
|
|
604
|
+
stdout, stderr, status = Open3.capture3("python3", script, "validate-results", "--feature", feature, "--json", chdir: root)
|
|
605
|
+
payload = JSON.parse(stdout)
|
|
606
|
+
return payload if status.success? && payload.is_a?(Hash) && payload["ok"] == true
|
|
607
|
+
|
|
608
|
+
{ "ok" => false, "errors" => payload.is_a?(Hash) && payload["errors"].is_a?(Array) ? payload["errors"] : [stderr.strip.empty? ? "acceptance results checker returned invalid output" : stderr.strip] }
|
|
609
|
+
rescue JSON::ParserError => error
|
|
610
|
+
{ "ok" => false, "errors" => ["acceptance results checker returned invalid JSON: #{error.message}"] }
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def execution_surface_check(root, feature)
|
|
614
|
+
script = File.join(__dir__, "execution_surface.py")
|
|
615
|
+
stdout, stderr, status = Open3.capture3("python3", script, "check-suite", "--feature", feature, "--json", chdir: root)
|
|
616
|
+
payload = JSON.parse(stdout)
|
|
617
|
+
return payload if status.success? && payload.is_a?(Hash) && payload["ok"] == true
|
|
618
|
+
|
|
619
|
+
{ "ok" => false, "errors" => payload.is_a?(Hash) && payload["errors"].is_a?(Array) ? payload["errors"] : [stderr.strip.empty? ? "execution surface checker returned invalid output" : stderr.strip] }
|
|
620
|
+
rescue JSON::ParserError => error
|
|
621
|
+
{ "ok" => false, "errors" => ["execution surface checker returned invalid JSON: #{error.message}"] }
|
|
622
|
+
end
|
|
623
|
+
|
|
591
624
|
def api_test_matrix_check(root, feature, graph)
|
|
592
625
|
return { "ok" => true, "skipped" => "no_api_nodes" } unless Array(graph["nodes"]).any? { |node| node.is_a?(Hash) && node["kind"] == "api" }
|
|
593
626
|
|
|
@@ -635,16 +668,16 @@ end
|
|
|
635
668
|
|
|
636
669
|
def business_cross_model_check(root, feature)
|
|
637
670
|
script = File.join(__dir__, "business_cross_model_check.py")
|
|
638
|
-
business = ".planning/business-model/#{feature}/canonical.json"
|
|
639
|
-
wireframe = ".planning/features/#{feature}/canonical.json"
|
|
671
|
+
business = ".devflow/planning/business-model/#{feature}/canonical.json"
|
|
672
|
+
wireframe = ".devflow/planning/features/#{feature}/canonical.json"
|
|
640
673
|
unless File.file?(File.join(root, business))
|
|
641
674
|
return { "valid" => false, "errors" => [{ "code" => "BUSINESS_CANONICAL_MISSING", "path" => business }] }
|
|
642
675
|
end
|
|
643
676
|
command = ["python3", script, "--business-canonical", business]
|
|
644
677
|
command += ["--wireframe", wireframe] if File.file?(File.join(root, wireframe))
|
|
645
|
-
claims = ".planning/features/#{feature}/business-claims.yaml"
|
|
678
|
+
claims = ".devflow/planning/features/#{feature}/business-claims.yaml"
|
|
646
679
|
command += ["--claims", claims] if File.file?(File.join(root, claims))
|
|
647
|
-
traceability = ".planning/requirements/#{feature}/traceability.json"
|
|
680
|
+
traceability = ".devflow/planning/requirements/#{feature}/traceability.json"
|
|
648
681
|
command += ["--traceability", traceability] if File.file?(File.join(root, traceability))
|
|
649
682
|
stdout, stderr, status = Open3.capture3(*command, "--json", chdir: root)
|
|
650
683
|
payload = JSON.parse(stdout)
|
|
@@ -675,7 +708,7 @@ rescue JSON::ParserError => error
|
|
|
675
708
|
end
|
|
676
709
|
|
|
677
710
|
def test_suite_freeze_attestation_path(root, feature)
|
|
678
|
-
DevflowControlIO.resolve_control_path(root, ".planning/features/#{feature}/test-suite-freeze.json", :artifact)
|
|
711
|
+
DevflowControlIO.resolve_control_path(root, ".devflow/planning/features/#{feature}/test-suite-freeze.json", :artifact)
|
|
679
712
|
end
|
|
680
713
|
|
|
681
714
|
def frozen_test_suite_pin(freeze_result)
|
|
@@ -976,6 +1009,18 @@ result =
|
|
|
976
1009
|
)
|
|
977
1010
|
end
|
|
978
1011
|
|
|
1012
|
+
acceptance_gate_step = if options[:complete_step]
|
|
1013
|
+
options[:complete_step].to_s
|
|
1014
|
+
elsif options[:confirm_checkpoint] && state["status"] == "waiting_checkpoint"
|
|
1015
|
+
state["current_step"].to_s
|
|
1016
|
+
end
|
|
1017
|
+
if result.nil? && ACCEPTANCE_RESULT_GATED_STEPS.include?(acceptance_gate_step) && step_by_id.key?(acceptance_gate_step)
|
|
1018
|
+
acceptance_check = acceptance_results_check(root, state.fetch("current_feature"))
|
|
1019
|
+
unless acceptance_check["ok"]
|
|
1020
|
+
result = fail_result("acceptance_results_not_passed", "step" => acceptance_gate_step, "acceptance_results" => acceptance_check)
|
|
1021
|
+
end
|
|
1022
|
+
end
|
|
1023
|
+
|
|
979
1024
|
if result.nil? && options[:complete_step]
|
|
980
1025
|
step_id = options[:complete_step]
|
|
981
1026
|
step = step_by_id[step_id]
|
|
@@ -1106,9 +1151,15 @@ result =
|
|
|
1106
1151
|
end
|
|
1107
1152
|
else
|
|
1108
1153
|
matrix_check = api_test_matrix_check(root, feature, graph)
|
|
1154
|
+
# A new freeze requires every TC to declare a consistent
|
|
1155
|
+
# execution_surface; this is a hard failure even in soft mode.
|
|
1156
|
+
surface_check = matrix_check["ok"] ? execution_surface_check(root, feature) : nil
|
|
1109
1157
|
unless matrix_check["ok"]
|
|
1110
1158
|
result = fail_result("api_test_matrix_incomplete", "api_test_matrix" => matrix_check)
|
|
1111
1159
|
else
|
|
1160
|
+
result = fail_result("execution_surface_mismatch", "execution_surface" => surface_check) unless surface_check["ok"]
|
|
1161
|
+
end
|
|
1162
|
+
if matrix_check["ok"] && surface_check["ok"]
|
|
1112
1163
|
candidate_check = test_task_binding_check(root, feature, "candidate")
|
|
1113
1164
|
unless candidate_check["ok"]
|
|
1114
1165
|
result = fail_result("acceptance_test_suite_candidate_binding_failed", "test_suite" => candidate_check)
|
|
@@ -1218,7 +1269,7 @@ result =
|
|
|
1218
1269
|
prospective_owner = if changed
|
|
1219
1270
|
{
|
|
1220
1271
|
"phase" => state["current_phase"], "feature" => state["current_feature"],
|
|
1221
|
-
"flow" => state["current_flow"], "state_path" => ".planning/STATE.md",
|
|
1272
|
+
"flow" => state["current_flow"], "state_path" => ".devflow/planning/STATE.md",
|
|
1222
1273
|
"scope" => "global", "status" => "active"
|
|
1223
1274
|
}
|
|
1224
1275
|
end
|
|
@@ -72,8 +72,8 @@ def write_text(path: Path, value: str) -> None:
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def paths(root: Path, feature: str) -> tuple[Path, Path, Path, Path]:
|
|
75
|
-
requirements = root / ".planning" / "requirements" / feature
|
|
76
|
-
feature_dir = root / ".planning" / "features" / feature
|
|
75
|
+
requirements = root / ".devflow/planning" / "requirements" / feature
|
|
76
|
+
feature_dir = root / ".devflow/planning" / "features" / feature
|
|
77
77
|
return (
|
|
78
78
|
requirements / "governance.json",
|
|
79
79
|
requirements / "api-test-matrix.json",
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Compute the current pins for a Core human-approval subject.
|
|
3
|
+
|
|
4
|
+
This module is the single pin implementation shared by the runtime extension
|
|
5
|
+
(propose + confirm CAS) and the Python approval reader. It only reads project
|
|
6
|
+
files; it never writes control.db. There is intentionally no confirm entry
|
|
7
|
+
point here: confirmation exists only in the runtime UI callback.
|
|
8
|
+
|
|
9
|
+
Pins per kind (change list §1.8):
|
|
10
|
+
|
|
11
|
+
- execution_plan {suite_graph_revision, suite_fingerprint, tc_sha256, dsl_sha256}
|
|
12
|
+
- environment_profile {profile_sha256}
|
|
13
|
+
- mode_override {suite_fingerprint, tc_sha256, binding_entry_sha256, from, to}
|
|
14
|
+
- human_result {suite_fingerprint, tc_sha256, evidence: [{path, sha256}]}
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import hashlib
|
|
21
|
+
import json
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
28
|
+
if str(SCRIPT_DIR) not in sys.path:
|
|
29
|
+
sys.path.insert(0, str(SCRIPT_DIR))
|
|
30
|
+
|
|
31
|
+
KINDS = ("execution_plan", "environment_profile", "mode_override", "human_result")
|
|
32
|
+
FEATURE_RE = re.compile(r"^[^/\\\x00-\x1f\x7f]+$")
|
|
33
|
+
TC_ID_RE = re.compile(r"^TC-[A-Za-z0-9_-]+$")
|
|
34
|
+
PROFILE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$")
|
|
35
|
+
MAX_EVIDENCE_FILE_BYTES = 25 * 1024 * 1024
|
|
36
|
+
MAX_DSL_FILE_BYTES = 2 * 1024 * 1024
|
|
37
|
+
MAX_EVIDENCE_FILES = 50
|
|
38
|
+
SUMMARY_LIMIT = 300
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PinError(Exception):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _import_tc_hash():
|
|
46
|
+
try:
|
|
47
|
+
import tc_hash # type: ignore
|
|
48
|
+
except ImportError as error: # pragma: no cover - depends on concurrent Core work
|
|
49
|
+
raise PinError(f"tc_hash module is unavailable: {error}") from error
|
|
50
|
+
return tc_hash
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def jcs_sha256(value: Any) -> str:
|
|
54
|
+
"""sha256(JCS(value)) using the single Core JCS implementation."""
|
|
55
|
+
tc_hash = _import_tc_hash()
|
|
56
|
+
return hashlib.sha256(tc_hash.canonicalize(value)).hexdigest()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def tc_sha256(data: dict[str, Any]) -> str:
|
|
60
|
+
return str(_import_tc_hash().tc_sha256(data))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _read_json(path: Path, label: str) -> Any:
|
|
64
|
+
try:
|
|
65
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
66
|
+
except FileNotFoundError as error:
|
|
67
|
+
raise PinError(f"missing {label}: {path.as_posix()}") from error
|
|
68
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
|
|
69
|
+
raise PinError(f"invalid {label}: {error}") from error
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def workflow_state(root: Path) -> dict[str, Any]:
|
|
73
|
+
"""Read revision and current_feature from STATE.md front matter."""
|
|
74
|
+
path = root / ".devflow/planning/STATE.md"
|
|
75
|
+
try:
|
|
76
|
+
text = path.read_text(encoding="utf-8")
|
|
77
|
+
except OSError as error:
|
|
78
|
+
raise PinError(f"workflow state is unavailable: {path.as_posix()}") from error
|
|
79
|
+
match = re.match(r"\A---\s*\n(.*?)\n---\s*(?:\n|\Z)", text, re.S)
|
|
80
|
+
if not match:
|
|
81
|
+
raise PinError("workflow state has no front matter")
|
|
82
|
+
fields: dict[str, str] = {}
|
|
83
|
+
for line in match.group(1).splitlines():
|
|
84
|
+
found = re.match(r"^([A-Za-z_][A-Za-z0-9_]*):\s*(.*?)\s*$", line)
|
|
85
|
+
if found:
|
|
86
|
+
fields[found.group(1)] = found.group(2)
|
|
87
|
+
raw_revision = fields.get("revision", "0").strip("'\"") or "0"
|
|
88
|
+
if not re.fullmatch(r"\d+", raw_revision):
|
|
89
|
+
raise PinError(f"workflow state revision is not an integer: {raw_revision}")
|
|
90
|
+
feature = fields.get("current_feature", "").strip()
|
|
91
|
+
if len(feature) >= 2 and feature[0] == feature[-1] and feature[0] in "'\"":
|
|
92
|
+
feature = feature[1:-1]
|
|
93
|
+
return {"revision": int(raw_revision), "current_feature": feature}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def load_suite(root: Path, feature: str) -> dict[str, Any]:
|
|
97
|
+
suite = _read_json(root / ".devflow/planning/requirements" / feature / "tests.json", "TC suite")
|
|
98
|
+
if not isinstance(suite, dict):
|
|
99
|
+
raise PinError("TC suite root must be an object")
|
|
100
|
+
return suite
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def suite_pins(suite: dict[str, Any]) -> dict[str, Any]:
|
|
104
|
+
meta = suite.get("view_meta") if isinstance(suite.get("view_meta"), dict) else {}
|
|
105
|
+
fingerprint = meta.get("fingerprint")
|
|
106
|
+
if not isinstance(fingerprint, str) or not fingerprint:
|
|
107
|
+
raise PinError("TC suite view_meta.fingerprint is missing")
|
|
108
|
+
revision = suite.get("graph_revision")
|
|
109
|
+
if revision is None or isinstance(revision, bool):
|
|
110
|
+
raise PinError("TC suite graph_revision is missing")
|
|
111
|
+
return {"suite_fingerprint": fingerprint, "suite_graph_revision": revision}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def frozen_case(suite: dict[str, Any], tc_id: str) -> dict[str, Any]:
|
|
115
|
+
for node in suite.get("nodes", []):
|
|
116
|
+
if isinstance(node, dict) and node.get("kind") == "test_case" and node.get("id") == tc_id:
|
|
117
|
+
if node.get("status") != "confirmed" or node.get("stale") is True:
|
|
118
|
+
raise PinError(f"TC is not frozen and fresh: {tc_id}")
|
|
119
|
+
data = node.get("data")
|
|
120
|
+
if not isinstance(data, dict):
|
|
121
|
+
raise PinError(f"TC has no data object: {tc_id}")
|
|
122
|
+
return data
|
|
123
|
+
raise PinError(f"TC is not in the frozen suite: {tc_id}")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def binding_entry(root: Path, feature: str, tc_id: str, required: bool) -> dict[str, Any] | None:
|
|
127
|
+
path = root / ".devflow/planning/features" / feature / "acceptance-test-bindings.json"
|
|
128
|
+
if not path.is_file():
|
|
129
|
+
if required:
|
|
130
|
+
raise PinError(f"missing acceptance-test bindings: {path.relative_to(root).as_posix()}")
|
|
131
|
+
return None
|
|
132
|
+
bindings = _read_json(path, "acceptance-test bindings")
|
|
133
|
+
cases = bindings.get("cases") if isinstance(bindings, dict) else None
|
|
134
|
+
if not isinstance(cases, list):
|
|
135
|
+
raise PinError("acceptance-test bindings cases must be an array")
|
|
136
|
+
matches = [entry for entry in cases if isinstance(entry, dict) and entry.get("tc_id") == tc_id]
|
|
137
|
+
if len(matches) > 1:
|
|
138
|
+
raise PinError(f"duplicate binding entries for {tc_id}")
|
|
139
|
+
if not matches:
|
|
140
|
+
if required:
|
|
141
|
+
raise PinError(f"no binding entry for {tc_id}")
|
|
142
|
+
return None
|
|
143
|
+
return matches[0]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _contained_file(root: Path, raw: Any, base: Path, label: str, max_bytes: int) -> tuple[Path, str]:
|
|
147
|
+
if not isinstance(raw, str) or not raw.strip() or "\x00" in raw:
|
|
148
|
+
raise PinError(f"{label} must be a non-empty project-relative path")
|
|
149
|
+
candidate = Path(raw)
|
|
150
|
+
if candidate.is_absolute() or ".." in candidate.parts:
|
|
151
|
+
raise PinError(f"{label} escapes the project root: {raw}")
|
|
152
|
+
root_real = root.resolve()
|
|
153
|
+
resolved = (root_real / candidate).resolve()
|
|
154
|
+
try:
|
|
155
|
+
relative = resolved.relative_to(root_real)
|
|
156
|
+
resolved.relative_to(base.resolve())
|
|
157
|
+
except ValueError as error:
|
|
158
|
+
raise PinError(f"{label} is outside {base.relative_to(root).as_posix() if base != root else 'the project root'}: {raw}") from error
|
|
159
|
+
if not resolved.is_file():
|
|
160
|
+
raise PinError(f"{label} is missing: {raw}")
|
|
161
|
+
if resolved.stat().st_size > max_bytes:
|
|
162
|
+
raise PinError(f"{label} exceeds {max_bytes} bytes: {raw}")
|
|
163
|
+
return resolved, relative.as_posix()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _truncate(value: Any, limit: int = SUMMARY_LIMIT) -> str:
|
|
167
|
+
text = value if isinstance(value, str) else json.dumps(value, ensure_ascii=False, sort_keys=True)
|
|
168
|
+
return text if len(text) <= limit else text[: limit - 1] + "…"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _target_text(target: Any) -> str:
|
|
172
|
+
if not isinstance(target, dict):
|
|
173
|
+
return ""
|
|
174
|
+
for key in ("testid", "role", "label", "placeholder", "text", "css"):
|
|
175
|
+
if key in target:
|
|
176
|
+
text = f"{key}={target[key]}"
|
|
177
|
+
if key == "role" and target.get("name") is not None:
|
|
178
|
+
text += f"[name={target['name']}]"
|
|
179
|
+
return text
|
|
180
|
+
return _truncate(target, 120)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def assertion_summary(step: dict[str, Any]) -> str:
|
|
184
|
+
"""Human-readable summary of one DSL step for the approval UI."""
|
|
185
|
+
action = str(step.get("action", "?"))
|
|
186
|
+
identifier = str(step.get("id", "?"))
|
|
187
|
+
if step.get("driver") == "api" or "request" in step or "method" in step:
|
|
188
|
+
request = step.get("request") if isinstance(step.get("request"), dict) else step
|
|
189
|
+
method = str(request.get("method", "")).upper()
|
|
190
|
+
url = request.get("url") or request.get("path") or ""
|
|
191
|
+
assertions = step.get("assertions", step.get("expect"))
|
|
192
|
+
return f"{identifier}: API {method} {url} 断言 {_truncate(assertions, 200)}".strip()
|
|
193
|
+
target = _target_text(step.get("target"))
|
|
194
|
+
parts = [f"{identifier}: {action}"]
|
|
195
|
+
if target:
|
|
196
|
+
parts.append(target)
|
|
197
|
+
if "expected" in step:
|
|
198
|
+
match = step.get("match", "exact")
|
|
199
|
+
parts.append(f"{match} = {_truncate(step.get('expected'), 200)}")
|
|
200
|
+
elif "url" in step and action == "assert_url":
|
|
201
|
+
parts.append(f"url {_truncate(step.get('url'), 200)}")
|
|
202
|
+
return " ".join(parts)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def dsl_steps(dsl: dict[str, Any]) -> list[dict[str, Any]]:
|
|
206
|
+
steps = [step for step in dsl.get("steps", []) if isinstance(step, dict)]
|
|
207
|
+
data = dsl.get("data") if isinstance(dsl.get("data"), dict) else {}
|
|
208
|
+
setup = [step for step in data.get("setup", []) if isinstance(step, dict)] if isinstance(data.get("setup"), list) else []
|
|
209
|
+
return setup + steps
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def coverage_display(tc_data: dict[str, Any], dsl: dict[str, Any]) -> dict[str, Any]:
|
|
213
|
+
oracle_steps = []
|
|
214
|
+
covered_pre: set[int] = set()
|
|
215
|
+
covered_steps: set[int] = set()
|
|
216
|
+
for step in dsl_steps(dsl):
|
|
217
|
+
covers = step.get("covers") if isinstance(step.get("covers"), dict) else {}
|
|
218
|
+
if covers.get("oracle") is True:
|
|
219
|
+
oracle_steps.append(assertion_summary(step))
|
|
220
|
+
for key, target in (("preconditions", covered_pre), ("steps", covered_steps)):
|
|
221
|
+
values = covers.get(key)
|
|
222
|
+
if isinstance(values, list):
|
|
223
|
+
target.update(item for item in values if isinstance(item, int) and not isinstance(item, bool))
|
|
224
|
+
preconditions = tc_data.get("preconditions") if isinstance(tc_data.get("preconditions"), list) else []
|
|
225
|
+
steps = tc_data.get("steps") if isinstance(tc_data.get("steps"), list) else []
|
|
226
|
+
return {
|
|
227
|
+
"oracle_assertions": oracle_steps,
|
|
228
|
+
"uncovered_preconditions": [index for index in range(1, len(preconditions) + 1) if index not in covered_pre],
|
|
229
|
+
"uncovered_steps": [index for index in range(1, len(steps) + 1) if index not in covered_steps],
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def structural_validation(tc_data: dict[str, Any], dsl: dict[str, Any]) -> tuple[str, list[str]]:
|
|
234
|
+
"""Run the Core DSL validators when they are installed (K05/K06)."""
|
|
235
|
+
errors: list[str] = []
|
|
236
|
+
try:
|
|
237
|
+
import dsl_v1 # type: ignore
|
|
238
|
+
import dsl_coverage # type: ignore
|
|
239
|
+
except ImportError:
|
|
240
|
+
return "unavailable", []
|
|
241
|
+
errors.extend(str(item) for item in dsl_v1.validate(dsl) or [])
|
|
242
|
+
if not errors:
|
|
243
|
+
errors.extend(str(item) for item in dsl_coverage.check(tc_data, dsl) or [])
|
|
244
|
+
return ("failed" if errors else "passed"), errors
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def tc_display(tc_id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
248
|
+
return {
|
|
249
|
+
"tc_id": tc_id,
|
|
250
|
+
"target": data.get("target"),
|
|
251
|
+
"oracle": data.get("oracle"),
|
|
252
|
+
"preconditions": data.get("preconditions") if isinstance(data.get("preconditions"), list) else [],
|
|
253
|
+
"steps": data.get("steps") if isinstance(data.get("steps"), list) else [],
|
|
254
|
+
"automation_mode": data.get("automation_mode"),
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _require_tc(subject_key: str) -> None:
|
|
259
|
+
if not TC_ID_RE.fullmatch(subject_key):
|
|
260
|
+
raise PinError(f"subject_key must be a TC id: {subject_key}")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def compute_execution_plan(root: Path, feature: str, tc_id: str, payload: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
264
|
+
_require_tc(tc_id)
|
|
265
|
+
suite = load_suite(root, feature)
|
|
266
|
+
pins_suite = suite_pins(suite)
|
|
267
|
+
data = frozen_case(suite, tc_id)
|
|
268
|
+
if data.get("automation_mode") != "automated":
|
|
269
|
+
raise PinError(f"execution_plan applies only to automated TCs: {tc_id} is {data.get('automation_mode')}")
|
|
270
|
+
base = root / ".devflow/planning/generated-cases" / feature
|
|
271
|
+
raw = payload.get("dsl_file") or f".devflow/planning/generated-cases/{feature}/{tc_id}.json"
|
|
272
|
+
path, relative = _contained_file(root, raw, base, "dsl_file", MAX_DSL_FILE_BYTES)
|
|
273
|
+
content = path.read_bytes()
|
|
274
|
+
try:
|
|
275
|
+
dsl = json.loads(content.decode("utf-8"))
|
|
276
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as error:
|
|
277
|
+
raise PinError(f"DSL is not valid JSON: {error}") from error
|
|
278
|
+
if not isinstance(dsl, dict):
|
|
279
|
+
raise PinError("DSL root must be an object")
|
|
280
|
+
current_tc = tc_sha256(data)
|
|
281
|
+
if dsl.get("tc_id") != tc_id:
|
|
282
|
+
raise PinError(f"DSL tc_id {dsl.get('tc_id')!r} does not match {tc_id}")
|
|
283
|
+
if dsl.get("tc_sha256") != current_tc:
|
|
284
|
+
raise PinError("DSL tc_sha256 does not match the current frozen TC")
|
|
285
|
+
validation, validation_errors = structural_validation(data, dsl)
|
|
286
|
+
if validation_errors:
|
|
287
|
+
raise PinError("DSL structural validation failed: " + "; ".join(validation_errors[:20]))
|
|
288
|
+
pins = {
|
|
289
|
+
"suite_graph_revision": pins_suite["suite_graph_revision"],
|
|
290
|
+
"suite_fingerprint": pins_suite["suite_fingerprint"],
|
|
291
|
+
"tc_sha256": current_tc,
|
|
292
|
+
"dsl_sha256": hashlib.sha256(content).hexdigest(),
|
|
293
|
+
}
|
|
294
|
+
display = {**tc_display(tc_id, data), "dsl_file": relative, "structural_validation": validation, **coverage_display(data, dsl)}
|
|
295
|
+
return pins, display
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def compute_environment_profile(root: Path, feature: str, name: str, payload: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
299
|
+
if not PROFILE_RE.fullmatch(name):
|
|
300
|
+
raise PinError(f"invalid environment profile name: {name}")
|
|
301
|
+
try:
|
|
302
|
+
import environment_profile # type: ignore
|
|
303
|
+
except ImportError as error: # pragma: no cover - depends on concurrent Core work
|
|
304
|
+
raise PinError(f"environment_profile module is unavailable: {error}") from error
|
|
305
|
+
profile, sha256, errors = environment_profile.load(root, name)
|
|
306
|
+
if errors:
|
|
307
|
+
raise PinError("environment profile is invalid: " + "; ".join(str(item) for item in errors))
|
|
308
|
+
if not isinstance(profile, dict) or not isinstance(sha256, str) or not sha256:
|
|
309
|
+
raise PinError("environment profile loader returned no profile hash")
|
|
310
|
+
secret_env = profile.get("secret_env") if isinstance(profile.get("secret_env"), dict) else {}
|
|
311
|
+
display = {
|
|
312
|
+
"profile": name,
|
|
313
|
+
"path": f".devflow/planning/test-environments/{name}.json",
|
|
314
|
+
"base_url": profile.get("base_url"),
|
|
315
|
+
"navigation_origins": profile.get("navigation_origins", []),
|
|
316
|
+
"api_origins": profile.get("api_origins", []),
|
|
317
|
+
"subresources": profile.get("subresources"),
|
|
318
|
+
# Names only: the profile maps aliases to environment variable names, never values.
|
|
319
|
+
"secret_env": {str(key): str(value) for key, value in secret_env.items()},
|
|
320
|
+
"trace": profile.get("trace"),
|
|
321
|
+
}
|
|
322
|
+
return {"profile_sha256": sha256}, display
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def compute_mode_override(root: Path, feature: str, tc_id: str, payload: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
326
|
+
_require_tc(tc_id)
|
|
327
|
+
suite = load_suite(root, feature)
|
|
328
|
+
pins_suite = suite_pins(suite)
|
|
329
|
+
data = frozen_case(suite, tc_id)
|
|
330
|
+
entry = binding_entry(root, feature, tc_id, required=True)
|
|
331
|
+
assert entry is not None
|
|
332
|
+
source = data.get("automation_mode")
|
|
333
|
+
target = entry.get("mode")
|
|
334
|
+
if source != "automated" or target != "manual":
|
|
335
|
+
raise PinError(f"mode_override only approves automated -> manual; got {source} -> {target}")
|
|
336
|
+
pins = {
|
|
337
|
+
"suite_fingerprint": pins_suite["suite_fingerprint"],
|
|
338
|
+
"tc_sha256": tc_sha256(data),
|
|
339
|
+
"binding_entry_sha256": jcs_sha256(entry),
|
|
340
|
+
"from": source,
|
|
341
|
+
"to": target,
|
|
342
|
+
}
|
|
343
|
+
display = {**tc_display(tc_id, data), "from": source, "to": target, "binding_entry": entry}
|
|
344
|
+
return pins, display
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def compute_human_result(root: Path, feature: str, tc_id: str, payload: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
348
|
+
_require_tc(tc_id)
|
|
349
|
+
suite = load_suite(root, feature)
|
|
350
|
+
pins_suite = suite_pins(suite)
|
|
351
|
+
data = frozen_case(suite, tc_id)
|
|
352
|
+
entry = binding_entry(root, feature, tc_id, required=False)
|
|
353
|
+
mode = entry.get("mode") if entry is not None else data.get("automation_mode")
|
|
354
|
+
if mode != "manual":
|
|
355
|
+
raise PinError(f"human_result applies only to manual TCs: {tc_id} is {mode}")
|
|
356
|
+
raw_files = payload.get("evidence_files", [])
|
|
357
|
+
if not isinstance(raw_files, list):
|
|
358
|
+
raise PinError("evidence_files must be an array of project-relative paths")
|
|
359
|
+
if len(raw_files) > MAX_EVIDENCE_FILES:
|
|
360
|
+
raise PinError(f"at most {MAX_EVIDENCE_FILES} evidence files are allowed")
|
|
361
|
+
evidence: dict[str, dict[str, Any]] = {}
|
|
362
|
+
for raw in raw_files:
|
|
363
|
+
path, relative = _contained_file(root, raw, root, "evidence file", MAX_EVIDENCE_FILE_BYTES)
|
|
364
|
+
if relative.startswith(".devflow/planning/test-runs/") and relative.endswith("human-acceptance-results.json"):
|
|
365
|
+
raise PinError("the human acceptance draft cannot be its own evidence")
|
|
366
|
+
content = path.read_bytes()
|
|
367
|
+
evidence[relative] = {"path": relative, "sha256": hashlib.sha256(content).hexdigest(), "bytes": len(content)}
|
|
368
|
+
ordered = [evidence[key] for key in sorted(evidence)]
|
|
369
|
+
pins = {
|
|
370
|
+
"suite_fingerprint": pins_suite["suite_fingerprint"],
|
|
371
|
+
"tc_sha256": tc_sha256(data),
|
|
372
|
+
"evidence": [{"path": item["path"], "sha256": item["sha256"]} for item in ordered],
|
|
373
|
+
}
|
|
374
|
+
display = {**tc_display(tc_id, data), "evidence": ordered}
|
|
375
|
+
return pins, display
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
COMPUTERS = {
|
|
379
|
+
"execution_plan": compute_execution_plan,
|
|
380
|
+
"environment_profile": compute_environment_profile,
|
|
381
|
+
"mode_override": compute_mode_override,
|
|
382
|
+
"human_result": compute_human_result,
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def compute(root: Path, feature: str, kind: str, subject_key: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
387
|
+
"""Return {ok, kind, feature, subject_key, state_revision, pins, display, errors}."""
|
|
388
|
+
result: dict[str, Any] = {"ok": False, "kind": kind, "feature": feature, "subject_key": subject_key, "errors": []}
|
|
389
|
+
try:
|
|
390
|
+
if kind not in COMPUTERS:
|
|
391
|
+
raise PinError(f"unknown approval kind: {kind}")
|
|
392
|
+
if not isinstance(feature, str) or not FEATURE_RE.fullmatch(feature):
|
|
393
|
+
raise PinError("feature must be a plain feature name")
|
|
394
|
+
if payload is not None and not isinstance(payload, dict):
|
|
395
|
+
raise PinError("payload must be an object")
|
|
396
|
+
state = workflow_state(root)
|
|
397
|
+
if state["current_feature"] != feature:
|
|
398
|
+
raise PinError(f"feature is not active: requested={feature}, current={state['current_feature'] or 'none'}")
|
|
399
|
+
pins, display = COMPUTERS[kind](root, feature, subject_key, payload or {})
|
|
400
|
+
except PinError as error:
|
|
401
|
+
result["errors"].append(str(error))
|
|
402
|
+
return result
|
|
403
|
+
result.update({"ok": True, "state_revision": state["revision"], "pins": pins, "display": display})
|
|
404
|
+
return result
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
408
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
409
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
410
|
+
compute_parser = sub.add_parser("compute", help="compute current pins for one approval subject (read-only)")
|
|
411
|
+
compute_parser.add_argument("--root", default=".")
|
|
412
|
+
compute_parser.add_argument("--feature", required=True)
|
|
413
|
+
compute_parser.add_argument("--kind", required=True, choices=KINDS)
|
|
414
|
+
compute_parser.add_argument("--subject", required=True)
|
|
415
|
+
compute_parser.add_argument("--payload-json", default="{}")
|
|
416
|
+
compute_parser.add_argument("--json", action="store_true")
|
|
417
|
+
return parser.parse_args(argv)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def main(argv: list[str] | None = None) -> int:
|
|
421
|
+
args = parse_args(argv)
|
|
422
|
+
try:
|
|
423
|
+
payload = json.loads(args.payload_json)
|
|
424
|
+
except json.JSONDecodeError as error:
|
|
425
|
+
print(json.dumps({"ok": False, "errors": [f"invalid --payload-json: {error.msg}"]}, ensure_ascii=False))
|
|
426
|
+
return 1
|
|
427
|
+
result = compute(Path(args.root).resolve(), args.feature, args.kind, args.subject, payload)
|
|
428
|
+
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
|
|
429
|
+
return 0 if result["ok"] else 1
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
if __name__ == "__main__":
|
|
433
|
+
raise SystemExit(main())
|