ai-delivery-workflow 0.2.2 → 0.4.0
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 +45 -14
- package/bin/ai-delivery.mjs +181 -6
- package/bin/benchmark-autonomy.mjs +217 -0
- package/bin/release-check.mjs +382 -0
- package/docs/CLI-PARAMETER-REFERENCE.zh-CN.md +346 -0
- package/docs/CODEGRAPH-INTEGRATION.zh-CN.md +89 -0
- package/docs/DUAL-REPOSITORY-WORKSPACE.zh-CN.md +2 -2
- package/docs/EVOLUTION-MAINTENANCE.zh-CN.md +7 -7
- package/docs/EVOLUTION-USER-GUIDE.zh-CN.md +2 -2
- package/docs/FILE-REFERENCE.zh-CN.md +120 -39
- package/docs/MAINTENANCE-VERIFICATION.zh-CN.md +51 -0
- package/docs/MOCK-SYSTEM.zh-CN.md +141 -0
- package/docs/NPM-RELEASE-MANAGEMENT.zh-CN.md +106 -0
- package/docs/PROJECT-MANUAL.zh-CN.md +154 -95
- package/docs/PROTOTYPE-MIGRATION.zh-CN.md +164 -0
- package/docs/STATE-CLI-MAINTENANCE.zh-CN.md +35 -17
- package/docs/STATE-CLI-USER-GUIDE.zh-CN.md +115 -39
- package/docs/VIEWER-MAINTENANCE.zh-CN.md +101 -324
- package/docs/VIEWER-USER-GUIDE.zh-CN.md +68 -190
- package/docs/adr/0001-prototype-first-ui-governance.md +3 -0
- package/docs/adr/0002-microcks-as-default-mock-system.md +3 -0
- package/docs/adr/0003-workflow-manager-governed-control-surface.md +46 -0
- package/docs/adr/0004-operational-carbon-visual-standard.md +9 -0
- package/docs/adr/0005-prototype-management-as-routed-pages.md +7 -0
- package/docs/agents/domain.md +12 -0
- package/docs/agents/issue-tracker.md +27 -0
- package/docs/agents/triage-labels.md +11 -0
- package/docs/agents/workflow-manager-acceptance-matrix.md +93 -0
- package/docs/agents/workflow-manager-agent-interface.md +145 -0
- package/docs/agents/workflow-manager-current-state.md +205 -0
- package/lib/artifact-files.mjs +47 -0
- package/lib/codegraph.mjs +405 -0
- package/lib/delivery-state.mjs +3079 -198
- package/lib/frontend-guard.mjs +288 -0
- package/lib/fs-utils.mjs +53 -0
- package/lib/history-anchor.mjs +98 -0
- package/lib/mock-scenario-pack.mjs +687 -0
- package/lib/mock-system.mjs +897 -0
- package/lib/npm-package-result.mjs +11 -0
- package/lib/png-evidence.mjs +149 -0
- package/lib/project-bootstrap.mjs +96 -55
- package/lib/project-installer.mjs +1484 -164
- package/lib/project-migration.mjs +1193 -0
- package/lib/prototype-migration.mjs +713 -0
- package/lib/prototype-page-review.mjs +384 -0
- package/lib/prototype-review-target.mjs +334 -0
- package/lib/prototype-revision.mjs +812 -0
- package/lib/stable-value.mjs +15 -0
- package/lib/toml-hooks.mjs +69 -0
- package/lib/ui-acceptance.mjs +565 -0
- package/lib/visual-standard.mjs +232 -0
- package/lib/workflow-manager-contracts.mjs +367 -0
- package/lib/workflow-manager-facts.mjs +778 -0
- package/lib/workflow-manager-governance.mjs +514 -0
- package/lib/workflow-manager-mock.mjs +183 -0
- package/lib/workflow-manager-prototypes.mjs +1847 -0
- package/lib/workflow-manager-public/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/lib/workflow-manager-public/assets/index-Chlg6r8f.css +1 -0
- package/lib/workflow-manager-public/assets/index-E-hGB5ZD.js +13 -0
- package/lib/workflow-manager-public/fonts/OFL-1.1.txt +90 -0
- package/lib/workflow-manager-public/index.html +14 -0
- package/lib/workflow-manager-systems.mjs +519 -0
- package/lib/workflow-manager-transaction.mjs +397 -0
- package/lib/workflow-manager.mjs +745 -0
- package/package.json +8 -2
- package/skills/ai-delivery-assemble-release/agents/openai.yaml +3 -3
- package/skills/ai-delivery-assemble-release/assets/release-material-index.csv +1 -1
- package/skills/ai-delivery-assemble-release/assets/version-inclusion.csv +1 -1
- package/skills/ai-delivery-bootstrap/SKILL.md +4 -4
- package/skills/ai-delivery-bootstrap/agents/openai.yaml +3 -3
- package/skills/ai-delivery-bootstrap/references/bootstrap-contract.md +4 -2
- package/skills/ai-delivery-checkpoint-task/SKILL.md +17 -3
- package/skills/ai-delivery-checkpoint-task/agents/openai.yaml +3 -3
- package/skills/ai-delivery-checkpoint-task/assets/runtime-template/task.json +7 -1
- package/skills/ai-delivery-checkpoint-task/references/checkpoint-contract.md +22 -8
- package/skills/ai-delivery-checkpoint-task/scripts/hook-event.mjs +91 -0
- package/skills/ai-delivery-checkpoint-task/scripts/task-state.mjs +1086 -51
- package/skills/ai-delivery-close-version/SKILL.md +9 -8
- package/skills/ai-delivery-close-version/agents/openai.yaml +3 -3
- package/skills/ai-delivery-close-version/assets/version-lineage.yaml +1 -1
- package/skills/ai-delivery-close-version/references/version-closeout-contract.md +2 -2
- package/skills/ai-delivery-define-product/SKILL.md +8 -4
- package/skills/ai-delivery-define-product/agents/openai.yaml +4 -4
- package/skills/ai-delivery-define-product/references/product-contract.md +2 -2
- package/skills/ai-delivery-deploy-production/agents/openai.yaml +3 -3
- package/skills/ai-delivery-design-architecture/SKILL.md +9 -5
- package/skills/ai-delivery-design-architecture/assets/architecture-template/prototype-architecture-validation.yaml +16 -16
- package/skills/ai-delivery-design-architecture/references/architecture-contract.md +1 -1
- package/skills/ai-delivery-design-experience/SKILL.md +36 -30
- package/skills/ai-delivery-design-experience/agents/openai.yaml +4 -4
- package/skills/ai-delivery-design-experience/assets/experience-template/LEGACY-COMPATIBILITY.md +16 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/design-reference-selection.yaml +32 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/experience-baseline.yaml +2 -2
- package/skills/ai-delivery-design-experience/assets/experience-template/formal-ui-confirmation.yaml +16 -3
- package/skills/ai-delivery-design-experience/assets/experience-template/frontend-change-scope.yaml +30 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/interaction-contract.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/mock-admin-export.yaml +8 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/mock-scenario-mappings.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/mock-scenario-pack-request.yaml +24 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/product-prototype-reconciliation.yaml +2 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-adjustment-log.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-component-bindings.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-component-candidates.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-components.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-confirmation.yaml +18 -18
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-data-contracts.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-impact-assessment.yaml +5 -5
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-implementation-bindings.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-manifest.yaml +2 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-pages.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-product-coverage.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-comments.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-decision.yaml +13 -12
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-scenes.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-sessions.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-unit-pages.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-units.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-revision.yaml +88 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-routes.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-set.yaml +2 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-source-inspection-snapshots.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-terminals.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-tool-candidates.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-tool-selection.yaml +2 -3
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-traceability.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-trigger-conditions.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype.yaml +6 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/ui-framework-selection.yaml +17 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/ui-handoff.yaml +6 -5
- package/skills/ai-delivery-design-experience/assets/experience-template/ui-mapping.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/visual-direction.yaml +4 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/visual-reference-manifest.yaml +23 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/visual-reference.html +163 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/visual-system-confirmation.yaml +11 -0
- package/skills/ai-delivery-design-experience/references/experience-contract.md +26 -27
- package/skills/ai-delivery-design-experience/references/prototype-management-contract.md +42 -21
- package/skills/ai-delivery-design-tests/SKILL.md +13 -7
- package/skills/ai-delivery-design-tests/assets/test-design-template/prototype-test-scope.csv +1 -1
- package/skills/ai-delivery-design-tests/assets/test-design-template/prototype-test-validation.yaml +17 -16
- package/skills/ai-delivery-design-tests/references/test-design-contract.md +7 -5
- package/skills/ai-delivery-develop-iteration/SKILL.md +18 -11
- package/skills/ai-delivery-develop-iteration/agents/openai.yaml +3 -3
- package/skills/ai-delivery-develop-iteration/assets/development-template/candidate-manifest.yaml +36 -0
- package/skills/ai-delivery-develop-iteration/assets/development-template/task-prototype-traceability.csv +1 -1
- package/skills/ai-delivery-develop-iteration/references/development-contract.md +5 -3
- package/skills/ai-delivery-evolve-workflow/agents/openai.yaml +3 -3
- package/skills/ai-delivery-execute-test/SKILL.md +6 -2
- package/skills/ai-delivery-execute-test/agents/openai.yaml +4 -4
- package/skills/ai-delivery-execute-work-package/SKILL.md +10 -3
- package/skills/ai-delivery-execute-work-package/agents/openai.yaml +4 -4
- package/skills/ai-delivery-execute-work-package/references/work-package-contract.md +1 -1
- package/skills/ai-delivery-manage-git/agents/openai.yaml +3 -3
- package/skills/ai-delivery-manage-standards/agents/openai.yaml +4 -4
- package/skills/ai-delivery-orchestrate/SKILL.md +47 -29
- package/skills/ai-delivery-orchestrate/agents/openai.yaml +3 -3
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/control/migrations/.gitkeep +0 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/control/repairs/.gitkeep +0 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/project.yaml +1 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/workflow-state.yaml +2 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/frontend-guard/frontend-guard.mjs +50 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/hooks/hook-event.mjs +91 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/manager/manager.mjs +64 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/manager/start.cmd +4 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/manager/start.ps1 +2 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/{viewer → manager}/start.sh +1 -1
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/mock/mock.mjs +50 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/ui-acceptance/ui-acceptance.mjs +37 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/AGENTS.md +32 -6
- package/skills/ai-delivery-orchestrate/assets/project-template/gitignore.template +1 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/CONTRIBUTING.md +25 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/LICENSE +21 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/README.md +250 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/UPSTREAM.yaml +19 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/airbnb/DESIGN.md +545 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/airbnb/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/airtable/DESIGN.md +554 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/airtable/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/apple/DESIGN.md +562 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/apple/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/binance/DESIGN.md +634 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/binance/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bmw/DESIGN.md +544 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bmw/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bmw-m/DESIGN.md +503 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bmw-m/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bugatti/DESIGN.md +454 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bugatti/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cal/DESIGN.md +542 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cal/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/claude/DESIGN.md +589 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/claude/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/clay/DESIGN.md +541 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/clay/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/clickhouse/DESIGN.md +544 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/clickhouse/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cohere/DESIGN.md +451 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cohere/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/coinbase/DESIGN.md +570 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/coinbase/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/composio/DESIGN.md +506 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/composio/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cursor/DESIGN.md +537 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cursor/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/dell-1996/DESIGN.md +632 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/dell-1996/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/elevenlabs/DESIGN.md +504 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/elevenlabs/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/expo/DESIGN.md +526 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/expo/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ferrari/DESIGN.md +531 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ferrari/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/figma/DESIGN.md +578 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/figma/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/framer/DESIGN.md +544 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/framer/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/hashicorp/DESIGN.md +575 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/hashicorp/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/hp/DESIGN.md +670 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/hp/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ibm/DESIGN.md +550 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ibm/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/intercom/DESIGN.md +546 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/intercom/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/kraken/DESIGN.md +125 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/kraken/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/lamborghini/DESIGN.md +288 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/lamborghini/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/linear.app/DESIGN.md +548 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/linear.app/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/lovable/DESIGN.md +298 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/lovable/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mastercard/DESIGN.md +365 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mastercard/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/meta/DESIGN.md +683 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/meta/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/minimax/DESIGN.md +746 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/minimax/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mintlify/DESIGN.md +852 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mintlify/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/miro/DESIGN.md +825 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/miro/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mistral.ai/DESIGN.md +773 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mistral.ai/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mongodb/DESIGN.md +767 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mongodb/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nike/DESIGN.md +575 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nike/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nintendo-2001/DESIGN.md +649 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nintendo-2001/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/notion/DESIGN.md +821 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/notion/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nvidia/DESIGN.md +640 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nvidia/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ollama/DESIGN.md +539 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ollama/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/opencode.ai/DESIGN.md +521 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/opencode.ai/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/pinterest/DESIGN.md +597 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/pinterest/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/playstation/DESIGN.md +661 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/playstation/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/posthog/DESIGN.md +690 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/posthog/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/raycast/DESIGN.md +669 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/raycast/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/renault/DESIGN.md +589 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/renault/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/replicate/DESIGN.md +616 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/replicate/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/resend/DESIGN.md +585 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/resend/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/revolut/DESIGN.md +636 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/revolut/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/runwayml/DESIGN.md +244 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/runwayml/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/sanity/DESIGN.md +357 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/sanity/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/sentry/DESIGN.md +551 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/sentry/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/shopify/DESIGN.md +516 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/shopify/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/slack/DESIGN.md +482 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/spacex/DESIGN.md +363 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/spacex/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/spotify/DESIGN.md +246 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/spotify/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/starbucks/DESIGN.md +580 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/starbucks/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/stripe/DESIGN.md +487 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/stripe/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/supabase/DESIGN.md +462 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/supabase/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/superhuman/DESIGN.md +448 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/superhuman/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/tesla/DESIGN.md +286 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/tesla/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/theverge/DESIGN.md +339 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/theverge/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/together.ai/DESIGN.md +633 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/together.ai/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/uber/DESIGN.md +636 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/uber/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/vercel/DESIGN.md +736 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/vercel/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/vodafone/DESIGN.md +538 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/vodafone/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/voltagent/DESIGN.md +521 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/voltagent/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/warp/DESIGN.md +526 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/warp/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/webflow/DESIGN.md +588 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/webflow/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/wired/DESIGN.md +497 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/wired/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/wise/DESIGN.md +544 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/wise/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/x.ai/DESIGN.md +465 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/x.ai/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/zapier/DESIGN.md +537 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/zapier/README.md +5 -0
- package/skills/ai-delivery-orchestrate/references/formal-state-contract.md +65 -20
- package/skills/ai-delivery-orchestrate/references/workflow-model.md +7 -5
- package/skills/ai-delivery-orchestrate-release/SKILL.md +2 -2
- package/skills/ai-delivery-orchestrate-release/agents/openai.yaml +4 -4
- package/skills/ai-delivery-orchestrate-release/references/release-workflow-contract.md +1 -1
- package/skills/ai-delivery-plan-iteration/SKILL.md +11 -4
- package/skills/ai-delivery-plan-iteration/agents/openai.yaml +4 -4
- package/skills/ai-delivery-plan-iteration/assets/planning-template/scope-manifest.yaml +17 -0
- package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-prototype-traceability.csv +1 -1
- package/skills/ai-delivery-plan-iteration/references/planning-contract.md +8 -2
- package/skills/ai-delivery-prepare-platform/SKILL.md +5 -1
- package/skills/ai-delivery-prepare-platform/agents/openai.yaml +4 -4
- package/skills/ai-delivery-prepare-release/agents/openai.yaml +4 -4
- package/skills/ai-delivery-review-change/SKILL.md +14 -4
- package/skills/ai-delivery-review-change/agents/openai.yaml +4 -4
- package/skills/ai-delivery-review-change/references/review-contract.md +6 -0
- package/skills/ai-delivery-triage-failure/SKILL.md +5 -1
- package/skills/ai-delivery-triage-failure/agents/openai.yaml +4 -4
- package/skills/ai-delivery-validate-artifacts/SKILL.md +6 -3
- package/skills/ai-delivery-validate-artifacts/agents/openai.yaml +4 -4
- package/skills/ai-delivery-validate-artifacts/references/artifact-contract.md +13 -13
- package/skills/ai-delivery-verify-candidate/SKILL.md +13 -6
- package/skills/ai-delivery-verify-candidate/agents/openai.yaml +3 -3
- package/skills/ai-delivery-verify-candidate/assets/verification-template/ui-acceptance-request.yaml +19 -0
- package/skills/ai-delivery-verify-candidate/assets/verification-template/ui-acceptance-results.yaml +53 -0
- package/skills/ai-delivery-verify-candidate/references/candidate-verification-contract.md +10 -2
- package/skills/ai-delivery-verify-production/agents/openai.yaml +3 -3
- package/skills/ai-delivery-design-experience/assets/experience-template/low-fidelity-confirmation.yaml +0 -32
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/app.js +0 -546
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/index.html +0 -199
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/styles.css +0 -529
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/server.mjs +0 -780
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.cmd +0 -4
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.ps1 +0 -2
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export function stableValue(value) {
|
|
4
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
5
|
+
if (!value || typeof value !== "object") return value;
|
|
6
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stableValue(value[key])]));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function sameStableValue(left, right) {
|
|
10
|
+
return JSON.stringify(stableValue(left)) === JSON.stringify(stableValue(right));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function stableValueChecksum(value) {
|
|
14
|
+
return crypto.createHash("sha256").update(JSON.stringify(stableValue(value))).digest("hex");
|
|
15
|
+
}
|
package/lib/toml-hooks.mjs
CHANGED
|
@@ -49,6 +49,55 @@ function replaceFeatureHooksFalse(text) {
|
|
|
49
49
|
return `${text.slice(0, sectionStart)}${section.replace(assignment, "$1true$2")}${text.slice(sectionEnd)}`;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function removeDeliveryHandlerBlocks(text) {
|
|
53
|
+
const newline = text.includes("\r\n") ? "\r\n" : "\n";
|
|
54
|
+
const lines = text.split(/\r?\n/);
|
|
55
|
+
const groups = [];
|
|
56
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
57
|
+
if (!/^\s*\[\[hooks\.[^.\]]+\]\]\s*$/.test(lines[index])) continue;
|
|
58
|
+
const end = lines.findIndex((line, candidate) =>
|
|
59
|
+
candidate > index && /^\s*\[\[hooks\.[^.\]]+\]\]\s*$/.test(line));
|
|
60
|
+
groups.push({ start: index, end: end === -1 ? lines.length : end });
|
|
61
|
+
index = (end === -1 ? lines.length : end) - 1;
|
|
62
|
+
}
|
|
63
|
+
let changed = false;
|
|
64
|
+
const output = [];
|
|
65
|
+
let cursor = 0;
|
|
66
|
+
for (const group of groups) {
|
|
67
|
+
output.push(...lines.slice(cursor, group.start));
|
|
68
|
+
const groupLines = lines.slice(group.start, group.end);
|
|
69
|
+
const remove = new Set();
|
|
70
|
+
for (let index = 0; index < groupLines.length; index += 1) {
|
|
71
|
+
if (!/^\s*\[\[hooks\.[^.\]]+\.hooks\]\]\s*$/.test(groupLines[index])) continue;
|
|
72
|
+
let end = index + 1;
|
|
73
|
+
while (end < groupLines.length && !/^\s*\[\[/.test(groupLines[end])) end += 1;
|
|
74
|
+
const handlerLines = groupLines.slice(index, end);
|
|
75
|
+
if (handlerLines.some((line) =>
|
|
76
|
+
/^\s*command(?:Windows)?\s*=/.test(line) && line.includes(`"${HOOK_COMMAND}"`))) {
|
|
77
|
+
for (let candidate = index; candidate < end; candidate += 1) {
|
|
78
|
+
if (candidate === index
|
|
79
|
+
|| (groupLines[candidate].trim() && !/^\s*#/.test(groupLines[candidate]))) {
|
|
80
|
+
remove.add(candidate);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
changed = true;
|
|
84
|
+
}
|
|
85
|
+
index = end - 1;
|
|
86
|
+
}
|
|
87
|
+
const retained = groupLines.filter((_, index) => !remove.has(index));
|
|
88
|
+
const onlyEmptyManagedMatcher = retained.every((line) =>
|
|
89
|
+
!line.trim()
|
|
90
|
+
|| /^\s*#/.test(line)
|
|
91
|
+
|| /^\s*\[\[hooks\.[^.\]]+\]\]\s*$/.test(line)
|
|
92
|
+
|| /^\s*matcher\s*=\s*""\s*$/.test(line));
|
|
93
|
+
const hasUserComment = retained.some((line) => /^\s*#/.test(line));
|
|
94
|
+
if (!onlyEmptyManagedMatcher || hasUserComment || !remove.size) output.push(...retained);
|
|
95
|
+
cursor = group.end;
|
|
96
|
+
}
|
|
97
|
+
output.push(...lines.slice(cursor));
|
|
98
|
+
return { text: output.join(newline), changed };
|
|
99
|
+
}
|
|
100
|
+
|
|
52
101
|
export function mergeHookConfig(existing, { force = false } = {}) {
|
|
53
102
|
const newline = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
54
103
|
let text = existing.trimEnd();
|
|
@@ -57,6 +106,11 @@ export function mergeHookConfig(existing, { force = false } = {}) {
|
|
|
57
106
|
text = text.replaceAll(LEGACY_HOOK_COMMAND, HOOK_COMMAND);
|
|
58
107
|
actions.push("migrate AI delivery Hook command into .workflow/tools");
|
|
59
108
|
}
|
|
109
|
+
if (force) {
|
|
110
|
+
const refreshed = removeDeliveryHandlerBlocks(text);
|
|
111
|
+
text = refreshed.text.trimEnd();
|
|
112
|
+
if (refreshed.changed) actions.push("refresh AI delivery Hook handlers");
|
|
113
|
+
}
|
|
60
114
|
let config = text ? parse(text) : {};
|
|
61
115
|
|
|
62
116
|
if (config.features?.hooks === false && !force) {
|
|
@@ -95,3 +149,18 @@ export function inspectHookConfig(text) {
|
|
|
95
149
|
registeredEvents: HOOK_EVENTS.filter((event) => hasDeliveryHandler(config, event)),
|
|
96
150
|
};
|
|
97
151
|
}
|
|
152
|
+
|
|
153
|
+
export function managedHookConfigProjection(text) {
|
|
154
|
+
const config = parse(text);
|
|
155
|
+
return {
|
|
156
|
+
hooks_enabled: config.features?.hooks ?? null,
|
|
157
|
+
events: HOOK_EVENTS.map((event) => ({
|
|
158
|
+
event,
|
|
159
|
+
handlers: (config.hooks?.[event] || []).flatMap((matcher) =>
|
|
160
|
+
(matcher.hooks || [])
|
|
161
|
+
.filter((handler) => handler?.command === HOOK_COMMAND
|
|
162
|
+
|| handler?.commandWindows === HOOK_COMMAND)
|
|
163
|
+
.map((handler) => ({ matcher: matcher.matcher ?? null, handler }))),
|
|
164
|
+
})),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
readYamlMapping,
|
|
5
|
+
requireSha256,
|
|
6
|
+
resolveContainedPath,
|
|
7
|
+
sha256File,
|
|
8
|
+
} from "./artifact-files.mjs";
|
|
9
|
+
import { parsePrototypeCatalogCsv } from "./prototype-revision.mjs";
|
|
10
|
+
import { buildPrototypeReviewTarget } from "./prototype-review-target.mjs";
|
|
11
|
+
import { validateFrozenMockScenarioPack } from "./mock-scenario-pack.mjs";
|
|
12
|
+
import { mockProtocolExpectation, validateMockDefinition } from "./mock-system.mjs";
|
|
13
|
+
import { verifyVisualPngEvidence } from "./png-evidence.mjs";
|
|
14
|
+
import { sameStableValue, stableValueChecksum } from "./stable-value.mjs";
|
|
15
|
+
|
|
16
|
+
const candidateAssuranceNode = "05-candidate-assurance";
|
|
17
|
+
const defaultViewports = [
|
|
18
|
+
{ viewport_id: "mobile", width: 390, height: 844 },
|
|
19
|
+
{ viewport_id: "tablet", width: 768, height: 1024 },
|
|
20
|
+
{ viewport_id: "desktop", width: 1440, height: 900 },
|
|
21
|
+
];
|
|
22
|
+
const traceabilityHeader = [
|
|
23
|
+
"task_id", "prototype_revision_id", "source_revision", "terminal_id", "page_id",
|
|
24
|
+
"review_scene_id", "prototype_element_id", "ui_mapping_id", "ui_component_id",
|
|
25
|
+
"frontend_source_ref", "acceptance_id",
|
|
26
|
+
].join(",");
|
|
27
|
+
|
|
28
|
+
function boundFile(root, binding, label) {
|
|
29
|
+
if (binding?.algorithm !== "sha256") throw new Error(`${label} must use sha256.`);
|
|
30
|
+
const file = resolveContainedPath(root, binding.ref, label);
|
|
31
|
+
const checksum = requireSha256(binding.checksum, label);
|
|
32
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile() || sha256File(file) !== checksum) {
|
|
33
|
+
throw new Error(`${label} checksum mismatch.`);
|
|
34
|
+
}
|
|
35
|
+
return { file, checksum };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function registeredArtifact(projectRoot, registry, artifactId, type, checksum, status) {
|
|
39
|
+
const artifact = (registry.artifacts || []).find((item) =>
|
|
40
|
+
(item.artifact_id || item.id) === artifactId);
|
|
41
|
+
if (!artifact || artifact.artifact_type !== type || artifact.status !== status
|
|
42
|
+
|| artifact.checksum_algorithm !== "sha256" || artifact.checksum !== checksum) {
|
|
43
|
+
throw new Error(`${type} ${artifactId} is not registered as ${status} with the exact checksum.`);
|
|
44
|
+
}
|
|
45
|
+
const file = resolveContainedPath(projectRoot, artifact.path, `${type} registry path`);
|
|
46
|
+
if (!fs.existsSync(file) || sha256File(file) !== checksum) {
|
|
47
|
+
throw new Error(`${type} ${artifactId} registry path checksum mismatch.`);
|
|
48
|
+
}
|
|
49
|
+
return { artifact, file };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function candidateBinding(projectRoot, request) {
|
|
53
|
+
const state = readYamlMapping(
|
|
54
|
+
path.join(projectRoot, ".workflow", "delivery", "workflow-state.yaml"),
|
|
55
|
+
"Workflow state",
|
|
56
|
+
);
|
|
57
|
+
const scope = state.schema_version >= 2 ? state.development_state : state;
|
|
58
|
+
const attempt = Number(scope?.node_attempts?.[candidateAssuranceNode] || 0);
|
|
59
|
+
const binding = scope?.node_candidate_bindings?.[candidateAssuranceNode];
|
|
60
|
+
if (scope?.scope_id !== request.iteration_id
|
|
61
|
+
|| !scope?.active_nodes?.includes(candidateAssuranceNode)
|
|
62
|
+
|| attempt !== Number(request.node_attempt)
|
|
63
|
+
|| Number(binding?.node_attempt) !== attempt
|
|
64
|
+
|| binding?.candidate_id !== request.candidate_id) {
|
|
65
|
+
throw new Error("UI Acceptance request does not bind the active Candidate Assurance attempt.");
|
|
66
|
+
}
|
|
67
|
+
const registry = readYamlMapping(
|
|
68
|
+
path.join(projectRoot, ".workflow", "delivery", "artifact-registry.yaml"),
|
|
69
|
+
"Artifact registry",
|
|
70
|
+
);
|
|
71
|
+
const candidateEntry = (registry.artifacts || []).find((item) =>
|
|
72
|
+
(item.artifact_id || item.id) === request.candidate_id);
|
|
73
|
+
if (!candidateEntry || candidateEntry.status !== "immutable"
|
|
74
|
+
|| candidateEntry.artifact_type !== "candidate-manifest"
|
|
75
|
+
|| candidateEntry.checksum_algorithm !== "sha256") {
|
|
76
|
+
throw new Error(`Candidate ${request.candidate_id} is not registered as immutable.`);
|
|
77
|
+
}
|
|
78
|
+
const candidateFile = resolveContainedPath(
|
|
79
|
+
projectRoot,
|
|
80
|
+
candidateEntry.path,
|
|
81
|
+
"Candidate manifest registry path",
|
|
82
|
+
);
|
|
83
|
+
const candidateChecksum = requireSha256(candidateEntry.checksum, "Candidate manifest");
|
|
84
|
+
if (!fs.existsSync(candidateFile) || sha256File(candidateFile) !== candidateChecksum) {
|
|
85
|
+
throw new Error("Immutable candidate manifest checksum mismatch.");
|
|
86
|
+
}
|
|
87
|
+
const candidate = readYamlMapping(candidateFile, "Candidate manifest");
|
|
88
|
+
if (candidate.candidate_id !== request.candidate_id
|
|
89
|
+
|| candidate.iteration_id !== request.iteration_id
|
|
90
|
+
|| candidate.status !== "immutable") {
|
|
91
|
+
throw new Error("Candidate manifest identity or immutable status is invalid.");
|
|
92
|
+
}
|
|
93
|
+
return { state, scope, registry, candidate, candidateFile, candidateChecksum, attempt };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readTraceability(candidateBindingValue, target, revision) {
|
|
97
|
+
const { candidate, candidateFile } = candidateBindingValue;
|
|
98
|
+
const file = resolveContainedPath(
|
|
99
|
+
path.dirname(candidateFile),
|
|
100
|
+
candidate.task_prototype_traceability_ref,
|
|
101
|
+
"Candidate Prototype traceability",
|
|
102
|
+
);
|
|
103
|
+
const checksum = requireSha256(
|
|
104
|
+
candidate.task_prototype_traceability_checksum,
|
|
105
|
+
"Candidate Prototype traceability",
|
|
106
|
+
);
|
|
107
|
+
if (!fs.existsSync(file) || sha256File(file) !== checksum) {
|
|
108
|
+
throw new Error("Candidate Prototype traceability checksum mismatch.");
|
|
109
|
+
}
|
|
110
|
+
const parsed = parsePrototypeCatalogCsv(
|
|
111
|
+
fs.readFileSync(file, "utf8"),
|
|
112
|
+
"Candidate Prototype traceability",
|
|
113
|
+
);
|
|
114
|
+
if (parsed.header !== traceabilityHeader || !parsed.rows.length) {
|
|
115
|
+
throw new Error("Candidate Prototype traceability is empty or has an invalid header.");
|
|
116
|
+
}
|
|
117
|
+
const mappingFile = resolveContainedPath(
|
|
118
|
+
target.contentRoot,
|
|
119
|
+
revision.catalogs.ui_mapping.ref,
|
|
120
|
+
"Prototype UI Mapping catalog",
|
|
121
|
+
);
|
|
122
|
+
if (sha256File(mappingFile) !== requireSha256(
|
|
123
|
+
revision.catalogs.ui_mapping.checksum,
|
|
124
|
+
"Prototype UI Mapping catalog",
|
|
125
|
+
) || candidate.ui_mapping_checksum !== revision.catalogs.ui_mapping.checksum) {
|
|
126
|
+
throw new Error("Candidate UI Mapping checksum mismatch.");
|
|
127
|
+
}
|
|
128
|
+
const mappings = parsePrototypeCatalogCsv(
|
|
129
|
+
fs.readFileSync(mappingFile, "utf8"),
|
|
130
|
+
"Prototype UI Mapping catalog",
|
|
131
|
+
).rows.filter((item) => item.lifecycle_status === "active");
|
|
132
|
+
const mappingById = new Map(mappings.map((item) => [item.ui_mapping_id, item]));
|
|
133
|
+
const pageIds = new Set();
|
|
134
|
+
const explicitSceneIds = new Set();
|
|
135
|
+
for (const row of parsed.rows) {
|
|
136
|
+
const mapping = mappingById.get(row.ui_mapping_id);
|
|
137
|
+
if (row.prototype_revision_id !== target.targetId
|
|
138
|
+
|| row.source_revision !== revision.source.revision
|
|
139
|
+
|| !mapping
|
|
140
|
+
|| mapping.page_id !== row.page_id
|
|
141
|
+
|| mapping.review_scene_id !== row.review_scene_id) {
|
|
142
|
+
throw new Error(`Candidate Prototype traceability row is inconsistent: ${row.task_id}.`);
|
|
143
|
+
}
|
|
144
|
+
pageIds.add(row.page_id);
|
|
145
|
+
if (row.review_scene_id) explicitSceneIds.add(row.review_scene_id);
|
|
146
|
+
}
|
|
147
|
+
const scenes = target.reviewScenes.filter((scene) => pageIds.has(scene.page_id));
|
|
148
|
+
if (explicitSceneIds.size && scenes.some((scene) => !explicitSceneIds.has(scene.review_scene_id))) {
|
|
149
|
+
throw new Error("Candidate Prototype traceability does not cover every Scene of an affected Page.");
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
checksum,
|
|
153
|
+
pageIds: [...pageIds].sort(),
|
|
154
|
+
scenes: scenes.sort((left, right) => left.review_scene_id.localeCompare(right.review_scene_id)),
|
|
155
|
+
mappings,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function acceptancePolicy(experienceRoot) {
|
|
160
|
+
const confirmation = readYamlMapping(
|
|
161
|
+
path.join(experienceRoot, "visual-system-confirmation.yaml"),
|
|
162
|
+
"Visual Standard confirmation",
|
|
163
|
+
);
|
|
164
|
+
const directionBinding = boundFile(experienceRoot, {
|
|
165
|
+
ref: confirmation.visual_direction_ref,
|
|
166
|
+
algorithm: "sha256",
|
|
167
|
+
checksum: confirmation.visual_direction_checksum,
|
|
168
|
+
}, "Visual direction");
|
|
169
|
+
const direction = readYamlMapping(directionBinding.file, "Visual direction");
|
|
170
|
+
const configured = direction.responsive_breakpoints;
|
|
171
|
+
const viewports = Array.isArray(configured) && configured.length ? configured : defaultViewports;
|
|
172
|
+
const normalized = viewports.map((item) => ({
|
|
173
|
+
viewport_id: String(item?.viewport_id || ""),
|
|
174
|
+
width: Number(item?.width),
|
|
175
|
+
height: Number(item?.height),
|
|
176
|
+
}));
|
|
177
|
+
if (normalized.some((item) => !/^[a-z][a-z0-9-]*$/.test(item.viewport_id)
|
|
178
|
+
|| !Number.isInteger(item.width) || item.width <= 0
|
|
179
|
+
|| !Number.isInteger(item.height) || item.height <= 0)
|
|
180
|
+
|| new Set(normalized.map((item) => item.viewport_id)).size !== normalized.length) {
|
|
181
|
+
throw new Error("Approved Visual Standard responsive breakpoints are invalid.");
|
|
182
|
+
}
|
|
183
|
+
const maxDiff = direction.ui_acceptance?.max_diff_pixel_ratio ?? 0.001;
|
|
184
|
+
if (typeof maxDiff !== "number" || maxDiff < 0 || maxDiff > 1) {
|
|
185
|
+
throw new Error("Approved maxDiffPixelRatio is invalid.");
|
|
186
|
+
}
|
|
187
|
+
return { viewports: normalized, max_diff_pixel_ratio: maxDiff };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function pinnedRunner(runner) {
|
|
191
|
+
if (runner?.engine !== "playwright"
|
|
192
|
+
|| !/^\d+\.\d+\.\d+$/.test(String(runner.package_version || ""))
|
|
193
|
+
|| !new Set(["chromium", "firefox", "webkit"]).has(runner.browser)
|
|
194
|
+
|| !/^\d+(?:\.\d+)+$/.test(String(runner.browser_version || ""))
|
|
195
|
+
|| !String(runner.environment_image || "")
|
|
196
|
+
|| !/^sha256:[a-f0-9]{64}$/.test(String(runner.environment_digest || ""))) {
|
|
197
|
+
throw new Error("UI Acceptance requires an exact pinned Playwright environment.");
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
engine: runner.engine,
|
|
201
|
+
package_version: runner.package_version,
|
|
202
|
+
browser: runner.browser,
|
|
203
|
+
browser_version: runner.browser_version,
|
|
204
|
+
environment_image: runner.environment_image,
|
|
205
|
+
environment_digest: runner.environment_digest,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function planUiAcceptance(projectRoot, options = {}) {
|
|
210
|
+
const root = path.resolve(projectRoot);
|
|
211
|
+
const requestFile = resolveContainedPath(root, options.request, "UI Acceptance request");
|
|
212
|
+
const request = readYamlMapping(requestFile, "UI Acceptance request");
|
|
213
|
+
if (request.schema_version !== 1 || request.artifact_type !== "ui-acceptance-request"
|
|
214
|
+
|| !request.iteration_id || !request.candidate_id || !Number.isInteger(request.node_attempt)) {
|
|
215
|
+
throw new Error("UI Acceptance request identity or Candidate Assurance binding is invalid.");
|
|
216
|
+
}
|
|
217
|
+
const candidate = candidateBinding(root, request);
|
|
218
|
+
const experienceRef = String(request.experience_ref || "");
|
|
219
|
+
const experienceRoot = resolveContainedPath(root, experienceRef, "UI Acceptance experience");
|
|
220
|
+
const target = buildPrototypeReviewTarget(root, {
|
|
221
|
+
experience: experienceRef,
|
|
222
|
+
revision: request.prototype_revision_ref,
|
|
223
|
+
now: options.now,
|
|
224
|
+
});
|
|
225
|
+
if (target.mode !== "prototype-revision" || target.iterationId !== request.iteration_id) {
|
|
226
|
+
throw new Error("UI Acceptance requires the exact frozen Prototype revision.");
|
|
227
|
+
}
|
|
228
|
+
const revision = readYamlMapping(target.revisionFile, "Prototype revision");
|
|
229
|
+
if (candidate.candidate.prototype_revision_ref
|
|
230
|
+
!== `${revision.artifact_id}@${revision.version}`
|
|
231
|
+
|| candidate.candidate.prototype_revision_checksum !== target.targetChecksum
|
|
232
|
+
|| candidate.candidate.prototype_source_revision !== revision.source.revision
|
|
233
|
+
|| candidate.candidate.prototype_source_checksum !== revision.source.checksum
|
|
234
|
+
|| candidate.candidate.prototype_review_bundle_checksum !== revision.review_bundle.checksum) {
|
|
235
|
+
throw new Error("Candidate Prototype revision binding is missing or inconsistent.");
|
|
236
|
+
}
|
|
237
|
+
const currentVisualStandardFile = path.join(experienceRoot, "visual-system-confirmation.yaml");
|
|
238
|
+
if (sha256File(currentVisualStandardFile) !== requireSha256(
|
|
239
|
+
revision.visual_standard?.checksum,
|
|
240
|
+
"Frozen Prototype Visual Standard",
|
|
241
|
+
)) {
|
|
242
|
+
throw new Error("Current Visual Standard does not match the frozen Prototype revision.");
|
|
243
|
+
}
|
|
244
|
+
registeredArtifact(
|
|
245
|
+
root,
|
|
246
|
+
candidate.registry,
|
|
247
|
+
revision.artifact_id,
|
|
248
|
+
"prototype-revision",
|
|
249
|
+
target.targetChecksum,
|
|
250
|
+
"frozen",
|
|
251
|
+
);
|
|
252
|
+
const traceability = readTraceability(candidate, target, revision);
|
|
253
|
+
for (const page of target.pages.filter((item) => traceability.pageIds.includes(item.page_id))) {
|
|
254
|
+
if (page.review_status !== "approved") {
|
|
255
|
+
throw new Error(`UI Acceptance requires a current approved Page receipt: ${page.page_id}.`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const packValidation = validateFrozenMockScenarioPack(root, {
|
|
260
|
+
experience: experienceRef,
|
|
261
|
+
scenarioPack: request.mock_scenario_pack_ref,
|
|
262
|
+
}, {
|
|
263
|
+
validateMockDefinition,
|
|
264
|
+
protocolExpectation: mockProtocolExpectation,
|
|
265
|
+
});
|
|
266
|
+
const { pack } = packValidation;
|
|
267
|
+
const packChecksum = packValidation.checksum;
|
|
268
|
+
if (pack.artifact_type !== "mock-scenario-pack" || pack.status !== "frozen"
|
|
269
|
+
|| pack.iteration_id !== request.iteration_id
|
|
270
|
+
|| pack.prototype_revision?.artifact_id !== target.targetId
|
|
271
|
+
|| pack.prototype_revision?.checksum !== target.targetChecksum
|
|
272
|
+
|| pack.prototype_revision?.source_checksum !== revision.source.checksum
|
|
273
|
+
|| pack.prototype_revision?.review_bundle_checksum !== revision.review_bundle.checksum
|
|
274
|
+
|| candidate.candidate.mock_scenario_pack_ref !== `${pack.artifact_id}@${pack.version}`
|
|
275
|
+
|| candidate.candidate.mock_scenario_pack_checksum !== packChecksum) {
|
|
276
|
+
throw new Error("Candidate Mock Scenario Pack binding is missing, stale, or inconsistent.");
|
|
277
|
+
}
|
|
278
|
+
registeredArtifact(
|
|
279
|
+
root,
|
|
280
|
+
candidate.registry,
|
|
281
|
+
pack.artifact_id,
|
|
282
|
+
"mock-scenario-pack",
|
|
283
|
+
packChecksum,
|
|
284
|
+
"frozen",
|
|
285
|
+
);
|
|
286
|
+
const scenarioByScene = new Map();
|
|
287
|
+
for (const mapping of Array.isArray(pack.scenario_mappings) ? pack.scenario_mappings : []) {
|
|
288
|
+
if (!mapping.review_scene_id || !mapping.scenario_id
|
|
289
|
+
|| !new Set(["external-dependency", "product-backend"]).has(mapping.mock_kind)
|
|
290
|
+
|| scenarioByScene.has(mapping.review_scene_id)) {
|
|
291
|
+
throw new Error("Mock Scenario Pack contains an invalid or duplicate Scene mapping.");
|
|
292
|
+
}
|
|
293
|
+
scenarioByScene.set(mapping.review_scene_id, mapping);
|
|
294
|
+
}
|
|
295
|
+
if (traceability.scenes.some((scene) => !scenarioByScene.has(scene.review_scene_id))) {
|
|
296
|
+
throw new Error("Mock Scenario Pack does not cover every affected Review Scene.");
|
|
297
|
+
}
|
|
298
|
+
const strategyBinding = boundFile(root, request.test_strategy, "Approved test strategy");
|
|
299
|
+
const strategy = readYamlMapping(strategyBinding.file, "Approved test strategy");
|
|
300
|
+
if (strategy.decision !== "approved") throw new Error("Candidate test strategy is not approved.");
|
|
301
|
+
const mockKinds = new Set(traceability.scenes.map((scene) =>
|
|
302
|
+
scenarioByScene.get(scene.review_scene_id).mock_kind));
|
|
303
|
+
if (mockKinds.has("external-dependency")
|
|
304
|
+
&& strategy.external_dependency_mock_allowed_in_candidate_assurance !== true) {
|
|
305
|
+
throw new Error("External Dependency Mock is not allowed by the approved test strategy.");
|
|
306
|
+
}
|
|
307
|
+
const policy = acceptancePolicy(experienceRoot);
|
|
308
|
+
const runner = pinnedRunner(request.runner);
|
|
309
|
+
const mappingByScene = new Map(traceability.mappings
|
|
310
|
+
.filter((item) => item.target_kind === "review-scene")
|
|
311
|
+
.map((item) => [item.review_scene_id, item]));
|
|
312
|
+
const executions = [];
|
|
313
|
+
for (const scene of traceability.scenes) {
|
|
314
|
+
const scenario = scenarioByScene.get(scene.review_scene_id);
|
|
315
|
+
const mapping = mappingByScene.get(scene.review_scene_id);
|
|
316
|
+
for (const viewport of policy.viewports) {
|
|
317
|
+
executions.push({
|
|
318
|
+
execution_id: `${scene.page_id}/${scene.review_scene_id}/${viewport.viewport_id}`,
|
|
319
|
+
terminal_id: scene.terminal_id,
|
|
320
|
+
page_id: scene.page_id,
|
|
321
|
+
review_scene_id: scene.review_scene_id,
|
|
322
|
+
viewport_id: viewport.viewport_id,
|
|
323
|
+
prototype_route: scene.prototype_route,
|
|
324
|
+
production_route: scene.production_route,
|
|
325
|
+
trigger_condition_id: scene.trigger_condition_id,
|
|
326
|
+
fixture_ref: scene.trigger?.fixture_ref || null,
|
|
327
|
+
ui_mapping_id: mapping.ui_mapping_id,
|
|
328
|
+
acceptance_id: mapping.acceptance_id,
|
|
329
|
+
mock_id: scenario.mock_id,
|
|
330
|
+
mock_kind: scenario.mock_kind,
|
|
331
|
+
scenario_id: scenario.scenario_id,
|
|
332
|
+
protocol: scenario.protocol,
|
|
333
|
+
operation_or_channel: scenario.operation_or_channel,
|
|
334
|
+
protocol_expectation: packValidation.expectations.get(scene.review_scene_id),
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const plan = {
|
|
339
|
+
valid: true,
|
|
340
|
+
action: "plan",
|
|
341
|
+
iteration_id: request.iteration_id,
|
|
342
|
+
candidate: {
|
|
343
|
+
candidate_id: request.candidate_id,
|
|
344
|
+
manifest_checksum: candidate.candidateChecksum,
|
|
345
|
+
node_attempt: candidate.attempt,
|
|
346
|
+
},
|
|
347
|
+
prototype_revision: {
|
|
348
|
+
artifact_id: target.targetId,
|
|
349
|
+
checksum: target.targetChecksum,
|
|
350
|
+
source_revision: revision.source.revision,
|
|
351
|
+
review_bundle_checksum: revision.review_bundle.checksum,
|
|
352
|
+
ui_mapping_checksum: revision.catalogs.ui_mapping.checksum,
|
|
353
|
+
},
|
|
354
|
+
visual_standard: {
|
|
355
|
+
ref: revision.visual_standard.ref,
|
|
356
|
+
checksum: revision.visual_standard.checksum,
|
|
357
|
+
},
|
|
358
|
+
mock_scenario_pack: {
|
|
359
|
+
artifact_id: pack.artifact_id,
|
|
360
|
+
checksum: packChecksum,
|
|
361
|
+
},
|
|
362
|
+
test_strategy: {
|
|
363
|
+
ref: request.test_strategy.ref,
|
|
364
|
+
checksum: strategyBinding.checksum,
|
|
365
|
+
},
|
|
366
|
+
runner,
|
|
367
|
+
policy,
|
|
368
|
+
affected_page_ids: traceability.pageIds,
|
|
369
|
+
affected_review_scene_ids: traceability.scenes.map((scene) => scene.review_scene_id),
|
|
370
|
+
real_product_backend_evidence_required: mockKinds.has("product-backend"),
|
|
371
|
+
executions,
|
|
372
|
+
};
|
|
373
|
+
return { ...plan, plan_checksum: stableValueChecksum(plan) };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function requirePassingCheck(projectRoot, check, label) {
|
|
377
|
+
if (check?.status !== "passed") throw new Error(`${label} did not pass.`);
|
|
378
|
+
boundFile(projectRoot, check.evidence, `${label} evidence`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function readJsonEvidence(file, label) {
|
|
382
|
+
try {
|
|
383
|
+
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
384
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
385
|
+
throw new Error("expected an object");
|
|
386
|
+
}
|
|
387
|
+
return value;
|
|
388
|
+
} catch (error) {
|
|
389
|
+
throw new Error(`${label} is not valid JSON evidence: ${error.message}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function validateRealProductBackendEvidence(projectRoot, real, plan) {
|
|
394
|
+
const environment = real?.environment;
|
|
395
|
+
if (real?.status !== "passed" || real?.source !== "real-product-backend"
|
|
396
|
+
|| !sameStableValue(real.candidate, plan.candidate)
|
|
397
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(String(environment?.environment_id || ""))
|
|
398
|
+
|| !String(environment?.deployment_ref || "").trim()
|
|
399
|
+
|| !/^sha256:[a-f0-9]{64}$/.test(String(environment?.deployment_digest || ""))
|
|
400
|
+
|| environment?.product_backend_mock_enabled !== false) {
|
|
401
|
+
throw new Error(
|
|
402
|
+
"Product Backend Mock cannot replace real product backend acceptance evidence: candidate or environment binding is invalid.",
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
const binding = boundFile(projectRoot, real.evidence, "Real product backend acceptance evidence");
|
|
406
|
+
const evidence = readJsonEvidence(binding.file, "Real product backend acceptance evidence");
|
|
407
|
+
const expected = {
|
|
408
|
+
schema_version: 1,
|
|
409
|
+
artifact_type: "real-product-backend-acceptance-evidence",
|
|
410
|
+
status: "passed",
|
|
411
|
+
source: "real-product-backend",
|
|
412
|
+
candidate: plan.candidate,
|
|
413
|
+
environment,
|
|
414
|
+
checks: {
|
|
415
|
+
contract: "passed",
|
|
416
|
+
end_to_end: "passed",
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
if (!sameStableValue(evidence, expected)) {
|
|
420
|
+
throw new Error("Real product backend evidence does not bind the candidate and non-Mock environment.");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function verifyUiAcceptance(projectRoot, options = {}) {
|
|
425
|
+
const root = path.resolve(projectRoot);
|
|
426
|
+
const plan = planUiAcceptance(root, options);
|
|
427
|
+
const resultsFile = resolveContainedPath(root, options.results, "UI Acceptance results");
|
|
428
|
+
const results = readYamlMapping(resultsFile, "UI Acceptance results");
|
|
429
|
+
if (results.schema_version !== 1 || results.artifact_type !== "ui-acceptance-results"
|
|
430
|
+
|| results.iteration_id !== plan.iteration_id
|
|
431
|
+
|| results.status !== "passed" || results.decision !== "pass") {
|
|
432
|
+
throw new Error("UI Acceptance results identity or passing decision is invalid.");
|
|
433
|
+
}
|
|
434
|
+
if (results.plan_checksum !== plan.plan_checksum
|
|
435
|
+
|| !sameStableValue(results.candidate, plan.candidate)
|
|
436
|
+
|| !sameStableValue(results.prototype_revision, plan.prototype_revision)
|
|
437
|
+
|| !sameStableValue(results.mock_scenario_pack, plan.mock_scenario_pack)
|
|
438
|
+
|| !sameStableValue(results.runner, plan.runner)) {
|
|
439
|
+
throw new Error("UI Acceptance results do not bind the current immutable plan.");
|
|
440
|
+
}
|
|
441
|
+
if (results.candidate_assurance?.node !== candidateAssuranceNode
|
|
442
|
+
|| Number(results.candidate_assurance?.node_attempt) !== plan.candidate.node_attempt
|
|
443
|
+
|| results.candidate_assurance?.candidate_id !== plan.candidate.candidate_id) {
|
|
444
|
+
throw new Error("UI Acceptance results do not bind the active Candidate Assurance attempt.");
|
|
445
|
+
}
|
|
446
|
+
const expectedById = new Map(plan.executions.map((item) => [item.execution_id, item]));
|
|
447
|
+
const executions = Array.isArray(results.executions) ? results.executions : [];
|
|
448
|
+
if (executions.length !== expectedById.size
|
|
449
|
+
|| new Set(executions.map((item) => item?.execution_id)).size !== executions.length
|
|
450
|
+
|| executions.some((item) => !expectedById.has(item?.execution_id))) {
|
|
451
|
+
throw new Error("UI Acceptance execution coverage is incomplete or contains unexpected entries.");
|
|
452
|
+
}
|
|
453
|
+
const viewportById = new Map(plan.policy.viewports.map((item) => [item.viewport_id, item]));
|
|
454
|
+
for (const execution of executions) {
|
|
455
|
+
const expected = expectedById.get(execution.execution_id);
|
|
456
|
+
if (execution.status !== "passed" || execution.scenario_id !== expected.scenario_id
|
|
457
|
+
|| execution.deterministic_rendering?.status !== "passed"
|
|
458
|
+
|| execution.deterministic_rendering?.scenario_id !== expected.scenario_id
|
|
459
|
+
|| (execution.deterministic_rendering?.fixture_ref || null) !== expected.fixture_ref) {
|
|
460
|
+
throw new Error(`Deterministic rendering did not pass: ${execution.execution_id}.`);
|
|
461
|
+
}
|
|
462
|
+
const visual = execution.visual;
|
|
463
|
+
const viewport = viewportById.get(expected.viewport_id);
|
|
464
|
+
const expectedPixels = viewport.width * viewport.height;
|
|
465
|
+
const changedPixels = Number(visual?.changed_pixels);
|
|
466
|
+
const totalPixels = Number(visual?.total_pixels);
|
|
467
|
+
const ratio = totalPixels > 0 ? changedPixels / totalPixels : Number.NaN;
|
|
468
|
+
if (visual?.status !== "passed" || !Number.isInteger(changedPixels) || changedPixels < 0
|
|
469
|
+
|| !Number.isInteger(totalPixels) || totalPixels !== expectedPixels
|
|
470
|
+
|| changedPixels > totalPixels
|
|
471
|
+
|| Math.abs(Number(visual.changed_pixel_ratio) - ratio) > Number.EPSILON
|
|
472
|
+
|| visual.max_diff_pixel_ratio !== plan.policy.max_diff_pixel_ratio
|
|
473
|
+
|| ratio > plan.policy.max_diff_pixel_ratio) {
|
|
474
|
+
throw new Error(`Visual diff exceeds or misreports the approved policy: ${execution.execution_id}.`);
|
|
475
|
+
}
|
|
476
|
+
const prototypeEvidence = boundFile(
|
|
477
|
+
root,
|
|
478
|
+
visual.prototype,
|
|
479
|
+
`Prototype screenshot ${execution.execution_id}`,
|
|
480
|
+
);
|
|
481
|
+
const candidateEvidence = boundFile(
|
|
482
|
+
root,
|
|
483
|
+
visual.candidate,
|
|
484
|
+
`Candidate screenshot ${execution.execution_id}`,
|
|
485
|
+
);
|
|
486
|
+
const diffEvidence = boundFile(root, visual.diff, `Visual diff ${execution.execution_id}`);
|
|
487
|
+
const measured = verifyVisualPngEvidence({
|
|
488
|
+
prototype: prototypeEvidence.file,
|
|
489
|
+
candidate: candidateEvidence.file,
|
|
490
|
+
diff: diffEvidence.file,
|
|
491
|
+
}, viewport, `UI Acceptance ${execution.execution_id}`);
|
|
492
|
+
if (measured.changedPixels !== changedPixels || measured.totalPixels !== totalPixels) {
|
|
493
|
+
throw new Error(
|
|
494
|
+
`Visual diff pixel counts do not match the PNG evidence: ${execution.execution_id}.`,
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
for (const name of [
|
|
498
|
+
"interaction", "keyboard", "focus", "touch_target", "text_overflow", "responsive", "recovery",
|
|
499
|
+
]) {
|
|
500
|
+
requirePassingCheck(root, execution[name], `${name} check ${execution.execution_id}`);
|
|
501
|
+
}
|
|
502
|
+
requirePassingCheck(root, execution.accessibility, `accessibility check ${execution.execution_id}`);
|
|
503
|
+
if (execution.accessibility.standard !== "WCAG 2.2 AA"
|
|
504
|
+
|| !Array.isArray(execution.accessibility.violations)
|
|
505
|
+
|| execution.accessibility.violations.length) {
|
|
506
|
+
throw new Error(`WCAG 2.2 AA acceptance failed: ${execution.execution_id}.`);
|
|
507
|
+
}
|
|
508
|
+
requirePassingCheck(root, execution.protocol, `protocol check ${execution.execution_id}`);
|
|
509
|
+
if (execution.protocol.scenario_id !== expected.scenario_id
|
|
510
|
+
|| execution.protocol.operation_or_channel !== expected.operation_or_channel) {
|
|
511
|
+
throw new Error(`Protocol evidence does not match the frozen Scenario: ${execution.execution_id}.`);
|
|
512
|
+
}
|
|
513
|
+
const protocolRequest = boundFile(
|
|
514
|
+
root,
|
|
515
|
+
execution.protocol.request,
|
|
516
|
+
`Protocol request ${execution.execution_id}`,
|
|
517
|
+
);
|
|
518
|
+
const protocolResponse = boundFile(
|
|
519
|
+
root,
|
|
520
|
+
execution.protocol.response,
|
|
521
|
+
`Protocol response ${execution.execution_id}`,
|
|
522
|
+
);
|
|
523
|
+
const actualRequest = readJsonEvidence(
|
|
524
|
+
protocolRequest.file,
|
|
525
|
+
`Protocol request ${execution.execution_id}`,
|
|
526
|
+
);
|
|
527
|
+
const actualResponse = readJsonEvidence(
|
|
528
|
+
protocolResponse.file,
|
|
529
|
+
`Protocol response ${execution.execution_id}`,
|
|
530
|
+
);
|
|
531
|
+
if (!sameStableValue(actualRequest, expected.protocol_expectation?.request)
|
|
532
|
+
|| !sameStableValue(actualResponse, expected.protocol_expectation?.response)) {
|
|
533
|
+
throw new Error(
|
|
534
|
+
`Protocol response or request contradicts the frozen Scenario: ${execution.execution_id}.`,
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
const expectedProductBackendMock = plan.real_product_backend_evidence_required;
|
|
539
|
+
if (results.mock_boundary?.product_backend_mock_used_for_rendering !== expectedProductBackendMock
|
|
540
|
+
|| results.mock_boundary?.product_backend_mock_accepted_as_real_backend_evidence !== false) {
|
|
541
|
+
throw new Error("Product Backend Mock authority is misrepresented in UI Acceptance results.");
|
|
542
|
+
}
|
|
543
|
+
if (expectedProductBackendMock) {
|
|
544
|
+
const real = results.real_product_backend_acceptance;
|
|
545
|
+
validateRealProductBackendEvidence(root, real, plan);
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
valid: true,
|
|
549
|
+
action: "verify",
|
|
550
|
+
decision: "pass",
|
|
551
|
+
candidate: plan.candidate,
|
|
552
|
+
plan_checksum: plan.plan_checksum,
|
|
553
|
+
results_checksum: sha256File(resultsFile),
|
|
554
|
+
execution_count: executions.length,
|
|
555
|
+
affected_page_ids: plan.affected_page_ids,
|
|
556
|
+
affected_review_scene_ids: plan.affected_review_scene_ids,
|
|
557
|
+
real_product_backend_evidence_verified: expectedProductBackendMock,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export function runUiAcceptanceCommand(projectRoot, action, options = {}) {
|
|
562
|
+
if (action === "plan") return planUiAcceptance(projectRoot, options);
|
|
563
|
+
if (action === "verify") return verifyUiAcceptance(projectRoot, options);
|
|
564
|
+
throw new Error(`Unknown ui-acceptance action: ${action}`);
|
|
565
|
+
}
|