ai-delivery-workflow 0.3.0 → 0.5.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 +42 -16
- package/bin/ai-delivery.mjs +181 -6
- package/bin/benchmark-autonomy.mjs +14 -6
- package/bin/release-check.mjs +10 -3
- package/docs/CLI-PARAMETER-REFERENCE.zh-CN.md +159 -17
- package/docs/CODEGRAPH-INTEGRATION.zh-CN.md +89 -0
- package/docs/DUAL-REPOSITORY-WORKSPACE.zh-CN.md +15 -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 +119 -46
- package/docs/MAINTENANCE-VERIFICATION.zh-CN.md +2 -2
- package/docs/MOCK-SYSTEM.zh-CN.md +141 -0
- package/docs/NPM-RELEASE-MANAGEMENT.zh-CN.md +2 -1
- package/docs/PROJECT-MANUAL.zh-CN.md +175 -98
- 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/adr/0006-product-line-candidate-inventory.md +20 -0
- package/docs/agents/domain.md +37 -0
- package/docs/agents/issue-tracker.md +26 -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-auth.mjs +16 -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 +6 -2
- package/skills/ai-delivery-assemble-release/SKILL.md +10 -8
- package/skills/ai-delivery-assemble-release/assets/release-candidate-manifest.yaml +16 -4
- package/skills/ai-delivery-assemble-release/assets/release-git-plan.yaml +96 -6
- package/skills/ai-delivery-assemble-release/assets/release-material-index.csv +1 -1
- package/skills/ai-delivery-assemble-release/assets/version-inclusion.csv +2 -2
- package/skills/ai-delivery-assemble-release/references/release-assembly-contract.md +16 -2
- package/skills/ai-delivery-assemble-release/scripts/plan-release.mjs +127 -3
- package/skills/ai-delivery-bootstrap/SKILL.md +4 -4
- 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/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/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/references/product-contract.md +2 -2
- 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/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/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-execute-test/SKILL.md +6 -2
- package/skills/ai-delivery-execute-work-package/SKILL.md +10 -3
- package/skills/ai-delivery-execute-work-package/references/work-package-contract.md +1 -1
- package/skills/ai-delivery-manage-git/SKILL.md +9 -7
- package/skills/ai-delivery-manage-git/references/git-policy.md +7 -3
- package/skills/ai-delivery-manage-git/scripts/release-git.mjs +995 -0
- package/skills/ai-delivery-orchestrate/SKILL.md +47 -29
- 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 +37 -9
- 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 +8 -6
- package/skills/ai-delivery-orchestrate-release/SKILL.md +12 -12
- package/skills/ai-delivery-orchestrate-release/assets/release-request.yaml +4 -1
- package/skills/ai-delivery-orchestrate-release/assets/version-selection.yaml +13 -1
- package/skills/ai-delivery-orchestrate-release/references/release-workflow-contract.md +15 -1
- package/skills/ai-delivery-plan-iteration/SKILL.md +11 -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-release/SKILL.md +6 -5
- package/skills/ai-delivery-prepare-release/assets/release-template/release-manifest.yaml +23 -2
- package/skills/ai-delivery-prepare-release/references/release-contract.md +5 -1
- package/skills/ai-delivery-review-change/SKILL.md +14 -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-validate-artifacts/SKILL.md +6 -3
- 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/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/SKILL.md +7 -4
- package/skills/ai-delivery-verify-production/assets/production-verification-template/production-verification.json +9 -0
- package/skills/ai-delivery-verify-production/assets/production-verification-template/production-verification.md +20 -0
- package/skills/ai-delivery-verify-production/references/production-verification-contract.md +19 -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,995 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const options = new Map();
|
|
12
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
13
|
+
const key = argv[index];
|
|
14
|
+
if (!key.startsWith("--")) throw new Error(`Unexpected argument: ${key}`);
|
|
15
|
+
const next = argv[index + 1];
|
|
16
|
+
const value = !next || next.startsWith("--") ? true : next;
|
|
17
|
+
if (value !== true) index += 1;
|
|
18
|
+
const previous = options.get(key);
|
|
19
|
+
options.set(key, previous === undefined ? value : [...(Array.isArray(previous) ? previous : [previous]), value]);
|
|
20
|
+
}
|
|
21
|
+
return options;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function option(options, name) {
|
|
25
|
+
const value = options.get(name);
|
|
26
|
+
if (Array.isArray(value)) throw new Error(`Option may be supplied only once: ${name}`);
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function required(options, name) {
|
|
31
|
+
const value = option(options, name);
|
|
32
|
+
if (value === undefined || value === true || String(value).trim() === "") {
|
|
33
|
+
throw new Error(`Missing required option: ${name}`);
|
|
34
|
+
}
|
|
35
|
+
return String(value).trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function optional(options, name) {
|
|
39
|
+
const value = option(options, name);
|
|
40
|
+
if (value === undefined) return null;
|
|
41
|
+
if (value === true || String(value).trim() === "") throw new Error(`Option requires a value: ${name}`);
|
|
42
|
+
return String(value).trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function flag(options, name) {
|
|
46
|
+
const value = option(options, name);
|
|
47
|
+
if (value === undefined) return false;
|
|
48
|
+
if (value !== true) throw new Error(`Flag does not accept a value: ${name}`);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function assertKnownOptions(options, allowed) {
|
|
53
|
+
for (const name of options.keys()) {
|
|
54
|
+
if (!allowed.has(name)) throw new Error(`Unsupported option: ${name}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function git(repo, args, { allowFailure = false } = {}) {
|
|
59
|
+
const result = spawnSync("git", args, { cwd: repo, encoding: "utf8", windowsHide: true });
|
|
60
|
+
if (result.error) throw new Error(`Cannot execute git ${args.join(" ")}: ${result.error.message}`);
|
|
61
|
+
if (!allowFailure && result.status !== 0) {
|
|
62
|
+
throw new Error(`Git command failed: git ${args.join(" ")}: ${(result.stderr || result.stdout).trim()}`);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
status: result.status,
|
|
66
|
+
stdout: (result.stdout || "").trim(),
|
|
67
|
+
stderr: (result.stderr || "").trim(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function repositoryRoot(input) {
|
|
72
|
+
const requested = path.resolve(input || process.cwd());
|
|
73
|
+
return git(requested, ["rev-parse", "--show-toplevel"]).stdout;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeObjectId(value, label) {
|
|
77
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
78
|
+
if (!/^[0-9a-f]{40}(?:[0-9a-f]{24})?$/.test(normalized)) {
|
|
79
|
+
throw new Error(`${label} must be a full immutable Git object ID.`);
|
|
80
|
+
}
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readJsonDocument(repo, value, label) {
|
|
85
|
+
const input = String(value || "").trim();
|
|
86
|
+
if (!input) throw new Error(`${label} is required.`);
|
|
87
|
+
const file = path.resolve(repo, input);
|
|
88
|
+
let raw;
|
|
89
|
+
let document;
|
|
90
|
+
try {
|
|
91
|
+
raw = fs.readFileSync(file, "utf8");
|
|
92
|
+
document = JSON.parse(raw);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
throw new Error(`Cannot read ${label} JSON: ${file} (${error.message})`);
|
|
95
|
+
}
|
|
96
|
+
if (!document || typeof document !== "object" || Array.isArray(document)) {
|
|
97
|
+
throw new Error(`${label} must contain a JSON object: ${file}`);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
file,
|
|
101
|
+
document,
|
|
102
|
+
sha256: crypto.createHash("sha256").update(raw).digest("hex"),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function readProductionVerification(repo, value, releaseId, verifiedCommit, expectedMain = null) {
|
|
107
|
+
const evidence = readJsonDocument(repo, value, "production_verification");
|
|
108
|
+
if (evidence.document.schema_version !== 1) {
|
|
109
|
+
throw new Error(`production_verification requires schema_version 1: ${evidence.file}`);
|
|
110
|
+
}
|
|
111
|
+
if (evidence.document.operation !== "production-verification-decision") {
|
|
112
|
+
throw new Error(`production_verification requires operation production-verification-decision: ${evidence.file}`);
|
|
113
|
+
}
|
|
114
|
+
if (evidence.document.release_id !== releaseId) {
|
|
115
|
+
throw new Error(`production_verification release_id does not match ${releaseId}: ${evidence.file}`);
|
|
116
|
+
}
|
|
117
|
+
if (evidence.document.decision !== "released") {
|
|
118
|
+
throw new Error(`production_verification must record decision released: ${evidence.file}`);
|
|
119
|
+
}
|
|
120
|
+
const evidenceCommit = normalizeObjectId(evidence.document.verified_release_commit, "production_verification.verified_release_commit");
|
|
121
|
+
if (evidenceCommit !== verifiedCommit) {
|
|
122
|
+
throw new Error(`production_verification binds ${evidenceCommit}, not verified release candidate ${verifiedCommit}.`);
|
|
123
|
+
}
|
|
124
|
+
const deployedCommit = normalizeObjectId(evidence.document.deployed_commit, "production_verification.deployed_commit");
|
|
125
|
+
if (deployedCommit !== verifiedCommit) {
|
|
126
|
+
throw new Error(`production_verification deployed_commit ${deployedCommit} does not match verified release candidate ${verifiedCommit}.`);
|
|
127
|
+
}
|
|
128
|
+
const evidenceExpectedMain = normalizeObjectId(evidence.document.expected_main_commit, "production_verification.expected_main_commit");
|
|
129
|
+
if (expectedMain !== null) {
|
|
130
|
+
if (evidenceExpectedMain !== expectedMain) {
|
|
131
|
+
throw new Error(`production_verification expected_main_commit ${evidenceExpectedMain} does not match R00 commit ${expectedMain}.`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
path: evidence.file,
|
|
136
|
+
sha256: evidence.sha256,
|
|
137
|
+
decision: evidence.document.decision,
|
|
138
|
+
verified_release_commit: evidenceCommit,
|
|
139
|
+
deployed_commit: deployedCommit,
|
|
140
|
+
...(expectedMain === null ? {} : { expected_main_commit: expectedMain }),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function readCandidateInventory(repo, value, releaseId, lineBranch, sourceCommit, verifiedCommit) {
|
|
145
|
+
const inventory = readJsonDocument(repo, value, "candidate_inventory");
|
|
146
|
+
if (inventory.document.schema_version !== 2) {
|
|
147
|
+
throw new Error(`candidate_inventory requires schema_version 2: ${inventory.file}`);
|
|
148
|
+
}
|
|
149
|
+
if (inventory.document.scope !== "product-line") {
|
|
150
|
+
throw new Error(`candidate_inventory requires scope product-line: ${inventory.file}`);
|
|
151
|
+
}
|
|
152
|
+
const productId = String(inventory.document.product_id || "").trim();
|
|
153
|
+
if (!productId) {
|
|
154
|
+
throw new Error(`candidate_inventory requires product_id: ${inventory.file}`);
|
|
155
|
+
}
|
|
156
|
+
const inventoryLineBranch = String(inventory.document.line_branch || "").trim();
|
|
157
|
+
if (!inventoryLineBranch) {
|
|
158
|
+
throw new Error(`candidate_inventory requires line_branch: ${inventory.file}`);
|
|
159
|
+
}
|
|
160
|
+
if (inventoryLineBranch !== lineBranch) {
|
|
161
|
+
throw new Error(`candidate_inventory line_branch ${inventoryLineBranch} does not match ${lineBranch}: ${inventory.file}`);
|
|
162
|
+
}
|
|
163
|
+
if (inventoryLineBranch !== `line/${productId}`) {
|
|
164
|
+
throw new Error(`candidate_inventory line_branch must be line/${productId}: ${inventory.file}`);
|
|
165
|
+
}
|
|
166
|
+
if (inventory.document.release_id !== undefined) {
|
|
167
|
+
throw new Error(`candidate_inventory is product-line scoped and must not define a root release_id: ${inventory.file}`);
|
|
168
|
+
}
|
|
169
|
+
const activeId = String(inventory.document.active_candidate_id || "").trim();
|
|
170
|
+
const activeCommit = normalizeObjectId(inventory.document.active_candidate_commit, "candidate_inventory.active_candidate_commit");
|
|
171
|
+
if (!activeId) throw new Error(`candidate_inventory requires active_candidate_id: ${inventory.file}`);
|
|
172
|
+
if (!Array.isArray(inventory.document.candidates)) {
|
|
173
|
+
throw new Error(`candidate_inventory requires candidates: ${inventory.file}`);
|
|
174
|
+
}
|
|
175
|
+
const allowedStatuses = new Set(["active", "release-ready", "verified", "approved", "stale", "released"]);
|
|
176
|
+
const candidates = inventory.document.candidates.map((candidate, index) => {
|
|
177
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
178
|
+
throw new Error(`candidate_inventory.candidates[${index}] must be an object: ${inventory.file}`);
|
|
179
|
+
}
|
|
180
|
+
const candidateId = String(candidate.candidate_id || "").trim();
|
|
181
|
+
if (!candidateId) throw new Error(`candidate_inventory.candidates[${index}] requires candidate_id: ${inventory.file}`);
|
|
182
|
+
const candidateReleaseId = String(candidate.release_id || "").trim();
|
|
183
|
+
if (!candidateReleaseId) throw new Error(`candidate_inventory.candidates[${index}] requires release_id: ${inventory.file}`);
|
|
184
|
+
const candidateCommit = normalizeObjectId(candidate.candidate_commit, `candidate_inventory.candidates[${index}].candidate_commit`);
|
|
185
|
+
const status = String(candidate.status || "").trim();
|
|
186
|
+
if (!status) throw new Error(`candidate_inventory.candidates[${index}] requires status: ${inventory.file}`);
|
|
187
|
+
if (!allowedStatuses.has(status)) {
|
|
188
|
+
throw new Error(`candidate_inventory.candidates[${index}] has unsupported status ${status}: ${inventory.file}`);
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
candidate_id: candidateId,
|
|
192
|
+
release_id: candidateReleaseId,
|
|
193
|
+
candidate_commit: candidateCommit,
|
|
194
|
+
status,
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
if (new Set(candidates.map((candidate) => candidate.candidate_id)).size !== candidates.length) {
|
|
198
|
+
throw new Error(`candidate_inventory contains duplicate candidate_id values: ${inventory.file}`);
|
|
199
|
+
}
|
|
200
|
+
const selectableStatuses = new Set(["active", "release-ready", "verified", "approved"]);
|
|
201
|
+
const active = candidates.find((candidate) => candidate.candidate_id === activeId);
|
|
202
|
+
if (!active || active.candidate_commit !== activeCommit) {
|
|
203
|
+
throw new Error(`candidate_inventory active candidate is not represented exactly in candidates: ${inventory.file}`);
|
|
204
|
+
}
|
|
205
|
+
if (active.release_id !== releaseId) {
|
|
206
|
+
throw new Error(`candidate_inventory active candidate ${activeId} does not belong to release ${releaseId}: ${inventory.file}`);
|
|
207
|
+
}
|
|
208
|
+
if (!selectableStatuses.has(active.status)) {
|
|
209
|
+
throw new Error(`candidate_inventory active candidate ${activeId} is not selectable: ${inventory.file}`);
|
|
210
|
+
}
|
|
211
|
+
const verifiedCandidate = candidates.find((candidate) => (
|
|
212
|
+
candidate.release_id === releaseId
|
|
213
|
+
&& candidate.candidate_commit === verifiedCommit
|
|
214
|
+
&& selectableStatuses.has(candidate.status)
|
|
215
|
+
));
|
|
216
|
+
if (!verifiedCandidate) {
|
|
217
|
+
throw new Error(`candidate_inventory does not include a selectable verified release candidate ${verifiedCommit}: ${inventory.file}`);
|
|
218
|
+
}
|
|
219
|
+
if (!isAncestor(repo, activeCommit, sourceCommit) || activeCommit === sourceCommit) {
|
|
220
|
+
throw new Error(`Release-fix commit ${sourceCommit} must descend from the active candidate ${activeId}.`);
|
|
221
|
+
}
|
|
222
|
+
if (!isAncestor(repo, activeCommit, verifiedCommit)) {
|
|
223
|
+
throw new Error(`Verified release candidate ${verifiedCommit} does not descend from active candidate ${activeId}.`);
|
|
224
|
+
}
|
|
225
|
+
const staleCandidateUpdates = candidates
|
|
226
|
+
.filter((candidate) => selectableStatuses.has(candidate.status) && !isAncestor(repo, sourceCommit, candidate.candidate_commit))
|
|
227
|
+
.map((candidate) => ({
|
|
228
|
+
candidate_id: candidate.candidate_id,
|
|
229
|
+
release_id: candidate.release_id,
|
|
230
|
+
candidate_commit: candidate.candidate_commit,
|
|
231
|
+
previous_status: candidate.status,
|
|
232
|
+
status: "stale",
|
|
233
|
+
reason: `omits-release-fix:${sourceCommit}`,
|
|
234
|
+
}));
|
|
235
|
+
return {
|
|
236
|
+
path: inventory.file,
|
|
237
|
+
sha256: inventory.sha256,
|
|
238
|
+
document: inventory.document,
|
|
239
|
+
product_id: productId,
|
|
240
|
+
line_branch: inventoryLineBranch,
|
|
241
|
+
active_candidate_id: activeId,
|
|
242
|
+
active_candidate_commit: activeCommit,
|
|
243
|
+
successor_active_candidate_id: staleCandidateUpdates.some((candidate) => candidate.candidate_id === activeId)
|
|
244
|
+
? verifiedCandidate.candidate_id
|
|
245
|
+
: activeId,
|
|
246
|
+
successor_active_candidate_commit: staleCandidateUpdates.some((candidate) => candidate.candidate_id === activeId)
|
|
247
|
+
? verifiedCandidate.candidate_commit
|
|
248
|
+
: activeCommit,
|
|
249
|
+
stale_candidate_updates: staleCandidateUpdates,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function jsonText(document) {
|
|
254
|
+
return `${JSON.stringify(document, null, 2)}\n`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function sha256Text(value) {
|
|
258
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function buildSuccessorCandidateInventory(candidateInventory) {
|
|
262
|
+
const document = JSON.parse(JSON.stringify(candidateInventory.document));
|
|
263
|
+
const updates = new Map(candidateInventory.stale_candidate_updates.map((candidate) => [candidate.candidate_id, candidate]));
|
|
264
|
+
for (const candidate of document.candidates) {
|
|
265
|
+
if (updates.has(candidate.candidate_id)) {
|
|
266
|
+
candidate.status = "stale";
|
|
267
|
+
candidate.status_reason = updates.get(candidate.candidate_id).reason;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
document.active_candidate_id = candidateInventory.successor_active_candidate_id;
|
|
271
|
+
document.active_candidate_commit = candidateInventory.successor_active_candidate_commit;
|
|
272
|
+
document.supersedes = {
|
|
273
|
+
path: candidateInventory.path,
|
|
274
|
+
sha256: candidateInventory.sha256,
|
|
275
|
+
};
|
|
276
|
+
return document;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function prepareSuccessorCandidateInventory(repo, value, candidateInventory) {
|
|
280
|
+
const destination = path.resolve(repo, String(value || "").trim());
|
|
281
|
+
if (samePath(destination, candidateInventory.path)) {
|
|
282
|
+
throw new Error("successor_candidate_inventory must not overwrite the immutable candidate_inventory input.");
|
|
283
|
+
}
|
|
284
|
+
const document = buildSuccessorCandidateInventory(candidateInventory);
|
|
285
|
+
const raw = jsonText(document);
|
|
286
|
+
return {
|
|
287
|
+
path: destination,
|
|
288
|
+
document,
|
|
289
|
+
raw,
|
|
290
|
+
sha256: sha256Text(raw),
|
|
291
|
+
schema_version: document.schema_version,
|
|
292
|
+
scope: document.scope,
|
|
293
|
+
product_id: document.product_id,
|
|
294
|
+
line_branch: document.line_branch,
|
|
295
|
+
active_candidate_id: document.active_candidate_id,
|
|
296
|
+
active_candidate_commit: document.active_candidate_commit,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function immutableDocumentConflict(label, destination) {
|
|
301
|
+
return new Error(`${label} already exists with content that does not match the controlled successor: ${destination}`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function preflightImmutableDocument(document, apply, label) {
|
|
305
|
+
if (fs.existsSync(document.path)) {
|
|
306
|
+
let raw;
|
|
307
|
+
try {
|
|
308
|
+
raw = fs.readFileSync(document.path, "utf8");
|
|
309
|
+
} catch (error) {
|
|
310
|
+
throw new Error(`Cannot read ${label}: ${document.path} (${error.message})`);
|
|
311
|
+
}
|
|
312
|
+
if (sha256Text(raw) !== document.sha256) throw immutableDocumentConflict(label, document.path);
|
|
313
|
+
return { exists: true, status: "existing-immutable-successor" };
|
|
314
|
+
}
|
|
315
|
+
if (!apply) return { exists: false, status: "not-written-dry-run" };
|
|
316
|
+
fs.mkdirSync(path.dirname(document.path), { recursive: true });
|
|
317
|
+
try {
|
|
318
|
+
fs.accessSync(path.dirname(document.path), fs.constants.W_OK);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
throw new Error(`Cannot write ${label} directory ${path.dirname(document.path)}: ${error.message}`);
|
|
321
|
+
}
|
|
322
|
+
return { exists: false, status: "pending-write" };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function writeImmutableDocument(document, preflight, label) {
|
|
326
|
+
if (preflight.exists) return { path: document.path, sha256: document.sha256, status: preflight.status };
|
|
327
|
+
try {
|
|
328
|
+
fs.writeFileSync(document.path, document.raw, { encoding: "utf8", flag: "wx" });
|
|
329
|
+
return { path: document.path, sha256: document.sha256, status: "written" };
|
|
330
|
+
} catch (error) {
|
|
331
|
+
if (error?.code !== "EEXIST") throw error;
|
|
332
|
+
const raced = preflightImmutableDocument(document, true, label);
|
|
333
|
+
if (!raced.exists) throw error;
|
|
334
|
+
return { path: document.path, sha256: document.sha256, status: raced.status };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function resolveCommit(repo, ref, label) {
|
|
339
|
+
const result = git(repo, ["rev-parse", "--verify", `${ref}^{commit}`], { allowFailure: true });
|
|
340
|
+
if (result.status !== 0) throw new Error(`${label} does not resolve to a commit: ${ref}`);
|
|
341
|
+
return result.stdout.toLowerCase();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function resolveBranch(repo, branch) {
|
|
345
|
+
const result = git(repo, ["rev-parse", "--verify", `refs/heads/${branch}^{commit}`], { allowFailure: true });
|
|
346
|
+
return result.status === 0 ? result.stdout.toLowerCase() : null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function assertBranchName(repo, branch, prefix, label) {
|
|
350
|
+
const normalized = String(branch || "").trim();
|
|
351
|
+
if (!normalized.startsWith(prefix)) throw new Error(`${label} must start with ${prefix}: ${normalized || "missing"}`);
|
|
352
|
+
const result = git(repo, ["check-ref-format", "--branch", normalized], { allowFailure: true });
|
|
353
|
+
if (result.status !== 0) throw new Error(`${label} is not a valid branch name: ${normalized}`);
|
|
354
|
+
return normalized;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function assertTagName(repo, tag, prefix, label) {
|
|
358
|
+
const normalized = String(tag || "").trim();
|
|
359
|
+
if (!normalized.startsWith(prefix)) throw new Error(`${label} must start with ${prefix}: ${normalized || "missing"}`);
|
|
360
|
+
const result = git(repo, ["check-ref-format", `refs/tags/${normalized}`], { allowFailure: true });
|
|
361
|
+
if (result.status !== 0) throw new Error(`${label} is not a valid Tag name: ${normalized}`);
|
|
362
|
+
return normalized;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function assertExactBranchHead(repo, branch, expected, label) {
|
|
366
|
+
const actual = resolveBranch(repo, branch);
|
|
367
|
+
if (!actual) throw new Error(`${label} does not exist: ${branch}`);
|
|
368
|
+
if (actual !== expected) {
|
|
369
|
+
throw new Error(`${label} drifted: expected ${expected}, found ${actual}. Refresh the release request.`);
|
|
370
|
+
}
|
|
371
|
+
return actual;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function assertRemoteBranchHead(remote, branch, expected) {
|
|
375
|
+
if (!remote) return { checked: false, remote: null, commit: null };
|
|
376
|
+
const reference = `refs/heads/${branch}`;
|
|
377
|
+
const result = spawnSync("git", ["ls-remote", "--heads", remote, reference], {
|
|
378
|
+
encoding: "utf8",
|
|
379
|
+
windowsHide: true,
|
|
380
|
+
});
|
|
381
|
+
if (result.error || result.status !== 0) {
|
|
382
|
+
throw new Error(`Cannot validate remote branch ${branch}: ${(result.stderr || result.error?.message || "unknown error").trim()}`);
|
|
383
|
+
}
|
|
384
|
+
const commits = (result.stdout || "").split(/\r?\n/).filter(Boolean).map((line) => line.split(/\s+/)[0].toLowerCase());
|
|
385
|
+
if (commits.length !== 1 || commits[0] !== expected) {
|
|
386
|
+
throw new Error(`Remote branch ${branch} drifted from expected commit ${expected}. Refresh the release request.`);
|
|
387
|
+
}
|
|
388
|
+
return { checked: true, remote, commit: expected };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function isAncestor(repo, ancestor, descendant) {
|
|
392
|
+
return git(repo, ["merge-base", "--is-ancestor", ancestor, descendant], { allowFailure: true }).status === 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function listWorktrees(repo) {
|
|
396
|
+
const lines = git(repo, ["worktree", "list", "--porcelain"]).stdout.split(/\r?\n/);
|
|
397
|
+
const worktrees = [];
|
|
398
|
+
let current = null;
|
|
399
|
+
const finish = () => {
|
|
400
|
+
if (current?.path) worktrees.push(current);
|
|
401
|
+
current = null;
|
|
402
|
+
};
|
|
403
|
+
for (const line of lines) {
|
|
404
|
+
if (!line) {
|
|
405
|
+
finish();
|
|
406
|
+
} else if (line.startsWith("worktree ")) {
|
|
407
|
+
finish();
|
|
408
|
+
current = { path: line.slice("worktree ".length), branch: null };
|
|
409
|
+
} else if (line.startsWith("branch ") && current) {
|
|
410
|
+
current.branch = line.slice("branch ".length);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
finish();
|
|
414
|
+
return worktrees;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function assertCleanWorktrees(repo) {
|
|
418
|
+
for (const worktree of listWorktrees(repo)) {
|
|
419
|
+
const status = git(worktree.path, ["status", "--porcelain=v1", "--untracked-files=all"]);
|
|
420
|
+
if (status.stdout) throw new Error(`Working tree is not clean: ${worktree.path}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function samePath(left, right) {
|
|
425
|
+
const normalize = (value) => {
|
|
426
|
+
const resolved = path.resolve(value).replaceAll("\\", "/");
|
|
427
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
428
|
+
};
|
|
429
|
+
return normalize(left) === normalize(right);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function targetBranchWorktree(repo, branch) {
|
|
433
|
+
const ref = `refs/heads/${branch}`;
|
|
434
|
+
return listWorktrees(repo).find((worktree) => worktree.branch === ref) || null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function zeroObjectId(repo) {
|
|
438
|
+
return git(repo, ["rev-parse", "--show-object-format"]).stdout === "sha256" ? "0".repeat(64) : "0".repeat(40);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function createRef(repo, branch, commit) {
|
|
442
|
+
git(repo, ["update-ref", `refs/heads/${branch}`, commit, zeroObjectId(repo)]);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function updateRef(repo, branch, commit, expected) {
|
|
446
|
+
git(repo, ["update-ref", `refs/heads/${branch}`, commit, expected]);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function withDetachedWorktree(repo, startCommit, action) {
|
|
450
|
+
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "ai-delivery-release-git-"));
|
|
451
|
+
let added = false;
|
|
452
|
+
try {
|
|
453
|
+
git(repo, ["worktree", "add", "--detach", "--quiet", temporary, startCommit]);
|
|
454
|
+
added = true;
|
|
455
|
+
return action(temporary);
|
|
456
|
+
} finally {
|
|
457
|
+
if (added) git(repo, ["worktree", "remove", "--force", temporary], { allowFailure: true });
|
|
458
|
+
if (fs.existsSync(temporary)) fs.rmSync(temporary, { recursive: true, force: true });
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function parentsOf(repo, commit) {
|
|
463
|
+
const values = git(repo, ["rev-list", "--parents", "-n", "1", commit]).stdout.split(/\s+/).filter(Boolean);
|
|
464
|
+
return values.slice(1).map((value) => value.toLowerCase());
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function treeOf(repo, commit) {
|
|
468
|
+
return git(repo, ["rev-parse", `${commit}^{tree}`]).stdout.toLowerCase();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function assertExistingCandidate(repo, branch, releaseBase, sourceCommit) {
|
|
472
|
+
const candidate = resolveBranch(repo, branch);
|
|
473
|
+
if (!candidate) return null;
|
|
474
|
+
if (candidate === releaseBase && isAncestor(repo, sourceCommit, releaseBase)) {
|
|
475
|
+
return { commit: candidate, merge_created: false };
|
|
476
|
+
}
|
|
477
|
+
const parents = parentsOf(repo, candidate);
|
|
478
|
+
if (parents.length !== 2 || parents[0] !== releaseBase || parents[1] !== sourceCommit) {
|
|
479
|
+
throw new Error(`Existing release branch ${branch} is not the controlled merge of ${releaseBase} and ${sourceCommit}.`);
|
|
480
|
+
}
|
|
481
|
+
if (treeOf(repo, candidate) !== treeOf(repo, sourceCommit)) {
|
|
482
|
+
throw new Error(`Existing release branch ${branch} has an unexpected integration tree.`);
|
|
483
|
+
}
|
|
484
|
+
return { commit: candidate, merge_created: true };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function receiptPath(repo, value, apply) {
|
|
488
|
+
if (!apply) return value ? path.resolve(repo, value) : null;
|
|
489
|
+
if (!value) throw new Error("--receipt is required with --apply so the Git transition is auditable.");
|
|
490
|
+
return path.resolve(repo, value);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function readReceipt(destination) {
|
|
494
|
+
try {
|
|
495
|
+
const parsed = JSON.parse(fs.readFileSync(destination, "utf8"));
|
|
496
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
497
|
+
throw new Error("receipt is not an object");
|
|
498
|
+
}
|
|
499
|
+
return parsed;
|
|
500
|
+
} catch (error) {
|
|
501
|
+
throw new Error(`Receipt already exists and is not valid JSON: ${destination} (${error.message})`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function sameReceiptValue(left, right) {
|
|
506
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function assertReceiptIdentity(existing, expected, destination) {
|
|
510
|
+
const identityFields = [
|
|
511
|
+
"schema_version", "operation", "release_id", "production_branch", "release_branch",
|
|
512
|
+
"release_base_commit", "expected_main_commit", "source_ref", "source_commit",
|
|
513
|
+
"verified_release_commit", "deployed_commit", "line_branch", "expected_line_commit",
|
|
514
|
+
"merge_strategy", "source_relation", "production_verification", "candidate_inventory",
|
|
515
|
+
"successor_candidate_inventory",
|
|
516
|
+
"stale_candidate_ids", "candidate_status_updates",
|
|
517
|
+
];
|
|
518
|
+
for (const field of identityFields) {
|
|
519
|
+
if (expected[field] === undefined) continue;
|
|
520
|
+
if (!sameReceiptValue(existing[field], expected[field])) {
|
|
521
|
+
throw new Error(`Receipt identity conflicts for ${field}: ${destination}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function preflightReceipt(receipt, destination, apply) {
|
|
527
|
+
if (!destination) return null;
|
|
528
|
+
if (fs.existsSync(destination)) {
|
|
529
|
+
const existing = readReceipt(destination);
|
|
530
|
+
assertReceiptIdentity(existing, receipt, destination);
|
|
531
|
+
return existing;
|
|
532
|
+
}
|
|
533
|
+
if (!apply) return null;
|
|
534
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
535
|
+
try {
|
|
536
|
+
fs.accessSync(path.dirname(destination), fs.constants.W_OK);
|
|
537
|
+
} catch (error) {
|
|
538
|
+
throw new Error(`Cannot write receipt directory ${path.dirname(destination)}: ${error.message}`);
|
|
539
|
+
}
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function attachReceipt(receipt, destination, apply, existingReceipt = null) {
|
|
544
|
+
if (!destination) return receipt;
|
|
545
|
+
if (!apply) return { ...receipt, receipt: { path: destination, status: "not-written-dry-run" } };
|
|
546
|
+
const existing = existingReceipt || (fs.existsSync(destination) ? readReceipt(destination) : null);
|
|
547
|
+
if (existing) {
|
|
548
|
+
assertReceiptIdentity(existing, receipt, destination);
|
|
549
|
+
return { ...receipt, receipt: { path: destination, status: "existing-immutable-receipt" } };
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
fs.writeFileSync(destination, `${JSON.stringify(receipt, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
553
|
+
} catch (error) {
|
|
554
|
+
if (error?.code !== "EEXIST") throw error;
|
|
555
|
+
const raced = readReceipt(destination);
|
|
556
|
+
assertReceiptIdentity(raced, receipt, destination);
|
|
557
|
+
return { ...receipt, receipt: { path: destination, status: "existing-immutable-receipt" } };
|
|
558
|
+
}
|
|
559
|
+
return { ...receipt, receipt: { path: destination, status: "written" } };
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function commonOptions(options) {
|
|
563
|
+
const repo = repositoryRoot(optional(options, "--repo") || process.cwd());
|
|
564
|
+
const apply = flag(options, "--apply");
|
|
565
|
+
if (flag(options, "--dry-run") && apply) throw new Error("--apply and --dry-run cannot be used together.");
|
|
566
|
+
return {
|
|
567
|
+
repo,
|
|
568
|
+
apply,
|
|
569
|
+
remote: optional(options, "--remote"),
|
|
570
|
+
receipt: receiptPath(repo, optional(options, "--receipt"), apply),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export function createReleaseCandidate(input) {
|
|
575
|
+
const repo = repositoryRoot(input.repo || process.cwd());
|
|
576
|
+
const releaseId = String(input.release_id || "").trim();
|
|
577
|
+
if (!releaseId) throw new Error("release_id is required.");
|
|
578
|
+
const releaseBranch = assertBranchName(repo, input.release_branch, `release/${releaseId}-`, "release_branch");
|
|
579
|
+
const productionBranch = assertBranchName(repo, input.production_branch || "main", "", "production_branch");
|
|
580
|
+
const sourceTag = assertTagName(repo, input.source_tag, "version-ready/", "source_tag");
|
|
581
|
+
const expectedMain = normalizeObjectId(input.expected_main, "expected_main");
|
|
582
|
+
const expectedSource = normalizeObjectId(input.source_commit, "source_commit");
|
|
583
|
+
const receipt = receiptPath(repo, input.receipt || null, Boolean(input.apply));
|
|
584
|
+
|
|
585
|
+
assertCleanWorktrees(repo);
|
|
586
|
+
assertExactBranchHead(repo, productionBranch, expectedMain, `Production branch ${productionBranch}`);
|
|
587
|
+
const remoteValidation = assertRemoteBranchHead(input.remote || null, productionBranch, expectedMain);
|
|
588
|
+
const sourceCommit = resolveCommit(repo, `refs/tags/${sourceTag}`, `Source Tag ${sourceTag}`);
|
|
589
|
+
if (sourceCommit !== expectedSource) throw new Error(`Source Tag ${sourceTag} drifted: expected ${expectedSource}, found ${sourceCommit}.`);
|
|
590
|
+
const targetContainsReleaseBase = isAncestor(repo, expectedMain, sourceCommit);
|
|
591
|
+
const targetAlreadyInReleaseBase = isAncestor(repo, sourceCommit, expectedMain);
|
|
592
|
+
if (!targetContainsReleaseBase && !targetAlreadyInReleaseBase) {
|
|
593
|
+
throw new Error(`Captured production branch ${productionBranch} commit ${expectedMain} and ${sourceTag} diverge; recreate the version candidate before release.`);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const existing = assertExistingCandidate(repo, releaseBranch, expectedMain, sourceCommit);
|
|
597
|
+
const baseReceipt = {
|
|
598
|
+
schema_version: 1,
|
|
599
|
+
operation: "create-release-candidate",
|
|
600
|
+
release_id: releaseId,
|
|
601
|
+
production_branch: productionBranch,
|
|
602
|
+
release_branch: releaseBranch,
|
|
603
|
+
release_base_commit: expectedMain,
|
|
604
|
+
source_ref: sourceTag,
|
|
605
|
+
source_commit: sourceCommit,
|
|
606
|
+
merge_strategy: "no-ff-single-version-ready-tag",
|
|
607
|
+
source_relation: targetContainsReleaseBase ? "target-descends-from-release-base" : "target-already-in-release-base",
|
|
608
|
+
remote_validation: remoteValidation,
|
|
609
|
+
};
|
|
610
|
+
const existingReceipt = preflightReceipt(baseReceipt, receipt, Boolean(input.apply));
|
|
611
|
+
if (existing) {
|
|
612
|
+
if (existingReceipt && existingReceipt.candidate_commit !== existing.commit) {
|
|
613
|
+
throw new Error(`Receipt ${receipt} does not describe the existing release branch ${releaseBranch}.`);
|
|
614
|
+
}
|
|
615
|
+
return attachReceipt({
|
|
616
|
+
...baseReceipt,
|
|
617
|
+
status: "idempotent",
|
|
618
|
+
candidate_commit: existing.commit,
|
|
619
|
+
merge_commit: existing.merge_created ? existing.commit : null,
|
|
620
|
+
merge_created: existing.merge_created,
|
|
621
|
+
}, receipt, Boolean(input.apply), existingReceipt);
|
|
622
|
+
}
|
|
623
|
+
if (existingReceipt) {
|
|
624
|
+
throw new Error(`Receipt ${receipt} exists but release branch ${releaseBranch} is missing; recover the controlled operation before retrying.`);
|
|
625
|
+
}
|
|
626
|
+
if (!input.apply) {
|
|
627
|
+
return attachReceipt({
|
|
628
|
+
...baseReceipt,
|
|
629
|
+
status: "planned",
|
|
630
|
+
candidate_commit: targetAlreadyInReleaseBase ? expectedMain : null,
|
|
631
|
+
merge_commit: null,
|
|
632
|
+
merge_created: targetAlreadyInReleaseBase ? false : null,
|
|
633
|
+
}, receipt, false);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const candidate = targetAlreadyInReleaseBase ? expectedMain : withDetachedWorktree(repo, expectedMain, (worktree) => {
|
|
637
|
+
git(worktree, ["merge", "--no-ff", "--no-edit", `refs/tags/${sourceTag}`]);
|
|
638
|
+
const commit = resolveCommit(worktree, "HEAD", "Release candidate");
|
|
639
|
+
const parents = parentsOf(worktree, commit);
|
|
640
|
+
if (parents.length !== 2 || parents[0] !== expectedMain || parents[1] !== sourceCommit) {
|
|
641
|
+
throw new Error("Controlled release merge did not create the expected two-parent candidate.");
|
|
642
|
+
}
|
|
643
|
+
if (treeOf(worktree, commit) !== treeOf(worktree, sourceCommit)) {
|
|
644
|
+
throw new Error("Controlled release merge produced an unexpected candidate tree.");
|
|
645
|
+
}
|
|
646
|
+
return commit;
|
|
647
|
+
});
|
|
648
|
+
assertExactBranchHead(repo, productionBranch, expectedMain, `Production branch ${productionBranch}`);
|
|
649
|
+
assertRemoteBranchHead(input.remote || null, productionBranch, expectedMain);
|
|
650
|
+
createRef(repo, releaseBranch, candidate);
|
|
651
|
+
return attachReceipt({
|
|
652
|
+
...baseReceipt,
|
|
653
|
+
status: "applied",
|
|
654
|
+
candidate_commit: candidate,
|
|
655
|
+
merge_commit: candidate === expectedMain ? null : candidate,
|
|
656
|
+
merge_created: candidate !== expectedMain,
|
|
657
|
+
}, receipt, true, existingReceipt);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export function promoteReleaseCandidate(input) {
|
|
661
|
+
const repo = repositoryRoot(input.repo || process.cwd());
|
|
662
|
+
const releaseId = String(input.release_id || "").trim();
|
|
663
|
+
if (!releaseId) throw new Error("release_id is required.");
|
|
664
|
+
const productionBranch = assertBranchName(repo, input.production_branch || "main", "", "production_branch");
|
|
665
|
+
const releaseBranch = assertBranchName(repo, input.release_branch, `release/${releaseId}-`, "release_branch");
|
|
666
|
+
const expectedMain = normalizeObjectId(input.expected_main, "expected_main");
|
|
667
|
+
const verifiedCommit = normalizeObjectId(input.verified_release_commit, "verified_release_commit");
|
|
668
|
+
const receipt = receiptPath(repo, input.receipt || null, Boolean(input.apply));
|
|
669
|
+
const productionVerification = readProductionVerification(
|
|
670
|
+
repo,
|
|
671
|
+
input.production_verification,
|
|
672
|
+
releaseId,
|
|
673
|
+
verifiedCommit,
|
|
674
|
+
expectedMain,
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
assertCleanWorktrees(repo);
|
|
678
|
+
const branchCommit = resolveBranch(repo, releaseBranch);
|
|
679
|
+
if (branchCommit !== verifiedCommit) {
|
|
680
|
+
throw new Error(`Release branch ${releaseBranch} does not name the verified candidate ${verifiedCommit}.`);
|
|
681
|
+
}
|
|
682
|
+
if (!isAncestor(repo, expectedMain, verifiedCommit)) {
|
|
683
|
+
throw new Error(`Verified release candidate ${verifiedCommit} does not descend from captured production commit ${expectedMain}.`);
|
|
684
|
+
}
|
|
685
|
+
const currentMain = resolveBranch(repo, productionBranch);
|
|
686
|
+
const baseReceipt = {
|
|
687
|
+
schema_version: 1,
|
|
688
|
+
operation: "promote-release-candidate",
|
|
689
|
+
release_id: releaseId,
|
|
690
|
+
production_branch: productionBranch,
|
|
691
|
+
release_branch: releaseBranch,
|
|
692
|
+
expected_main_commit: expectedMain,
|
|
693
|
+
verified_release_commit: verifiedCommit,
|
|
694
|
+
deployed_commit: verifiedCommit,
|
|
695
|
+
merge_strategy: "fast-forward-production-to-verified-candidate",
|
|
696
|
+
production_verification: productionVerification,
|
|
697
|
+
};
|
|
698
|
+
const existingReceipt = preflightReceipt(baseReceipt, receipt, Boolean(input.apply));
|
|
699
|
+
if (currentMain === verifiedCommit) {
|
|
700
|
+
if (currentMain !== expectedMain && !existingReceipt) {
|
|
701
|
+
throw new Error(`Production branch ${productionBranch} already names the verified candidate without a matching immutable receipt; recover or recreate the release request.`);
|
|
702
|
+
}
|
|
703
|
+
if (existingReceipt && existingReceipt.new_main_head !== verifiedCommit) {
|
|
704
|
+
throw new Error(`Receipt ${receipt} does not describe production branch ${productionBranch} at ${verifiedCommit}.`);
|
|
705
|
+
}
|
|
706
|
+
const remoteValidation = assertRemoteBranchHead(input.remote || null, productionBranch, verifiedCommit);
|
|
707
|
+
return attachReceipt({
|
|
708
|
+
...baseReceipt,
|
|
709
|
+
status: "idempotent",
|
|
710
|
+
old_main_head: currentMain,
|
|
711
|
+
new_main_head: currentMain,
|
|
712
|
+
remote_validation: remoteValidation,
|
|
713
|
+
}, receipt, Boolean(input.apply), existingReceipt);
|
|
714
|
+
}
|
|
715
|
+
if (currentMain !== expectedMain) {
|
|
716
|
+
throw new Error(`Production branch ${productionBranch} changed after candidate creation: expected ${expectedMain}, found ${currentMain}. Recreate the release candidate.`);
|
|
717
|
+
}
|
|
718
|
+
if (existingReceipt) {
|
|
719
|
+
throw new Error(`Receipt ${receipt} exists but production branch ${productionBranch} has not reached the recorded candidate; recover the controlled operation before retrying.`);
|
|
720
|
+
}
|
|
721
|
+
const remoteValidation = assertRemoteBranchHead(input.remote || null, productionBranch, expectedMain);
|
|
722
|
+
if (!input.apply) {
|
|
723
|
+
return attachReceipt({
|
|
724
|
+
...baseReceipt,
|
|
725
|
+
status: "planned",
|
|
726
|
+
old_main_head: currentMain,
|
|
727
|
+
new_main_head: verifiedCommit,
|
|
728
|
+
remote_validation: remoteValidation,
|
|
729
|
+
}, receipt, false);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const holder = targetBranchWorktree(repo, productionBranch);
|
|
733
|
+
if (holder && !samePath(holder.path, repo)) {
|
|
734
|
+
throw new Error(`Production branch ${productionBranch} is checked out in another worktree: ${holder.path}`);
|
|
735
|
+
}
|
|
736
|
+
if (holder) {
|
|
737
|
+
assertExactBranchHead(repo, productionBranch, expectedMain, `Production branch ${productionBranch}`);
|
|
738
|
+
git(repo, ["merge", "--ff-only", verifiedCommit]);
|
|
739
|
+
} else {
|
|
740
|
+
assertExactBranchHead(repo, productionBranch, expectedMain, `Production branch ${productionBranch}`);
|
|
741
|
+
assertRemoteBranchHead(input.remote || null, productionBranch, expectedMain);
|
|
742
|
+
updateRef(repo, productionBranch, verifiedCommit, expectedMain);
|
|
743
|
+
}
|
|
744
|
+
const newMain = assertExactBranchHead(repo, productionBranch, verifiedCommit, `Production branch ${productionBranch}`);
|
|
745
|
+
return attachReceipt({
|
|
746
|
+
...baseReceipt,
|
|
747
|
+
status: "applied",
|
|
748
|
+
old_main_head: currentMain,
|
|
749
|
+
new_main_head: newMain,
|
|
750
|
+
remote_validation: remoteValidation,
|
|
751
|
+
}, receipt, true, existingReceipt);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
export function synchronizeReleaseFix(input) {
|
|
755
|
+
const repo = repositoryRoot(input.repo || process.cwd());
|
|
756
|
+
const releaseId = String(input.release_id || "").trim();
|
|
757
|
+
if (!releaseId) throw new Error("release_id is required.");
|
|
758
|
+
const sourceRef = assertBranchName(repo, input.source_ref, `release-fix/${releaseId}/`, "source_ref");
|
|
759
|
+
const lineBranch = assertBranchName(repo, input.line_branch, "line/", "line_branch");
|
|
760
|
+
const expectedLine = normalizeObjectId(input.expected_line, "expected_line");
|
|
761
|
+
const expectedSource = normalizeObjectId(input.source_commit, "source_commit");
|
|
762
|
+
const verifiedCommit = normalizeObjectId(input.verified_release_commit, "verified_release_commit");
|
|
763
|
+
const receipt = receiptPath(repo, input.receipt || null, Boolean(input.apply));
|
|
764
|
+
|
|
765
|
+
assertCleanWorktrees(repo);
|
|
766
|
+
const sourceCommit = resolveBranch(repo, sourceRef);
|
|
767
|
+
if (sourceCommit !== expectedSource) {
|
|
768
|
+
throw new Error(`Release-fix branch ${sourceRef} drifted: expected ${expectedSource}, found ${sourceCommit || "missing"}.`);
|
|
769
|
+
}
|
|
770
|
+
if (!isAncestor(repo, sourceCommit, verifiedCommit)) {
|
|
771
|
+
throw new Error(`Verified release candidate ${verifiedCommit} does not contain release-fix commit ${sourceCommit}.`);
|
|
772
|
+
}
|
|
773
|
+
const productionVerification = readProductionVerification(
|
|
774
|
+
repo,
|
|
775
|
+
input.production_verification,
|
|
776
|
+
releaseId,
|
|
777
|
+
verifiedCommit,
|
|
778
|
+
);
|
|
779
|
+
const candidateInventory = readCandidateInventory(
|
|
780
|
+
repo,
|
|
781
|
+
input.candidate_inventory,
|
|
782
|
+
releaseId,
|
|
783
|
+
lineBranch,
|
|
784
|
+
sourceCommit,
|
|
785
|
+
verifiedCommit,
|
|
786
|
+
);
|
|
787
|
+
let successorCandidateInventory = null;
|
|
788
|
+
let successorInventoryPreflight = null;
|
|
789
|
+
const successorInventoryValue = String(input.successor_candidate_inventory || "").trim();
|
|
790
|
+
const successorInventoryPath = successorInventoryValue ? path.resolve(repo, successorInventoryValue) : null;
|
|
791
|
+
if (input.apply && !successorInventoryPath) {
|
|
792
|
+
throw new Error("--successor-candidate-inventory is required with --apply so stale status changes are immutable.");
|
|
793
|
+
}
|
|
794
|
+
if (successorInventoryPath) {
|
|
795
|
+
successorCandidateInventory = prepareSuccessorCandidateInventory(repo, successorInventoryPath, candidateInventory);
|
|
796
|
+
if (receipt && samePath(receipt, successorCandidateInventory.path)) {
|
|
797
|
+
throw new Error("successor_candidate_inventory must not overwrite the operation receipt.");
|
|
798
|
+
}
|
|
799
|
+
successorInventoryPreflight = preflightImmutableDocument(
|
|
800
|
+
successorCandidateInventory,
|
|
801
|
+
Boolean(input.apply),
|
|
802
|
+
"successor_candidate_inventory",
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
const currentLine = resolveBranch(repo, lineBranch);
|
|
806
|
+
if (!currentLine) throw new Error(`Development line does not exist: ${lineBranch}`);
|
|
807
|
+
const baseReceipt = {
|
|
808
|
+
schema_version: 1,
|
|
809
|
+
operation: "synchronize-release-fix-to-line",
|
|
810
|
+
release_id: releaseId,
|
|
811
|
+
source_ref: sourceRef,
|
|
812
|
+
source_commit: sourceCommit,
|
|
813
|
+
verified_release_commit: verifiedCommit,
|
|
814
|
+
line_branch: lineBranch,
|
|
815
|
+
expected_line_commit: expectedLine,
|
|
816
|
+
merge_strategy: "no-ff-merge-release-fix-into-line",
|
|
817
|
+
production_verification: productionVerification,
|
|
818
|
+
candidate_inventory: {
|
|
819
|
+
path: candidateInventory.path,
|
|
820
|
+
sha256: candidateInventory.sha256,
|
|
821
|
+
schema_version: 2,
|
|
822
|
+
scope: candidateInventory.document.scope,
|
|
823
|
+
product_id: candidateInventory.product_id,
|
|
824
|
+
line_branch: candidateInventory.line_branch,
|
|
825
|
+
active_candidate_id: candidateInventory.active_candidate_id,
|
|
826
|
+
active_candidate_commit: candidateInventory.active_candidate_commit,
|
|
827
|
+
},
|
|
828
|
+
...(successorCandidateInventory ? {
|
|
829
|
+
successor_candidate_inventory: {
|
|
830
|
+
path: successorCandidateInventory.path,
|
|
831
|
+
sha256: successorCandidateInventory.sha256,
|
|
832
|
+
schema_version: successorCandidateInventory.schema_version,
|
|
833
|
+
scope: successorCandidateInventory.scope,
|
|
834
|
+
product_id: successorCandidateInventory.product_id,
|
|
835
|
+
line_branch: successorCandidateInventory.line_branch,
|
|
836
|
+
active_candidate_id: successorCandidateInventory.active_candidate_id,
|
|
837
|
+
active_candidate_commit: successorCandidateInventory.active_candidate_commit,
|
|
838
|
+
},
|
|
839
|
+
} : {}),
|
|
840
|
+
stale_candidate_ids: candidateInventory.stale_candidate_updates.map((candidate) => candidate.candidate_id),
|
|
841
|
+
candidate_status_updates: candidateInventory.stale_candidate_updates,
|
|
842
|
+
};
|
|
843
|
+
const existingReceipt = preflightReceipt(baseReceipt, receipt, Boolean(input.apply));
|
|
844
|
+
if (isAncestor(repo, sourceCommit, currentLine)) {
|
|
845
|
+
if (existingReceipt && !isAncestor(repo, sourceCommit, existingReceipt.new_line_head || currentLine)) {
|
|
846
|
+
throw new Error(`Receipt ${receipt} does not retain release-fix commit ${sourceCommit}.`);
|
|
847
|
+
}
|
|
848
|
+
if (existingReceipt && (!successorInventoryPreflight || !successorInventoryPreflight.exists)) {
|
|
849
|
+
throw new Error(`Receipt ${receipt} requires an exact immutable successor candidate inventory, but it is missing.`);
|
|
850
|
+
}
|
|
851
|
+
const remoteValidation = assertRemoteBranchHead(input.remote || null, lineBranch, currentLine);
|
|
852
|
+
const successorInventoryWrite = input.apply && successorCandidateInventory
|
|
853
|
+
? writeImmutableDocument(successorCandidateInventory, successorInventoryPreflight, "successor_candidate_inventory")
|
|
854
|
+
: successorCandidateInventory
|
|
855
|
+
? { path: successorCandidateInventory.path, sha256: successorCandidateInventory.sha256, status: successorInventoryPreflight.status }
|
|
856
|
+
: null;
|
|
857
|
+
return attachReceipt({
|
|
858
|
+
...baseReceipt,
|
|
859
|
+
status: "idempotent",
|
|
860
|
+
old_line_head: currentLine,
|
|
861
|
+
new_line_head: currentLine,
|
|
862
|
+
remote_validation: remoteValidation,
|
|
863
|
+
...(successorInventoryWrite ? { successor_candidate_inventory_write: successorInventoryWrite } : {}),
|
|
864
|
+
}, receipt, Boolean(input.apply), existingReceipt);
|
|
865
|
+
}
|
|
866
|
+
if (currentLine !== expectedLine) {
|
|
867
|
+
throw new Error(`Development line ${lineBranch} drifted: expected ${expectedLine}, found ${currentLine}. Replan the release-fix synchronization.`);
|
|
868
|
+
}
|
|
869
|
+
if (existingReceipt) {
|
|
870
|
+
throw new Error(`Receipt ${receipt} exists but development line ${lineBranch} has not incorporated the release fix; recover the controlled operation before retrying.`);
|
|
871
|
+
}
|
|
872
|
+
const remoteValidation = assertRemoteBranchHead(input.remote || null, lineBranch, expectedLine);
|
|
873
|
+
if (!input.apply) {
|
|
874
|
+
return attachReceipt({
|
|
875
|
+
...baseReceipt,
|
|
876
|
+
status: "planned",
|
|
877
|
+
old_line_head: currentLine,
|
|
878
|
+
new_line_head: null,
|
|
879
|
+
remote_validation: remoteValidation,
|
|
880
|
+
}, receipt, false);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const holder = targetBranchWorktree(repo, lineBranch);
|
|
884
|
+
if (holder && !samePath(holder.path, repo)) {
|
|
885
|
+
throw new Error(`Development line ${lineBranch} is checked out in another worktree: ${holder.path}`);
|
|
886
|
+
}
|
|
887
|
+
let newLine;
|
|
888
|
+
if (holder) {
|
|
889
|
+
assertExactBranchHead(repo, lineBranch, expectedLine, `Development line ${lineBranch}`);
|
|
890
|
+
git(repo, ["merge", "--no-ff", "--no-edit", `refs/heads/${sourceRef}`]);
|
|
891
|
+
newLine = resolveCommit(repo, "HEAD", "Development line merge");
|
|
892
|
+
} else {
|
|
893
|
+
newLine = withDetachedWorktree(repo, expectedLine, (worktree) => {
|
|
894
|
+
git(worktree, ["merge", "--no-ff", "--no-edit", `refs/heads/${sourceRef}`]);
|
|
895
|
+
return resolveCommit(worktree, "HEAD", "Development line merge");
|
|
896
|
+
});
|
|
897
|
+
assertExactBranchHead(repo, lineBranch, expectedLine, `Development line ${lineBranch}`);
|
|
898
|
+
assertRemoteBranchHead(input.remote || null, lineBranch, expectedLine);
|
|
899
|
+
updateRef(repo, lineBranch, newLine, expectedLine);
|
|
900
|
+
}
|
|
901
|
+
if (!isAncestor(repo, sourceCommit, newLine)) throw new Error("Release-fix synchronization did not retain the release-fix commit.");
|
|
902
|
+
const successorInventoryWrite = writeImmutableDocument(
|
|
903
|
+
successorCandidateInventory,
|
|
904
|
+
successorInventoryPreflight,
|
|
905
|
+
"successor_candidate_inventory",
|
|
906
|
+
);
|
|
907
|
+
return attachReceipt({
|
|
908
|
+
...baseReceipt,
|
|
909
|
+
status: "applied",
|
|
910
|
+
old_line_head: currentLine,
|
|
911
|
+
new_line_head: newLine,
|
|
912
|
+
remote_validation: remoteValidation,
|
|
913
|
+
successor_candidate_inventory_write: successorInventoryWrite,
|
|
914
|
+
}, receipt, true, existingReceipt);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function candidateInput(options) {
|
|
918
|
+
assertKnownOptions(options, new Set([
|
|
919
|
+
"--repo", "--remote", "--receipt", "--apply", "--dry-run", "--release-id", "--release-branch",
|
|
920
|
+
"--production-branch", "--expected-main", "--source-tag", "--source-commit",
|
|
921
|
+
]));
|
|
922
|
+
const shared = commonOptions(options);
|
|
923
|
+
return {
|
|
924
|
+
...shared,
|
|
925
|
+
release_id: required(options, "--release-id"),
|
|
926
|
+
release_branch: required(options, "--release-branch"),
|
|
927
|
+
production_branch: optional(options, "--production-branch") || "main",
|
|
928
|
+
expected_main: required(options, "--expected-main"),
|
|
929
|
+
source_tag: required(options, "--source-tag"),
|
|
930
|
+
source_commit: required(options, "--source-commit"),
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function promotionInput(options) {
|
|
935
|
+
assertKnownOptions(options, new Set([
|
|
936
|
+
"--repo", "--remote", "--receipt", "--apply", "--dry-run", "--release-id", "--release-branch",
|
|
937
|
+
"--production-branch", "--expected-main", "--verified-release-commit", "--production-verification",
|
|
938
|
+
]));
|
|
939
|
+
const shared = commonOptions(options);
|
|
940
|
+
return {
|
|
941
|
+
...shared,
|
|
942
|
+
release_id: required(options, "--release-id"),
|
|
943
|
+
release_branch: required(options, "--release-branch"),
|
|
944
|
+
production_branch: optional(options, "--production-branch") || "main",
|
|
945
|
+
expected_main: required(options, "--expected-main"),
|
|
946
|
+
verified_release_commit: required(options, "--verified-release-commit"),
|
|
947
|
+
production_verification: required(options, "--production-verification"),
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function releaseFixInput(options) {
|
|
952
|
+
assertKnownOptions(options, new Set([
|
|
953
|
+
"--repo", "--remote", "--receipt", "--apply", "--dry-run", "--release-id", "--source-ref",
|
|
954
|
+
"--source-commit", "--verified-release-commit", "--line-branch", "--expected-line",
|
|
955
|
+
"--production-verification", "--candidate-inventory", "--successor-candidate-inventory",
|
|
956
|
+
]));
|
|
957
|
+
const shared = commonOptions(options);
|
|
958
|
+
return {
|
|
959
|
+
...shared,
|
|
960
|
+
release_id: required(options, "--release-id"),
|
|
961
|
+
source_ref: required(options, "--source-ref"),
|
|
962
|
+
source_commit: required(options, "--source-commit"),
|
|
963
|
+
verified_release_commit: required(options, "--verified-release-commit"),
|
|
964
|
+
line_branch: required(options, "--line-branch"),
|
|
965
|
+
expected_line: required(options, "--expected-line"),
|
|
966
|
+
production_verification: required(options, "--production-verification"),
|
|
967
|
+
candidate_inventory: required(options, "--candidate-inventory"),
|
|
968
|
+
successor_candidate_inventory: shared.apply
|
|
969
|
+
? required(options, "--successor-candidate-inventory")
|
|
970
|
+
: optional(options, "--successor-candidate-inventory"),
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function main(argv) {
|
|
975
|
+
const [action, ...rest] = argv;
|
|
976
|
+
const options = parseArgs(rest);
|
|
977
|
+
let result;
|
|
978
|
+
if (action === "create-candidate") result = createReleaseCandidate(candidateInput(options));
|
|
979
|
+
else if (action === "promote") result = promoteReleaseCandidate(promotionInput(options));
|
|
980
|
+
else if (action === "sync-release-fix") result = synchronizeReleaseFix(releaseFixInput(options));
|
|
981
|
+
else {
|
|
982
|
+
throw new Error("Usage: release-git.mjs <create-candidate|promote|sync-release-fix> [options] [--apply]");
|
|
983
|
+
}
|
|
984
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
988
|
+
if (isMain) {
|
|
989
|
+
try {
|
|
990
|
+
main(process.argv.slice(2));
|
|
991
|
+
} catch (error) {
|
|
992
|
+
process.stderr.write(`${error.message}\n`);
|
|
993
|
+
process.exitCode = 1;
|
|
994
|
+
}
|
|
995
|
+
}
|