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
|
@@ -5,8 +5,12 @@ import path from "node:path";
|
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { isSeq, parse as parseYaml, parseDocument } from "yaml";
|
|
9
|
-
import {
|
|
8
|
+
import { isSeq, parse as parseYaml, parseDocument, stringify as stringifyYaml } from "yaml";
|
|
9
|
+
import { assertPathInside, atomicWrite, filesUnder } from "./fs-utils.mjs";
|
|
10
|
+
import { inspectCodeGraph } from "./codegraph.mjs";
|
|
11
|
+
import { executeDeliveryStateCommand } from "./delivery-state.mjs";
|
|
12
|
+
import { planPrototypeMigration } from "./prototype-migration.mjs";
|
|
13
|
+
import { inspectHookConfig, managedHookConfigProjection, mergeHookConfig } from "./toml-hooks.mjs";
|
|
10
14
|
import {
|
|
11
15
|
LEGACY_WORKSPACE_CONFIG,
|
|
12
16
|
WORKSPACE_CONFIG,
|
|
@@ -22,6 +26,10 @@ const PACKAGE_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
|
22
26
|
const MANAGED_START = "<!-- ai-delivery:start -->";
|
|
23
27
|
const MANAGED_END = "<!-- ai-delivery:end -->";
|
|
24
28
|
const IGNORE_ENTRIES = [".workflow/delivery/runtime/"];
|
|
29
|
+
const RAW_GIT_ATTRIBUTE_TARGETS = Object.freeze([
|
|
30
|
+
"ref/ui/README.md",
|
|
31
|
+
"ref/ui/LICENSE",
|
|
32
|
+
]);
|
|
25
33
|
const GIT_ATTRIBUTE_ENTRIES = [
|
|
26
34
|
"/.workflow/** text=auto eol=lf",
|
|
27
35
|
"/.agents/skills/ai-delivery-*/** text=auto eol=lf",
|
|
@@ -29,13 +37,43 @@ const GIT_ATTRIBUTE_ENTRIES = [
|
|
|
29
37
|
"/AGENTS.md text eol=lf",
|
|
30
38
|
"/.gitignore text eol=lf",
|
|
31
39
|
"/.gitattributes text eol=lf",
|
|
40
|
+
...RAW_GIT_ATTRIBUTE_TARGETS.map((file) => `/${file} -text`),
|
|
32
41
|
];
|
|
33
42
|
const OBSOLETE_IGNORE_ENTRIES = new Set([".ai-delivery-task", ".ai-delivery/backups/"]);
|
|
34
43
|
const INSTALL_MANIFEST = path.join(".workflow", "config", "install-manifest.json");
|
|
44
|
+
const UPGRADE_RUNTIME = path.join(".workflow", "delivery", "runtime", "upgrades");
|
|
45
|
+
const PRESERVED_DETAIL_LIMIT = 200;
|
|
46
|
+
const PRESERVED_TRANSIENT_ROOTS = Object.freeze([".workflow/delivery/runtime"]);
|
|
47
|
+
const DOCTOR_ATTRIBUTE_BATCH_SIZE = 5_000;
|
|
48
|
+
const DOCTOR_DETAIL_LIMIT = 200;
|
|
49
|
+
const FORMAL_STATE_INSPECT_MAX_BUFFER = 16 * 1024 * 1024;
|
|
50
|
+
const INSTALL_MANIFEST_SCHEMA = 6;
|
|
51
|
+
const UI_CONTRACT_VERSION = "prototype-first-ui-v1";
|
|
52
|
+
const FORMAL_STATE_YAML = {
|
|
53
|
+
parse: parseYaml,
|
|
54
|
+
stringify: (value) => stringifyYaml(value, { lineWidth: 0 }),
|
|
55
|
+
};
|
|
56
|
+
const MERGED_MANAGED_SURFACES = Object.freeze([
|
|
57
|
+
{ key: "agents", path: "AGENTS.md" },
|
|
58
|
+
{ key: "hook_config", path: ".codex/config.toml" },
|
|
59
|
+
{ key: "gitignore", path: ".gitignore" },
|
|
60
|
+
{ key: "gitattributes", path: ".gitattributes" },
|
|
61
|
+
]);
|
|
35
62
|
const ACTIVE_TASK = path.join(".workflow", "delivery", "runtime", "active-task.json");
|
|
36
63
|
const LEGACY_ACTIVE_TASK = ".ai-delivery-task";
|
|
37
64
|
const WORKFLOW_CONTENT_DIRECTORIES = ["product", "architecture", "experience", "iterations", "releases"];
|
|
38
|
-
const CONTROL_DIRECTORIES = [
|
|
65
|
+
const CONTROL_DIRECTORIES = [
|
|
66
|
+
"tasks", "checkpoints", "artifacts", "gates", "versions", "releases", "events", "archives",
|
|
67
|
+
"migrations", "repairs",
|
|
68
|
+
];
|
|
69
|
+
const ENSURED_CONTROL_DIRECTORIES = ["migrations", "repairs"];
|
|
70
|
+
const RETIRED_MANAGED_ASSETS = Object.freeze([
|
|
71
|
+
{
|
|
72
|
+
path: ".workflow/tools/viewer",
|
|
73
|
+
kind: "retired-directory",
|
|
74
|
+
manifest_key: "viewer",
|
|
75
|
+
},
|
|
76
|
+
]);
|
|
39
77
|
const ESSENTIAL_STARTUP_SKILLS = [
|
|
40
78
|
"ai-delivery-assemble-release",
|
|
41
79
|
"ai-delivery-bootstrap",
|
|
@@ -45,6 +83,79 @@ const ESSENTIAL_STARTUP_SKILLS = [
|
|
|
45
83
|
"ai-delivery-orchestrate",
|
|
46
84
|
"ai-delivery-orchestrate-release",
|
|
47
85
|
];
|
|
86
|
+
const WORKFLOW_MANAGER_LAUNCHER_FILES = Object.freeze([
|
|
87
|
+
"manager.mjs",
|
|
88
|
+
"start.cmd",
|
|
89
|
+
"start.ps1",
|
|
90
|
+
"start.sh",
|
|
91
|
+
]);
|
|
92
|
+
const WORKFLOW_MANAGER_RUNTIME_MODULES = Object.freeze([
|
|
93
|
+
"workflow-manager-facts.mjs",
|
|
94
|
+
"workflow-manager-contracts.mjs",
|
|
95
|
+
"workflow-manager-transaction.mjs",
|
|
96
|
+
"workflow-manager-systems.mjs",
|
|
97
|
+
"workflow-manager-mock.mjs",
|
|
98
|
+
"workflow-manager-prototypes.mjs",
|
|
99
|
+
"workflow-manager-governance.mjs",
|
|
100
|
+
]);
|
|
101
|
+
const PROTOTYPE_EVIDENCE_RUNTIME_MODULES = Object.freeze([
|
|
102
|
+
"prototype-review-target.mjs",
|
|
103
|
+
"prototype-page-review.mjs",
|
|
104
|
+
"prototype-revision.mjs",
|
|
105
|
+
"visual-standard.mjs",
|
|
106
|
+
"artifact-files.mjs",
|
|
107
|
+
"yaml-runtime.mjs",
|
|
108
|
+
]);
|
|
109
|
+
const V2_PROTOTYPE_CATALOG_TEMPLATE_FILES = Object.freeze([
|
|
110
|
+
"prototype-terminals.csv",
|
|
111
|
+
"prototype-pages.csv",
|
|
112
|
+
"prototype-review-scenes.csv",
|
|
113
|
+
"prototype-trigger-conditions.csv",
|
|
114
|
+
"prototype-routes.csv",
|
|
115
|
+
"ui-mapping.csv",
|
|
116
|
+
"prototype-review-units.csv",
|
|
117
|
+
"prototype-review-unit-pages.csv",
|
|
118
|
+
"prototype-components.csv",
|
|
119
|
+
"prototype-data-contracts.csv",
|
|
120
|
+
"prototype-component-candidates.csv",
|
|
121
|
+
"prototype-component-bindings.csv",
|
|
122
|
+
"prototype-implementation-bindings.csv",
|
|
123
|
+
"prototype-source-inspection-snapshots.csv",
|
|
124
|
+
]);
|
|
125
|
+
const EXPERIENCE_TEMPLATE_ROOT = "skills/ai-delivery-design-experience/assets/experience-template";
|
|
126
|
+
const V2_PROTOTYPE_DELIVERY_SURFACE_FILES = Object.freeze([
|
|
127
|
+
"docs/agents/workflow-manager-agent-interface.md",
|
|
128
|
+
"lib/workflow-manager.mjs",
|
|
129
|
+
"lib/workflow-manager-public/index.html",
|
|
130
|
+
"skills/ai-delivery-design-experience/SKILL.md",
|
|
131
|
+
...[...WORKFLOW_MANAGER_RUNTIME_MODULES, ...PROTOTYPE_EVIDENCE_RUNTIME_MODULES]
|
|
132
|
+
.map((name) => `lib/${name}`),
|
|
133
|
+
...WORKFLOW_MANAGER_LAUNCHER_FILES
|
|
134
|
+
.map((name) => `skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/manager/${name}`),
|
|
135
|
+
...["prototype.yaml", "prototype-revision.yaml", "frontend-change-scope.yaml", ...V2_PROTOTYPE_CATALOG_TEMPLATE_FILES]
|
|
136
|
+
.map((name) => `${EXPERIENCE_TEMPLATE_ROOT}/${name}`),
|
|
137
|
+
]);
|
|
138
|
+
const REQUIRED_PACKAGE_SURFACE_FILES = Object.freeze([
|
|
139
|
+
"LICENSE",
|
|
140
|
+
"bin/ai-delivery.mjs",
|
|
141
|
+
"docs/PROTOTYPE-MIGRATION.zh-CN.md",
|
|
142
|
+
"lib/mock-scenario-pack.mjs",
|
|
143
|
+
"lib/mock-system.mjs",
|
|
144
|
+
"lib/project-migration.mjs",
|
|
145
|
+
"lib/prototype-migration.mjs",
|
|
146
|
+
"lib/prototype-page-review.mjs",
|
|
147
|
+
"lib/prototype-review-target.mjs",
|
|
148
|
+
"skills/ai-delivery-checkpoint-task/scripts/hook-event.mjs",
|
|
149
|
+
"skills/ai-delivery-orchestrate/SKILL.md",
|
|
150
|
+
"skills/ai-delivery-orchestrate/assets/project-template/AGENTS.md",
|
|
151
|
+
"skills/ai-delivery-orchestrate/assets/project-template/gitignore.template",
|
|
152
|
+
"skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/mock/mock.mjs",
|
|
153
|
+
"skills/ai-delivery-orchestrate/assets/project-template/ref/ui/README.md",
|
|
154
|
+
"skills/ai-delivery-orchestrate/assets/project-template/ref/ui/LICENSE",
|
|
155
|
+
"skills/ai-delivery-orchestrate/assets/project-template/ref/ui/UPSTREAM.yaml",
|
|
156
|
+
"skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/linear.app/DESIGN.md",
|
|
157
|
+
...V2_PROTOTYPE_DELIVERY_SURFACE_FILES,
|
|
158
|
+
]);
|
|
48
159
|
const DELIVERY_SKILLS = Object.freeze([
|
|
49
160
|
"ai-delivery-assemble-release",
|
|
50
161
|
"ai-delivery-bootstrap",
|
|
@@ -132,40 +243,13 @@ function validateTarget(target, options = {}) {
|
|
|
132
243
|
}
|
|
133
244
|
|
|
134
245
|
function validateManagedPath(target, destination) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (parent === existingAncestor) break;
|
|
140
|
-
existingAncestor = parent;
|
|
141
|
-
}
|
|
142
|
-
const realAncestor = fs.realpathSync(existingAncestor);
|
|
143
|
-
if (!isInside(realAncestor, realTarget)) {
|
|
144
|
-
throw new Error(`Managed path resolves outside the project: ${destination} -> ${realAncestor}`);
|
|
246
|
+
try {
|
|
247
|
+
assertPathInside(target, destination);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
throw new Error(`Managed path resolves outside the project: ${error.message}`);
|
|
145
250
|
}
|
|
146
251
|
}
|
|
147
252
|
|
|
148
|
-
function atomicWrite(file, content) {
|
|
149
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
150
|
-
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
151
|
-
fs.writeFileSync(temp, content, "utf8");
|
|
152
|
-
fs.renameSync(temp, file);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function filesUnder(root) {
|
|
156
|
-
if (!fs.existsSync(root)) return [];
|
|
157
|
-
const files = [];
|
|
158
|
-
const visit = (directory) => {
|
|
159
|
-
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
160
|
-
const absolute = path.join(directory, entry.name);
|
|
161
|
-
if (entry.isDirectory()) visit(absolute);
|
|
162
|
-
else if (entry.isFile()) files.push(absolute);
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
visit(root);
|
|
166
|
-
return files.sort((left, right) => left.localeCompare(right));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
253
|
function normalizedManagedContent(file) {
|
|
170
254
|
const content = fs.readFileSync(file);
|
|
171
255
|
return content.includes(0) ? content : Buffer.from(content.toString("utf8").replaceAll("\r\n", "\n"));
|
|
@@ -178,7 +262,8 @@ function managedFilesEqual(left, right) {
|
|
|
178
262
|
function directoryDigest(root) {
|
|
179
263
|
const hash = crypto.createHash("sha256");
|
|
180
264
|
for (const file of filesUnder(root)) {
|
|
181
|
-
|
|
265
|
+
const relative = path.relative(root, file).replaceAll("\\", "/");
|
|
266
|
+
hash.update(relative);
|
|
182
267
|
hash.update("\0");
|
|
183
268
|
hash.update(normalizedManagedContent(file));
|
|
184
269
|
hash.update("\0");
|
|
@@ -190,6 +275,39 @@ function managedFileDigest(file) {
|
|
|
190
275
|
return crypto.createHash("sha256").update(normalizedManagedContent(file)).digest("hex");
|
|
191
276
|
}
|
|
192
277
|
|
|
278
|
+
function contentDigest(content) {
|
|
279
|
+
const value = Buffer.isBuffer(content) ? content : Buffer.from(String(content).replaceAll("\r\n", "\n"));
|
|
280
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function pathDigest(target) {
|
|
284
|
+
if (!fs.existsSync(target)) return null;
|
|
285
|
+
if (fs.statSync(target).isDirectory()) return directoryDigest(target);
|
|
286
|
+
if (fs.statSync(target).isFile()) return managedFileDigest(target);
|
|
287
|
+
throw new Error(`Managed asset is not a regular file or directory: ${target}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function exactTreeDigest(root) {
|
|
291
|
+
if (!fs.existsSync(root)) return null;
|
|
292
|
+
const hash = crypto.createHash("sha256");
|
|
293
|
+
for (const file of filesUnder(root)) {
|
|
294
|
+
hash.update(path.relative(root, file).replaceAll("\\", "/"));
|
|
295
|
+
hash.update("\0");
|
|
296
|
+
hash.update(fs.readFileSync(file));
|
|
297
|
+
hash.update("\0");
|
|
298
|
+
}
|
|
299
|
+
return hash.digest("hex");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function exactPathDigest(target) {
|
|
303
|
+
if (!fs.existsSync(target)) return null;
|
|
304
|
+
if (fs.statSync(target).isDirectory()) return exactTreeDigest(target);
|
|
305
|
+
if (fs.statSync(target).isFile()) {
|
|
306
|
+
return crypto.createHash("sha256").update(fs.readFileSync(target)).digest("hex");
|
|
307
|
+
}
|
|
308
|
+
throw new Error(`Managed asset is not a regular file or directory: ${target}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
193
311
|
function sourceSetDigest(sources) {
|
|
194
312
|
const hash = crypto.createHash("sha256");
|
|
195
313
|
for (const item of [...sources].sort((left, right) => left.relative.localeCompare(right.relative))) {
|
|
@@ -324,6 +442,14 @@ function stateRuntimeSources(packageRoot, sourceTemplate) {
|
|
|
324
442
|
source: path.join(packageRoot, "lib", "delivery-state.mjs"),
|
|
325
443
|
relative: "delivery-state.mjs",
|
|
326
444
|
},
|
|
445
|
+
{
|
|
446
|
+
source: path.join(packageRoot, "lib", "history-anchor.mjs"),
|
|
447
|
+
relative: "history-anchor.mjs",
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
source: path.join(packageRoot, "lib", "fs-utils.mjs"),
|
|
451
|
+
relative: "fs-utils.mjs",
|
|
452
|
+
},
|
|
327
453
|
...filesUnder(yamlRoot).map((source) => ({
|
|
328
454
|
source,
|
|
329
455
|
relative: path.join("vendor", "yaml", path.relative(yamlRoot, source)),
|
|
@@ -344,19 +470,341 @@ function evolutionRuntimeSources(packageRoot, sourceTemplate) {
|
|
|
344
470
|
];
|
|
345
471
|
}
|
|
346
472
|
|
|
473
|
+
function mockRuntimeSources(packageRoot, sourceTemplate) {
|
|
474
|
+
return [
|
|
475
|
+
{
|
|
476
|
+
source: path.join(sourceTemplate, ".workflow", "tools", "mock", "mock.mjs"),
|
|
477
|
+
relative: "mock.mjs",
|
|
478
|
+
},
|
|
479
|
+
...[
|
|
480
|
+
"mock-system.mjs",
|
|
481
|
+
"mock-scenario-pack.mjs",
|
|
482
|
+
"prototype-revision.mjs",
|
|
483
|
+
"artifact-files.mjs",
|
|
484
|
+
"workspace.mjs",
|
|
485
|
+
"yaml-runtime.mjs",
|
|
486
|
+
"fs-utils.mjs",
|
|
487
|
+
"stable-value.mjs",
|
|
488
|
+
].map((name) => ({
|
|
489
|
+
source: path.join(packageRoot, "lib", name),
|
|
490
|
+
relative: name,
|
|
491
|
+
})),
|
|
492
|
+
];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function packagedRuntimeModules(packageRoot, modules) {
|
|
496
|
+
return modules.map((name) => ({
|
|
497
|
+
source: path.join(packageRoot, "lib", name),
|
|
498
|
+
relative: name,
|
|
499
|
+
}));
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function frontendGuardRuntimeSources(packageRoot, sourceTemplate) {
|
|
503
|
+
return [
|
|
504
|
+
{
|
|
505
|
+
source: path.join(sourceTemplate, ".workflow", "tools", "frontend-guard", "frontend-guard.mjs"),
|
|
506
|
+
relative: "frontend-guard.mjs",
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
source: path.join(packageRoot, "lib", "frontend-guard.mjs"),
|
|
510
|
+
relative: "frontend-guard-runtime.mjs",
|
|
511
|
+
},
|
|
512
|
+
...packagedRuntimeModules(packageRoot, PROTOTYPE_EVIDENCE_RUNTIME_MODULES),
|
|
513
|
+
];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function uiAcceptanceRuntimeSources(packageRoot, sourceTemplate) {
|
|
517
|
+
return [
|
|
518
|
+
{
|
|
519
|
+
source: path.join(sourceTemplate, ".workflow", "tools", "ui-acceptance", "ui-acceptance.mjs"),
|
|
520
|
+
relative: "ui-acceptance.mjs",
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
source: path.join(packageRoot, "lib", "ui-acceptance.mjs"),
|
|
524
|
+
relative: "ui-acceptance-runtime.mjs",
|
|
525
|
+
},
|
|
526
|
+
...packagedRuntimeModules(packageRoot, [
|
|
527
|
+
...PROTOTYPE_EVIDENCE_RUNTIME_MODULES,
|
|
528
|
+
"mock-scenario-pack.mjs",
|
|
529
|
+
"mock-system.mjs",
|
|
530
|
+
"png-evidence.mjs",
|
|
531
|
+
"fs-utils.mjs",
|
|
532
|
+
"workspace.mjs",
|
|
533
|
+
"stable-value.mjs",
|
|
534
|
+
]),
|
|
535
|
+
];
|
|
536
|
+
}
|
|
537
|
+
|
|
347
538
|
function bootstrapRuntimeSources(packageRoot, sourceTemplate) {
|
|
348
539
|
return [
|
|
349
540
|
{
|
|
350
541
|
source: path.join(sourceTemplate, ".workflow", "tools", "bootstrap", "bootstrap.mjs"),
|
|
351
542
|
relative: "bootstrap.mjs",
|
|
352
543
|
},
|
|
353
|
-
...[
|
|
544
|
+
...[
|
|
545
|
+
"project-bootstrap.mjs", "workspace.mjs", "yaml-runtime.mjs", "delivery-state.mjs",
|
|
546
|
+
"history-anchor.mjs", "fs-utils.mjs",
|
|
547
|
+
].map((name) => ({
|
|
548
|
+
source: path.join(packageRoot, "lib", name),
|
|
549
|
+
relative: name,
|
|
550
|
+
})),
|
|
551
|
+
];
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function managerRuntimeSources(packageRoot, sourceTemplate) {
|
|
555
|
+
const publicRoot = path.join(packageRoot, "lib", "workflow-manager-public");
|
|
556
|
+
return [
|
|
557
|
+
{
|
|
558
|
+
source: path.join(sourceTemplate, ".workflow", "tools", "manager", "manager.mjs"),
|
|
559
|
+
relative: "manager.mjs",
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
source: path.join(packageRoot, "docs", "agents", "workflow-manager-agent-interface.md"),
|
|
563
|
+
relative: "AGENT-INTERFACE.md",
|
|
564
|
+
},
|
|
565
|
+
...WORKFLOW_MANAGER_LAUNCHER_FILES.filter((name) => name !== "manager.mjs").map((name) => ({
|
|
566
|
+
source: path.join(sourceTemplate, ".workflow", "tools", "manager", name),
|
|
567
|
+
relative: name,
|
|
568
|
+
})),
|
|
569
|
+
{
|
|
570
|
+
source: path.join(packageRoot, "lib", "workflow-manager.mjs"),
|
|
571
|
+
relative: "workflow-manager-runtime.mjs",
|
|
572
|
+
},
|
|
573
|
+
...WORKFLOW_MANAGER_RUNTIME_MODULES.map((name) => ({
|
|
354
574
|
source: path.join(packageRoot, "lib", name),
|
|
355
575
|
relative: name,
|
|
356
576
|
})),
|
|
577
|
+
...packagedRuntimeModules(packageRoot, PROTOTYPE_EVIDENCE_RUNTIME_MODULES),
|
|
578
|
+
...filesUnder(publicRoot).map((source) => ({
|
|
579
|
+
source,
|
|
580
|
+
relative: path.join("public", path.relative(publicRoot, source)),
|
|
581
|
+
})),
|
|
357
582
|
];
|
|
358
583
|
}
|
|
359
584
|
|
|
585
|
+
function installationSources(packageRoot) {
|
|
586
|
+
const sourceSkills = path.join(packageRoot, "skills");
|
|
587
|
+
const sourceTemplate = path.join(sourceSkills, "ai-delivery-orchestrate", "assets", "project-template");
|
|
588
|
+
const sourceWorkflow = path.join(sourceTemplate, ".workflow");
|
|
589
|
+
const sourceDesignReferenceLibrary = path.join(sourceTemplate, "ref", "ui");
|
|
590
|
+
const gitignoreTemplate = path.join(sourceTemplate, "gitignore.template");
|
|
591
|
+
if (!fs.existsSync(gitignoreTemplate) || !fs.statSync(gitignoreTemplate).isFile()) {
|
|
592
|
+
throw new Error("Project template is missing gitignore.template.");
|
|
593
|
+
}
|
|
594
|
+
const expectedGitignoreTemplate = `${IGNORE_ENTRIES.join("\n")}\n`;
|
|
595
|
+
if (fs.readFileSync(gitignoreTemplate, "utf8").replaceAll("\r\n", "\n")
|
|
596
|
+
!== expectedGitignoreTemplate) {
|
|
597
|
+
throw new Error("Project gitignore.template does not match the managed runtime ignore contract.");
|
|
598
|
+
}
|
|
599
|
+
validateDesignReferenceLibrary(sourceDesignReferenceLibrary);
|
|
600
|
+
const skillNames = packagedSkillNames(packageRoot);
|
|
601
|
+
return {
|
|
602
|
+
sourceSkills,
|
|
603
|
+
sourceTemplate,
|
|
604
|
+
sourceWorkflow,
|
|
605
|
+
skillNames,
|
|
606
|
+
sourceHook: path.join(sourceSkills, "ai-delivery-checkpoint-task", "scripts", "hook-event.mjs"),
|
|
607
|
+
sourceHookConfig: path.join(sourceSkills, "ai-delivery-checkpoint-task", "assets", "codex-hook-config.toml"),
|
|
608
|
+
sourceDesignReferenceLibrary,
|
|
609
|
+
bootstrapSources: bootstrapRuntimeSources(packageRoot, sourceTemplate),
|
|
610
|
+
stateSources: stateRuntimeSources(packageRoot, sourceTemplate),
|
|
611
|
+
evolutionSources: evolutionRuntimeSources(packageRoot, sourceTemplate),
|
|
612
|
+
mockSources: mockRuntimeSources(packageRoot, sourceTemplate),
|
|
613
|
+
frontendGuardSources: frontendGuardRuntimeSources(packageRoot, sourceTemplate),
|
|
614
|
+
uiAcceptanceSources: uiAcceptanceRuntimeSources(packageRoot, sourceTemplate),
|
|
615
|
+
managerSources: managerRuntimeSources(packageRoot, sourceTemplate),
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function validateDesignReferenceLibrary(source) {
|
|
620
|
+
for (const relative of ["README.md", "LICENSE", "UPSTREAM.yaml"]) {
|
|
621
|
+
if (!fs.existsSync(path.join(source, relative))) {
|
|
622
|
+
throw new Error(`Design Reference Library is missing required ${relative}.`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const upstream = parseYaml(fs.readFileSync(path.join(source, "UPSTREAM.yaml"), "utf8"));
|
|
626
|
+
if (!/^[a-f0-9]{40}$/.test(String(upstream?.source_commit || ""))) {
|
|
627
|
+
throw new Error("Design Reference Library UPSTREAM.yaml must pin a full source commit.");
|
|
628
|
+
}
|
|
629
|
+
if (upstream?.license !== "MIT" || upstream?.license_ref !== "LICENSE") {
|
|
630
|
+
throw new Error("Design Reference Library must declare its MIT license and LICENSE path.");
|
|
631
|
+
}
|
|
632
|
+
if (!/^MIT License\b/.test(fs.readFileSync(path.join(source, "LICENSE"), "utf8"))) {
|
|
633
|
+
throw new Error("Design Reference Library LICENSE content is missing or invalid.");
|
|
634
|
+
}
|
|
635
|
+
for (const [relative, checksumField] of [
|
|
636
|
+
["README.md", "source_readme_sha256"],
|
|
637
|
+
["LICENSE", "license_sha256"],
|
|
638
|
+
]) {
|
|
639
|
+
const expected = String(upstream?.[checksumField] || "");
|
|
640
|
+
const actual = crypto.createHash("sha256").update(fs.readFileSync(path.join(source, relative))).digest("hex");
|
|
641
|
+
if (!/^[a-f0-9]{64}$/.test(expected) || actual !== expected) {
|
|
642
|
+
throw new Error(`Design Reference Library ${relative} checksum does not match UPSTREAM.yaml.`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
const references = filesUnder(path.join(source, "design-md"))
|
|
646
|
+
.filter((file) => path.basename(file) === "DESIGN.md");
|
|
647
|
+
if (references.length < 2) {
|
|
648
|
+
throw new Error("Design Reference Library must contain selectable DESIGN.md references.");
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function designReferenceDoctorCheck(target, packageSource, manifest) {
|
|
653
|
+
const installed = path.join(target, "ref", "ui");
|
|
654
|
+
try {
|
|
655
|
+
validateDesignReferenceLibrary(packageSource);
|
|
656
|
+
validateDesignReferenceLibrary(installed);
|
|
657
|
+
const expectedDigest = directoryDigest(packageSource);
|
|
658
|
+
const installedDigest = directoryDigest(installed);
|
|
659
|
+
const manifestDigest = manifest?.managed_assets?.design_reference_library?.sha256;
|
|
660
|
+
const ok = installedDigest === expectedDigest && manifestDigest === expectedDigest;
|
|
661
|
+
return {
|
|
662
|
+
ok,
|
|
663
|
+
scope: "design-reference-library",
|
|
664
|
+
message: ok
|
|
665
|
+
? "Design Reference Library integrity and raw attribution checksums match the package baseline"
|
|
666
|
+
: "Design Reference Library integrity does not match the package baseline",
|
|
667
|
+
};
|
|
668
|
+
} catch (error) {
|
|
669
|
+
return {
|
|
670
|
+
ok: false,
|
|
671
|
+
scope: "design-reference-library",
|
|
672
|
+
message: `Design Reference Library integrity check failed: ${error.message}`,
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function packageSurfaceDoctorCheck(packageRoot) {
|
|
678
|
+
const missing = [];
|
|
679
|
+
const empty = [];
|
|
680
|
+
for (const relative of REQUIRED_PACKAGE_SURFACE_FILES) {
|
|
681
|
+
const file = path.join(packageRoot, ...relative.split("/"));
|
|
682
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) missing.push(relative);
|
|
683
|
+
else if (fs.statSync(file).size === 0) empty.push(relative);
|
|
684
|
+
}
|
|
685
|
+
const ok = missing.length === 0 && empty.length === 0;
|
|
686
|
+
return {
|
|
687
|
+
ok,
|
|
688
|
+
scope: "package-surface",
|
|
689
|
+
message: ok
|
|
690
|
+
? `release package surface contains all ${REQUIRED_PACKAGE_SURFACE_FILES.length} required files`
|
|
691
|
+
: `release package surface is incomplete: missing=${missing.join(", ") || "none"}; empty=${empty.join(", ") || "none"}`,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function managedMarkdownContribution(text) {
|
|
696
|
+
const start = text.indexOf(MANAGED_START);
|
|
697
|
+
const end = text.indexOf(MANAGED_END);
|
|
698
|
+
if (start === -1 || end === -1 || end < start) {
|
|
699
|
+
throw new Error("AGENTS.md does not contain a complete ai-delivery managed block.");
|
|
700
|
+
}
|
|
701
|
+
return text.slice(start, end + MANAGED_END.length);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function mergedSurfaceManagedDigest(key, text, codePath) {
|
|
705
|
+
if (key === "agents") return contentDigest(managedMarkdownContribution(text));
|
|
706
|
+
if (key === "hook_config") return contentDigest(JSON.stringify(managedHookConfigProjection(text)));
|
|
707
|
+
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
708
|
+
if (key === "gitignore") {
|
|
709
|
+
const managedEntries = new Set([...IGNORE_ENTRIES, `/${codePath}/`, ...OBSOLETE_IGNORE_ENTRIES]);
|
|
710
|
+
return contentDigest(JSON.stringify(lines.filter((line) => managedEntries.has(line)).sort()));
|
|
711
|
+
}
|
|
712
|
+
return contentDigest(JSON.stringify(lines.filter((line) => GIT_ATTRIBUTE_ENTRIES.includes(line)).sort()));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function expectedMergedSurfaceText(surface, sources, codePath) {
|
|
716
|
+
if (surface.key === "agents") {
|
|
717
|
+
const template = fs.readFileSync(path.join(sources.sourceTemplate, "AGENTS.md"), "utf8");
|
|
718
|
+
return mergeManagedMarkdown("", template);
|
|
719
|
+
}
|
|
720
|
+
if (surface.key === "hook_config") return fs.readFileSync(sources.sourceHookConfig, "utf8");
|
|
721
|
+
if (surface.key === "gitignore") return mergeGitignore("", [`/${codePath}/`]);
|
|
722
|
+
return mergeGitattributes("");
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function buildInstallManifest(sources, packageVersion, codeRepository, previousManifest = null) {
|
|
726
|
+
const inventory = managedAssetInventory(sources, previousManifest);
|
|
727
|
+
const named = Object.fromEntries(
|
|
728
|
+
inventory.filter((asset) => asset.manifest_key).map((asset) => [asset.manifest_key, asset]),
|
|
729
|
+
);
|
|
730
|
+
return {
|
|
731
|
+
schema_version: INSTALL_MANIFEST_SCHEMA,
|
|
732
|
+
package: "ai-delivery-workflow",
|
|
733
|
+
version: packageVersion,
|
|
734
|
+
scope: "workspace",
|
|
735
|
+
skill_root: ".agents/skills",
|
|
736
|
+
workflow_root: ".workflow",
|
|
737
|
+
bootstrap_cli: ".workflow/tools/bootstrap/bootstrap.mjs",
|
|
738
|
+
state_cli: ".workflow/tools/state/state.mjs",
|
|
739
|
+
evolution_cli: ".workflow/tools/evolution/evolve.mjs",
|
|
740
|
+
mock_cli: ".workflow/tools/mock/mock.mjs",
|
|
741
|
+
frontend_guard_cli: ".workflow/tools/frontend-guard/frontend-guard.mjs",
|
|
742
|
+
ui_acceptance_cli: ".workflow/tools/ui-acceptance/ui-acceptance.mjs",
|
|
743
|
+
manager_root: ".workflow/tools/manager",
|
|
744
|
+
manager_cli: ".workflow/tools/manager/manager.mjs",
|
|
745
|
+
hook_protocol_version: 1,
|
|
746
|
+
hook_cli: ".workflow/tools/hooks/hook-event.mjs",
|
|
747
|
+
code_repository: codeRepository,
|
|
748
|
+
manager_start: {
|
|
749
|
+
windows_cmd: ".workflow/tools/manager/start.cmd",
|
|
750
|
+
powershell: ".workflow/tools/manager/start.ps1",
|
|
751
|
+
unix: ".workflow/tools/manager/start.sh",
|
|
752
|
+
},
|
|
753
|
+
installed_skills: sources.skillNames,
|
|
754
|
+
managed_assets: {
|
|
755
|
+
skills: Object.fromEntries(inventory
|
|
756
|
+
.filter((asset) => asset.skill_name)
|
|
757
|
+
.map((asset) => [asset.skill_name, asset.expected_digest])),
|
|
758
|
+
hook: {
|
|
759
|
+
path: named.hook.path,
|
|
760
|
+
sha256: named.hook.expected_digest,
|
|
761
|
+
},
|
|
762
|
+
state_runtime: {
|
|
763
|
+
path: named.state_runtime.path,
|
|
764
|
+
sha256: named.state_runtime.expected_digest,
|
|
765
|
+
},
|
|
766
|
+
evolution_runtime: {
|
|
767
|
+
path: named.evolution_runtime.path,
|
|
768
|
+
sha256: named.evolution_runtime.expected_digest,
|
|
769
|
+
},
|
|
770
|
+
mock_runtime: {
|
|
771
|
+
path: named.mock_runtime.path,
|
|
772
|
+
sha256: named.mock_runtime.expected_digest,
|
|
773
|
+
},
|
|
774
|
+
frontend_guard_runtime: {
|
|
775
|
+
path: named.frontend_guard_runtime.path,
|
|
776
|
+
sha256: named.frontend_guard_runtime.expected_digest,
|
|
777
|
+
},
|
|
778
|
+
ui_acceptance_runtime: {
|
|
779
|
+
path: named.ui_acceptance_runtime.path,
|
|
780
|
+
sha256: named.ui_acceptance_runtime.expected_digest,
|
|
781
|
+
},
|
|
782
|
+
manager_runtime: {
|
|
783
|
+
path: named.manager_runtime.path,
|
|
784
|
+
sha256: named.manager_runtime.expected_digest,
|
|
785
|
+
},
|
|
786
|
+
bootstrap_runtime: {
|
|
787
|
+
path: named.bootstrap_runtime.path,
|
|
788
|
+
sha256: named.bootstrap_runtime.expected_digest,
|
|
789
|
+
},
|
|
790
|
+
design_reference_library: {
|
|
791
|
+
path: named.design_reference_library.path,
|
|
792
|
+
sha256: named.design_reference_library.expected_digest,
|
|
793
|
+
},
|
|
794
|
+
merged_surfaces: Object.fromEntries(
|
|
795
|
+
MERGED_MANAGED_SURFACES.map((surface) => {
|
|
796
|
+
const expected = expectedMergedSurfaceText(surface, sources, codeRepository.path);
|
|
797
|
+
return [surface.key, {
|
|
798
|
+
path: surface.path,
|
|
799
|
+
sha256: mergedSurfaceManagedDigest(surface.key, expected, codeRepository.path),
|
|
800
|
+
}];
|
|
801
|
+
}),
|
|
802
|
+
),
|
|
803
|
+
},
|
|
804
|
+
installed_at: previousManifest?.installed_at || new Date().toISOString(),
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
|
|
360
808
|
function managedRuntimeMatches(destination, sources) {
|
|
361
809
|
if (!fs.existsSync(destination)) return false;
|
|
362
810
|
const expected = new Map(sources.map((item) => [
|
|
@@ -410,6 +858,7 @@ function applyManagedRuntimePlan(target, plan, backupRoot, actions, dryRun) {
|
|
|
410
858
|
|
|
411
859
|
export function initProject(packageRoot, target, options = {}) {
|
|
412
860
|
const { dryRun = false, force = false, packageVersion = "0.0.0" } = options;
|
|
861
|
+
const sources = installationSources(packageRoot);
|
|
413
862
|
validateTarget(target, { create: !dryRun });
|
|
414
863
|
const backupRoot = path.join(
|
|
415
864
|
target,
|
|
@@ -474,15 +923,22 @@ export function initProject(packageRoot, target, options = {}) {
|
|
|
474
923
|
dryRun,
|
|
475
924
|
});
|
|
476
925
|
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
926
|
+
const {
|
|
927
|
+
sourceSkills,
|
|
928
|
+
sourceTemplate,
|
|
929
|
+
sourceWorkflow,
|
|
930
|
+
sourceHook,
|
|
931
|
+
sourceHookConfig: sourceConfig,
|
|
932
|
+
sourceDesignReferenceLibrary,
|
|
933
|
+
bootstrapSources,
|
|
934
|
+
stateSources,
|
|
935
|
+
evolutionSources,
|
|
936
|
+
mockSources,
|
|
937
|
+
frontendGuardSources,
|
|
938
|
+
uiAcceptanceSources,
|
|
939
|
+
managerSources,
|
|
940
|
+
skillNames,
|
|
941
|
+
} = sources;
|
|
486
942
|
const sourceStandardsIndex = path.join(sourceWorkflow, "standards", "index.yaml");
|
|
487
943
|
const workflowRoot = path.join(target, ".workflow");
|
|
488
944
|
const standardsIndexFile = path.join(workflowRoot, "standards", "index.yaml");
|
|
@@ -510,7 +966,7 @@ export function initProject(packageRoot, target, options = {}) {
|
|
|
510
966
|
]) {
|
|
511
967
|
validateManagedPath(target, path.join(target, managedPath));
|
|
512
968
|
}
|
|
513
|
-
|
|
969
|
+
validateManagedPath(target, path.join(target, "ref", "ui"));
|
|
514
970
|
if (!skillNames.length) throw new Error(`No packaged skills found under ${sourceSkills}`);
|
|
515
971
|
|
|
516
972
|
const actions = [];
|
|
@@ -530,18 +986,29 @@ export function initProject(packageRoot, target, options = {}) {
|
|
|
530
986
|
const hookConflict = fs.existsSync(hookDestination)
|
|
531
987
|
&& !managedFilesEqual(hookDestination, sourceHook);
|
|
532
988
|
if (hookConflict && !reinstallManagedRuntime) conflicts.push(path.relative(target, hookDestination));
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
989
|
+
const retiredViewerDestination = path.join(target, ".workflow", "tools", "viewer");
|
|
990
|
+
const retireLegacyViewer = hasLegacyRoot && fs.existsSync(retiredViewerDestination);
|
|
991
|
+
const designReferenceDestination = path.join(target, "ref", "ui");
|
|
992
|
+
let designReferencePlan = null;
|
|
993
|
+
if (!fs.existsSync(designReferenceDestination)) {
|
|
994
|
+
designReferencePlan = {
|
|
995
|
+
type: "copy", source: sourceDesignReferenceLibrary, destination: designReferenceDestination,
|
|
996
|
+
};
|
|
997
|
+
} else if (directoryDigest(sourceDesignReferenceLibrary) !== directoryDigest(designReferenceDestination)) {
|
|
998
|
+
if (reinstallManagedRuntime) {
|
|
999
|
+
designReferencePlan = {
|
|
1000
|
+
type: "replace", source: sourceDesignReferenceLibrary, destination: designReferenceDestination,
|
|
1001
|
+
};
|
|
1002
|
+
} else conflicts.push(path.relative(target, designReferenceDestination));
|
|
540
1003
|
}
|
|
541
1004
|
const runtimePlans = [
|
|
542
1005
|
[".workflow/tools/state", stateSources],
|
|
543
1006
|
[".workflow/tools/evolution", evolutionSources],
|
|
544
1007
|
[".workflow/tools/bootstrap", bootstrapSources],
|
|
1008
|
+
[".workflow/tools/mock", mockSources],
|
|
1009
|
+
[".workflow/tools/frontend-guard", frontendGuardSources],
|
|
1010
|
+
[".workflow/tools/ui-acceptance", uiAcceptanceSources],
|
|
1011
|
+
[".workflow/tools/manager", managerSources],
|
|
545
1012
|
].map(([installPath, sources]) => planManagedRuntime({
|
|
546
1013
|
target,
|
|
547
1014
|
installPath,
|
|
@@ -631,14 +1098,19 @@ export function initProject(packageRoot, target, options = {}) {
|
|
|
631
1098
|
} else if (hookConflict) {
|
|
632
1099
|
backupAndReplace(target, hookDestination, sourceHook, backupRoot, actions, dryRun);
|
|
633
1100
|
}
|
|
634
|
-
if (
|
|
635
|
-
|
|
1101
|
+
if (retireLegacyViewer) {
|
|
1102
|
+
backupAndRemove(target, retiredViewerDestination, backupRoot, actions, dryRun);
|
|
1103
|
+
}
|
|
1104
|
+
if (designReferencePlan?.type === "copy") {
|
|
1105
|
+
actions.push("install ref/ui Design Reference Library");
|
|
636
1106
|
if (!dryRun) {
|
|
637
|
-
fs.mkdirSync(path.dirname(
|
|
638
|
-
fs.cpSync(
|
|
1107
|
+
fs.mkdirSync(path.dirname(designReferenceDestination), { recursive: true });
|
|
1108
|
+
fs.cpSync(sourceDesignReferenceLibrary, designReferenceDestination, { recursive: true });
|
|
639
1109
|
}
|
|
640
|
-
} else if (
|
|
641
|
-
backupAndReplace(
|
|
1110
|
+
} else if (designReferencePlan?.type === "replace") {
|
|
1111
|
+
backupAndReplace(
|
|
1112
|
+
target, designReferenceDestination, sourceDesignReferenceLibrary, backupRoot, actions, dryRun,
|
|
1113
|
+
);
|
|
642
1114
|
}
|
|
643
1115
|
for (const runtimePlan of runtimePlans) {
|
|
644
1116
|
applyManagedRuntimePlan(target, runtimePlan, backupRoot, actions, dryRun);
|
|
@@ -716,50 +1188,12 @@ export function initProject(packageRoot, target, options = {}) {
|
|
|
716
1188
|
} catch {
|
|
717
1189
|
existingManifest = null;
|
|
718
1190
|
}
|
|
719
|
-
const manifest =
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
workflow_root: ".workflow",
|
|
726
|
-
viewer_root: ".workflow/tools/viewer",
|
|
727
|
-
bootstrap_cli: ".workflow/tools/bootstrap/bootstrap.mjs",
|
|
728
|
-
state_cli: ".workflow/tools/state/state.mjs",
|
|
729
|
-
evolution_cli: ".workflow/tools/evolution/evolve.mjs",
|
|
730
|
-
hook_protocol_version: 1,
|
|
731
|
-
hook_cli: ".workflow/tools/hooks/hook-event.mjs",
|
|
732
|
-
code_repository: workspace.configuration.code_repository,
|
|
733
|
-
viewer_start: {
|
|
734
|
-
windows_cmd: ".workflow/tools/viewer/start.cmd",
|
|
735
|
-
powershell: ".workflow/tools/viewer/start.ps1",
|
|
736
|
-
unix: ".workflow/tools/viewer/start.sh",
|
|
737
|
-
},
|
|
738
|
-
installed_skills: skillNames,
|
|
739
|
-
managed_assets: {
|
|
740
|
-
skills: Object.fromEntries(skillNames.map((name) => [
|
|
741
|
-
name,
|
|
742
|
-
directoryDigest(path.join(sourceSkills, name)),
|
|
743
|
-
])),
|
|
744
|
-
hook: {
|
|
745
|
-
path: ".workflow/tools/hooks/hook-event.mjs",
|
|
746
|
-
sha256: managedFileDigest(sourceHook),
|
|
747
|
-
},
|
|
748
|
-
state_runtime: {
|
|
749
|
-
path: ".workflow/tools/state",
|
|
750
|
-
sha256: sourceSetDigest(stateSources),
|
|
751
|
-
},
|
|
752
|
-
evolution_runtime: {
|
|
753
|
-
path: ".workflow/tools/evolution",
|
|
754
|
-
sha256: sourceSetDigest(evolutionSources),
|
|
755
|
-
},
|
|
756
|
-
bootstrap_runtime: {
|
|
757
|
-
path: ".workflow/tools/bootstrap",
|
|
758
|
-
sha256: sourceSetDigest(bootstrapSources),
|
|
759
|
-
},
|
|
760
|
-
},
|
|
761
|
-
installed_at: existingManifest?.installed_at || new Date().toISOString(),
|
|
762
|
-
};
|
|
1191
|
+
const manifest = buildInstallManifest(
|
|
1192
|
+
sources,
|
|
1193
|
+
packageVersion,
|
|
1194
|
+
workspace.configuration.code_repository,
|
|
1195
|
+
existingManifest,
|
|
1196
|
+
);
|
|
763
1197
|
const manifestText = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
764
1198
|
if (!fs.existsSync(manifestFile) || fs.readFileSync(manifestFile, "utf8") !== manifestText) {
|
|
765
1199
|
actions.push("write .workflow/config/install-manifest.json");
|
|
@@ -783,11 +1217,827 @@ export function initProject(packageRoot, target, options = {}) {
|
|
|
783
1217
|
|
|
784
1218
|
return {
|
|
785
1219
|
actions: [...workspace.actions, ...actions],
|
|
786
|
-
summary: `${dryRun ? "Dry run" : "Initialized"} ${skillNames.length} workspace-scoped AI delivery skills in ${target}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1220
|
+
summary: `${dryRun ? "Dry run" : "Initialized"} ${skillNames.length} workspace-scoped AI delivery skills in ${target}`,
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
function readInstallManifest(target) {
|
|
1225
|
+
const manifestFile = path.join(target, INSTALL_MANIFEST);
|
|
1226
|
+
let manifest;
|
|
1227
|
+
try {
|
|
1228
|
+
manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
throw new Error(`Cannot read installed workflow manifest: ${error.message}`);
|
|
1231
|
+
}
|
|
1232
|
+
if (manifest.package !== "ai-delivery-workflow" || manifest.scope !== "workspace") {
|
|
1233
|
+
throw new Error("The installation manifest is not a compatible project-scoped ai-delivery-workflow installation.");
|
|
1234
|
+
}
|
|
1235
|
+
return { manifest, manifestFile };
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function installPath(target, relative) {
|
|
1239
|
+
return path.join(target, ...relative.split("/"));
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function managedAssetInventory(sources, previousManifest = {}) {
|
|
1243
|
+
const previous = previousManifest || {};
|
|
1244
|
+
const assets = sources.skillNames.map((name) => ({
|
|
1245
|
+
path: `.agents/skills/${name}`,
|
|
1246
|
+
kind: "directory",
|
|
1247
|
+
skill_name: name,
|
|
1248
|
+
source: path.join(sources.sourceSkills, name),
|
|
1249
|
+
expected_digest: directoryDigest(path.join(sources.sourceSkills, name)),
|
|
1250
|
+
previous_digest: previous.managed_assets?.skills?.[name] || null,
|
|
1251
|
+
}));
|
|
1252
|
+
assets.push(
|
|
1253
|
+
...ENSURED_CONTROL_DIRECTORIES.map((directory) => ({
|
|
1254
|
+
path: `.workflow/control/${directory}`,
|
|
1255
|
+
kind: "ensure-directory",
|
|
1256
|
+
source: path.join(sources.sourceTemplate, ".workflow", "control", directory),
|
|
1257
|
+
expected_digest: directoryDigest(
|
|
1258
|
+
path.join(sources.sourceTemplate, ".workflow", "control", directory),
|
|
1259
|
+
),
|
|
1260
|
+
previous_digest: null,
|
|
1261
|
+
})),
|
|
1262
|
+
...ENSURED_CONTROL_DIRECTORIES.map((directory) => ({
|
|
1263
|
+
path: `.workflow/control/${directory}/.gitkeep`,
|
|
1264
|
+
kind: "ensure-directory-marker",
|
|
1265
|
+
source: path.join(sources.sourceTemplate, ".workflow", "control", directory, ".gitkeep"),
|
|
1266
|
+
expected_digest: managedFileDigest(
|
|
1267
|
+
path.join(sources.sourceTemplate, ".workflow", "control", directory, ".gitkeep"),
|
|
1268
|
+
),
|
|
1269
|
+
previous_digest: null,
|
|
1270
|
+
})),
|
|
1271
|
+
{
|
|
1272
|
+
path: ".workflow/tools/hooks/hook-event.mjs",
|
|
1273
|
+
kind: "file",
|
|
1274
|
+
manifest_key: "hook",
|
|
1275
|
+
source: sources.sourceHook,
|
|
1276
|
+
expected_digest: managedFileDigest(sources.sourceHook),
|
|
1277
|
+
previous_digest: previous.managed_assets?.hook?.sha256 || null,
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
path: ".workflow/tools/state",
|
|
1281
|
+
kind: "runtime",
|
|
1282
|
+
manifest_key: "state_runtime",
|
|
1283
|
+
sources: sources.stateSources,
|
|
1284
|
+
expected_digest: sourceSetDigest(sources.stateSources),
|
|
1285
|
+
previous_digest: previous.managed_assets?.state_runtime?.sha256 || null,
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
path: ".workflow/tools/evolution",
|
|
1289
|
+
kind: "runtime",
|
|
1290
|
+
manifest_key: "evolution_runtime",
|
|
1291
|
+
sources: sources.evolutionSources,
|
|
1292
|
+
expected_digest: sourceSetDigest(sources.evolutionSources),
|
|
1293
|
+
previous_digest: previous.managed_assets?.evolution_runtime?.sha256 || null,
|
|
1294
|
+
},
|
|
1295
|
+
{
|
|
1296
|
+
path: ".workflow/tools/bootstrap",
|
|
1297
|
+
kind: "runtime",
|
|
1298
|
+
manifest_key: "bootstrap_runtime",
|
|
1299
|
+
sources: sources.bootstrapSources,
|
|
1300
|
+
expected_digest: sourceSetDigest(sources.bootstrapSources),
|
|
1301
|
+
previous_digest: previous.managed_assets?.bootstrap_runtime?.sha256 || null,
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
path: ".workflow/tools/mock",
|
|
1305
|
+
kind: "runtime",
|
|
1306
|
+
manifest_key: "mock_runtime",
|
|
1307
|
+
sources: sources.mockSources,
|
|
1308
|
+
expected_digest: sourceSetDigest(sources.mockSources),
|
|
1309
|
+
previous_digest: previous.managed_assets?.mock_runtime?.sha256 || null,
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
path: ".workflow/tools/frontend-guard",
|
|
1313
|
+
kind: "runtime",
|
|
1314
|
+
manifest_key: "frontend_guard_runtime",
|
|
1315
|
+
sources: sources.frontendGuardSources,
|
|
1316
|
+
expected_digest: sourceSetDigest(sources.frontendGuardSources),
|
|
1317
|
+
previous_digest: previous.managed_assets?.frontend_guard_runtime?.sha256 || null,
|
|
1318
|
+
},
|
|
1319
|
+
{
|
|
1320
|
+
path: ".workflow/tools/ui-acceptance",
|
|
1321
|
+
kind: "runtime",
|
|
1322
|
+
manifest_key: "ui_acceptance_runtime",
|
|
1323
|
+
sources: sources.uiAcceptanceSources,
|
|
1324
|
+
expected_digest: sourceSetDigest(sources.uiAcceptanceSources),
|
|
1325
|
+
previous_digest: previous.managed_assets?.ui_acceptance_runtime?.sha256 || null,
|
|
1326
|
+
},
|
|
1327
|
+
{
|
|
1328
|
+
path: ".workflow/tools/manager",
|
|
1329
|
+
kind: "runtime",
|
|
1330
|
+
manifest_key: "manager_runtime",
|
|
1331
|
+
sources: sources.managerSources,
|
|
1332
|
+
expected_digest: sourceSetDigest(sources.managerSources),
|
|
1333
|
+
previous_digest: previous.managed_assets?.manager_runtime?.sha256 || null,
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
path: "ref/ui",
|
|
1337
|
+
kind: "directory",
|
|
1338
|
+
manifest_key: "design_reference_library",
|
|
1339
|
+
source: sources.sourceDesignReferenceLibrary,
|
|
1340
|
+
expected_digest: directoryDigest(sources.sourceDesignReferenceLibrary),
|
|
1341
|
+
previous_digest: previous.managed_assets?.design_reference_library?.sha256 || null,
|
|
1342
|
+
},
|
|
1343
|
+
);
|
|
1344
|
+
return assets;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
function upgradeMergedAssets(target, sources, manifest) {
|
|
1348
|
+
const codePath = manifest.code_repository?.path;
|
|
1349
|
+
if (!codePath || path.isAbsolute(codePath) || codePath.split(/[\\/]/).includes("..")) {
|
|
1350
|
+
throw new Error("The installation manifest contains an unsafe code repository path.");
|
|
1351
|
+
}
|
|
1352
|
+
const definitions = MERGED_MANAGED_SURFACES.map((surface) => {
|
|
1353
|
+
if (surface.key === "agents") {
|
|
1354
|
+
return { ...surface, desired(existing) {
|
|
1355
|
+
const template = fs.readFileSync(path.join(sources.sourceTemplate, "AGENTS.md"), "utf8");
|
|
1356
|
+
return mergeManagedMarkdown(existing, template);
|
|
1357
|
+
} };
|
|
1358
|
+
}
|
|
1359
|
+
if (surface.key === "hook_config") {
|
|
1360
|
+
return { ...surface, desired(existing, refreshManaged) {
|
|
1361
|
+
return existing
|
|
1362
|
+
? mergeHookConfig(existing, { force: refreshManaged }).text
|
|
1363
|
+
: fs.readFileSync(sources.sourceHookConfig, "utf8");
|
|
1364
|
+
} };
|
|
1365
|
+
}
|
|
1366
|
+
if (surface.key === "gitignore") {
|
|
1367
|
+
return { ...surface, desired: (existing) => mergeGitignore(existing, [`/${codePath}/`]) };
|
|
1368
|
+
}
|
|
1369
|
+
return { ...surface, desired: (existing) => mergeGitattributes(existing) };
|
|
1370
|
+
});
|
|
1371
|
+
return definitions.map((definition) => {
|
|
1372
|
+
const destination = installPath(target, definition.path);
|
|
1373
|
+
const existing = fs.existsSync(destination) ? fs.readFileSync(destination, "utf8") : "";
|
|
1374
|
+
const previousDigest = manifest.managed_assets?.merged_surfaces?.[definition.key]?.sha256 || null;
|
|
1375
|
+
const currentManagedDigest = fs.existsSync(destination)
|
|
1376
|
+
? mergedSurfaceManagedDigest(definition.key, existing, codePath)
|
|
1377
|
+
: null;
|
|
1378
|
+
const packagedManagedDigest = mergedSurfaceManagedDigest(
|
|
1379
|
+
definition.key,
|
|
1380
|
+
expectedMergedSurfaceText(definition, sources, codePath),
|
|
1381
|
+
codePath,
|
|
1382
|
+
);
|
|
1383
|
+
const desired = definition.desired(
|
|
1384
|
+
existing,
|
|
1385
|
+
!previousDigest
|
|
1386
|
+
|| currentManagedDigest !== previousDigest
|
|
1387
|
+
|| packagedManagedDigest !== previousDigest,
|
|
1388
|
+
);
|
|
1389
|
+
return {
|
|
1390
|
+
...definition,
|
|
1391
|
+
destination,
|
|
1392
|
+
exists: fs.existsSync(destination),
|
|
1393
|
+
current_digest: fs.existsSync(destination) ? managedFileDigest(destination) : null,
|
|
1394
|
+
current_managed_digest: currentManagedDigest,
|
|
1395
|
+
previous_digest: previousDigest,
|
|
1396
|
+
desired,
|
|
1397
|
+
expected_digest: crypto.createHash("sha256")
|
|
1398
|
+
.update(Buffer.from(desired.replaceAll("\r\n", "\n")))
|
|
1399
|
+
.digest("hex"),
|
|
1400
|
+
};
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
function preservedWorkflowFiles(target, managedPaths) {
|
|
1405
|
+
const entries = [];
|
|
1406
|
+
let totalFiles = 0;
|
|
1407
|
+
const roots = [path.join(target, ".workflow"), path.join(target, ".agents")];
|
|
1408
|
+
const normalizedManaged = managedPaths.map((item) => item.replaceAll("\\", "/").replace(/\/$/, ""));
|
|
1409
|
+
const isWithin = (relative, prefixes) => prefixes.some(
|
|
1410
|
+
(prefix) => relative === prefix || relative.startsWith(`${prefix}/`),
|
|
1411
|
+
);
|
|
1412
|
+
for (const root of roots) {
|
|
1413
|
+
if (!fs.existsSync(root)) continue;
|
|
1414
|
+
const pending = [{ absolute: root, directory: true }];
|
|
1415
|
+
while (pending.length) {
|
|
1416
|
+
const current = pending.pop();
|
|
1417
|
+
const relative = path.relative(target, current.absolute).replaceAll("\\", "/");
|
|
1418
|
+
if (isWithin(relative, PRESERVED_TRANSIENT_ROOTS) || isWithin(relative, normalizedManaged)) {
|
|
1419
|
+
continue;
|
|
1420
|
+
}
|
|
1421
|
+
if (current.directory) {
|
|
1422
|
+
const children = fs.readdirSync(current.absolute, { withFileTypes: true })
|
|
1423
|
+
.filter((entry) => entry.isDirectory() || entry.isFile())
|
|
1424
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
1425
|
+
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
1426
|
+
const child = children[index];
|
|
1427
|
+
pending.push({
|
|
1428
|
+
absolute: path.join(current.absolute, child.name),
|
|
1429
|
+
directory: child.isDirectory(),
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
if (relative === INSTALL_MANIFEST.replaceAll("\\", "/")) continue;
|
|
1435
|
+
totalFiles += 1;
|
|
1436
|
+
if (entries.length < PRESERVED_DETAIL_LIMIT) {
|
|
1437
|
+
entries.push({ path: relative, reason: "outside managed installation assets" });
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
return {
|
|
1442
|
+
entries,
|
|
1443
|
+
summary: {
|
|
1444
|
+
total_files: totalFiles,
|
|
1445
|
+
reported_files: entries.length,
|
|
1446
|
+
omitted_files: totalFiles - entries.length,
|
|
1447
|
+
excluded_transient_roots: [...PRESERVED_TRANSIENT_ROOTS],
|
|
1448
|
+
},
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
function planUpgrade(packageRoot, target, options) {
|
|
1453
|
+
const { force = false, packageVersion = "0.0.0" } = options;
|
|
1454
|
+
validateTarget(target);
|
|
1455
|
+
for (const managedPath of [".agents", ".codex", ".workflow", "AGENTS.md", ".gitignore", ".gitattributes"]) {
|
|
1456
|
+
validateManagedPath(target, path.join(target, managedPath));
|
|
1457
|
+
}
|
|
1458
|
+
const { manifest, manifestFile } = readInstallManifest(target);
|
|
1459
|
+
if (!Number.isInteger(manifest.schema_version)
|
|
1460
|
+
|| manifest.schema_version < 5
|
|
1461
|
+
|| manifest.schema_version > INSTALL_MANIFEST_SCHEMA) {
|
|
1462
|
+
throw new Error(
|
|
1463
|
+
`Installation manifest schema ${manifest.schema_version} is incompatible with upgrade; run the compatible migration or repair command first.`,
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
const comparison = comparePackageVersions(packageVersion, manifest.version);
|
|
1467
|
+
if (comparison === null) {
|
|
1468
|
+
throw new Error(`Cannot compare installed package version ${manifest.version} with ${packageVersion}.`);
|
|
1469
|
+
}
|
|
1470
|
+
if (comparison < 0 && !force) {
|
|
1471
|
+
throw new Error(`Refusing package downgrade from ${manifest.version} to ${packageVersion}; use --force only after explicit approval.`);
|
|
1472
|
+
}
|
|
1473
|
+
const workspace = readWorkspaceConfiguration(target);
|
|
1474
|
+
const contractRetirement = executeDeliveryStateCommand([
|
|
1475
|
+
"contract", "retire", "--project", target, "--gate-id", "UX-LF", "--dry-run",
|
|
1476
|
+
], { yaml: FORMAL_STATE_YAML });
|
|
1477
|
+
const prototypeMigration = planPrototypeMigration(target);
|
|
1478
|
+
const codeRepository = workspace.configuration.code_repository;
|
|
1479
|
+
if (["path", "repo_id", "remote", "default_branch"].some((key) =>
|
|
1480
|
+
(manifest.code_repository?.[key] ?? null) !== (codeRepository[key] ?? null))) {
|
|
1481
|
+
throw new Error(
|
|
1482
|
+
"The installation manifest code repository identity differs from authoritative .workflow/config/workspace.yaml; repair the installation state before upgrade.",
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
const sources = installationSources(packageRoot);
|
|
1486
|
+
const exactAssets = managedAssetInventory(sources, manifest);
|
|
1487
|
+
const retiredAssets = RETIRED_MANAGED_ASSETS
|
|
1488
|
+
.filter((asset) => manifest.managed_assets?.[asset.manifest_key])
|
|
1489
|
+
.map((asset) => ({
|
|
1490
|
+
...asset,
|
|
1491
|
+
previous_digest: manifest.managed_assets[asset.manifest_key].sha256 || null,
|
|
1492
|
+
}));
|
|
1493
|
+
const mergedAssets = upgradeMergedAssets(
|
|
1494
|
+
target,
|
|
1495
|
+
sources,
|
|
1496
|
+
{ ...manifest, code_repository: codeRepository },
|
|
1497
|
+
);
|
|
1498
|
+
for (const asset of [...exactAssets, ...retiredAssets, ...mergedAssets]) {
|
|
1499
|
+
validateManagedPath(target, installPath(target, asset.path));
|
|
1500
|
+
}
|
|
1501
|
+
const additions = [];
|
|
1502
|
+
const replacements = [];
|
|
1503
|
+
const removals = [];
|
|
1504
|
+
const preserved = [];
|
|
1505
|
+
const conflicts = [];
|
|
1506
|
+
for (const asset of exactAssets) {
|
|
1507
|
+
const destination = installPath(target, asset.path);
|
|
1508
|
+
asset.destination = destination;
|
|
1509
|
+
if (asset.kind === "ensure-directory") {
|
|
1510
|
+
if (!fs.existsSync(destination)) {
|
|
1511
|
+
asset.current_digest = null;
|
|
1512
|
+
additions.push({ path: asset.path, kind: asset.kind });
|
|
1513
|
+
} else if (fs.statSync(destination).isDirectory()) {
|
|
1514
|
+
asset.current_digest = directoryDigest(destination);
|
|
1515
|
+
preserved.push({ path: asset.path, reason: "required control shard directory is present" });
|
|
1516
|
+
} else {
|
|
1517
|
+
asset.current_digest = pathDigest(destination);
|
|
1518
|
+
conflicts.push({
|
|
1519
|
+
path: asset.path,
|
|
1520
|
+
reason: "required control shard path is not a directory",
|
|
1521
|
+
forceable: false,
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
const currentDigest = pathDigest(destination);
|
|
1527
|
+
asset.current_digest = currentDigest;
|
|
1528
|
+
if (currentDigest === null) additions.push({ path: asset.path, kind: asset.kind });
|
|
1529
|
+
else if (currentDigest === asset.expected_digest) {
|
|
1530
|
+
preserved.push({ path: asset.path, reason: "already current" });
|
|
1531
|
+
} else if (asset.previous_digest && currentDigest !== asset.previous_digest) {
|
|
1532
|
+
conflicts.push({ path: asset.path, reason: "managed asset differs from the installed manifest digest" });
|
|
1533
|
+
if (force) replacements.push({ path: asset.path, kind: asset.kind, forced: true });
|
|
1534
|
+
} else if (!asset.previous_digest) {
|
|
1535
|
+
conflicts.push({ path: asset.path, reason: "installed manifest does not prove ownership of the current bytes" });
|
|
1536
|
+
if (force) replacements.push({ path: asset.path, kind: asset.kind, forced: true });
|
|
1537
|
+
} else {
|
|
1538
|
+
replacements.push({ path: asset.path, kind: asset.kind, forced: false });
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
for (const asset of mergedAssets) {
|
|
1542
|
+
if (!asset.exists) additions.push({ path: asset.path, kind: "merged-file" });
|
|
1543
|
+
else if (asset.current_digest === asset.expected_digest) {
|
|
1544
|
+
preserved.push({ path: asset.path, reason: "user-owned portions and managed contribution already current" });
|
|
1545
|
+
} else if (asset.previous_digest && asset.current_managed_digest !== asset.previous_digest) {
|
|
1546
|
+
conflicts.push({ path: asset.path, reason: "managed contribution differs from the installed manifest digest" });
|
|
1547
|
+
if (force) replacements.push({ path: asset.path, kind: "merged-file", forced: true });
|
|
1548
|
+
} else if (!asset.previous_digest) {
|
|
1549
|
+
conflicts.push({ path: asset.path, reason: "installed manifest does not prove ownership of the managed contribution" });
|
|
1550
|
+
if (force) replacements.push({ path: asset.path, kind: "merged-file", forced: true });
|
|
1551
|
+
} else {
|
|
1552
|
+
replacements.push({ path: asset.path, kind: "merged-file", forced: false });
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
for (const asset of retiredAssets) {
|
|
1556
|
+
const destination = installPath(target, asset.path);
|
|
1557
|
+
asset.destination = destination;
|
|
1558
|
+
const currentDigest = pathDigest(destination);
|
|
1559
|
+
asset.current_digest = currentDigest;
|
|
1560
|
+
if (currentDigest === null) {
|
|
1561
|
+
preserved.push({ path: asset.path, reason: "retired managed asset is already absent" });
|
|
1562
|
+
} else if (asset.previous_digest && currentDigest === asset.previous_digest) {
|
|
1563
|
+
removals.push({ path: asset.path, kind: asset.kind, forced: false });
|
|
1564
|
+
} else {
|
|
1565
|
+
conflicts.push({
|
|
1566
|
+
path: asset.path,
|
|
1567
|
+
reason: asset.previous_digest
|
|
1568
|
+
? "retired managed asset differs from the installed manifest digest"
|
|
1569
|
+
: "installed manifest does not prove ownership of the retired managed asset",
|
|
1570
|
+
});
|
|
1571
|
+
if (force) removals.push({ path: asset.path, kind: asset.kind, forced: true });
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
const preservedWorkflow = preservedWorkflowFiles(
|
|
1575
|
+
target,
|
|
1576
|
+
[
|
|
1577
|
+
...exactAssets
|
|
1578
|
+
.filter((asset) => asset.kind !== "ensure-directory")
|
|
1579
|
+
.map((asset) => asset.path),
|
|
1580
|
+
...retiredAssets.map((asset) => asset.path),
|
|
1581
|
+
...mergedAssets.map((asset) => asset.path),
|
|
1582
|
+
],
|
|
1583
|
+
);
|
|
1584
|
+
for (const entry of preservedWorkflow.entries) preserved.push(entry);
|
|
1585
|
+
const changedPaths = [...additions, ...replacements, ...removals].map((item) => item.path);
|
|
1586
|
+
const manifestReplacement = {
|
|
1587
|
+
path: INSTALL_MANIFEST.replaceAll("\\", "/"),
|
|
1588
|
+
kind: "manifest",
|
|
1589
|
+
forced: false,
|
|
1590
|
+
};
|
|
1591
|
+
replacements.push(manifestReplacement);
|
|
1592
|
+
const backups = [
|
|
1593
|
+
...changedPaths.filter((relative) => fs.existsSync(installPath(target, relative))),
|
|
1594
|
+
INSTALL_MANIFEST.replaceAll("\\", "/"),
|
|
1595
|
+
].map((relative) => ({ path: relative }));
|
|
1596
|
+
return {
|
|
1597
|
+
manifest,
|
|
1598
|
+
manifestFile,
|
|
1599
|
+
codeRepository,
|
|
1600
|
+
sources,
|
|
1601
|
+
exactAssets,
|
|
1602
|
+
retiredAssets,
|
|
1603
|
+
mergedAssets,
|
|
1604
|
+
additions,
|
|
1605
|
+
replacements,
|
|
1606
|
+
removals,
|
|
1607
|
+
preserved,
|
|
1608
|
+
preservedSummary: preservedWorkflow.summary,
|
|
1609
|
+
conflicts,
|
|
1610
|
+
backups,
|
|
1611
|
+
comparison,
|
|
1612
|
+
contractRetirement,
|
|
1613
|
+
prototypeMigration,
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
function copyUpgradeAsset(asset, destination) {
|
|
1618
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
1619
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
1620
|
+
if (asset.kind === "runtime") installManagedRuntime(destination, asset.sources);
|
|
1621
|
+
else if (new Set(["directory", "ensure-directory"]).has(asset.kind)) {
|
|
1622
|
+
fs.cpSync(asset.source, destination, { recursive: true });
|
|
1623
|
+
}
|
|
1624
|
+
else fs.copyFileSync(asset.source, destination);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
function backupUpgradePath(target, upgradeRoot, relative) {
|
|
1628
|
+
const source = installPath(target, relative);
|
|
1629
|
+
if (!fs.existsSync(source)) return null;
|
|
1630
|
+
const backupRelative = path.posix.join("backup", relative.replaceAll("\\", "/"));
|
|
1631
|
+
const backup = installPath(upgradeRoot, backupRelative);
|
|
1632
|
+
fs.mkdirSync(path.dirname(backup), { recursive: true });
|
|
1633
|
+
fs.cpSync(source, backup, { recursive: true });
|
|
1634
|
+
return backupRelative;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
function restoreUpgradeAssets(target, upgradeRoot, assets) {
|
|
1638
|
+
for (const asset of [...assets].reverse()) {
|
|
1639
|
+
const destination = installPath(target, asset.path);
|
|
1640
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
1641
|
+
if (asset.operation === "replace" || asset.operation === "remove") {
|
|
1642
|
+
const backup = installPath(upgradeRoot, asset.backup_path);
|
|
1643
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
1644
|
+
fs.cpSync(backup, destination, { recursive: true });
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
function applyReceiptBackedAsset({
|
|
1650
|
+
target,
|
|
1651
|
+
upgradeRoot,
|
|
1652
|
+
asset,
|
|
1653
|
+
operation,
|
|
1654
|
+
receiptAssets,
|
|
1655
|
+
apply,
|
|
1656
|
+
}) {
|
|
1657
|
+
const backupPath = backupUpgradePath(target, upgradeRoot, asset.path);
|
|
1658
|
+
const receiptAsset = {
|
|
1659
|
+
path: asset.path,
|
|
1660
|
+
operation,
|
|
1661
|
+
backup_path: backupPath,
|
|
1662
|
+
before_digest: exactPathDigest(asset.destination),
|
|
1663
|
+
after_digest: null,
|
|
790
1664
|
};
|
|
1665
|
+
receiptAssets.push(receiptAsset);
|
|
1666
|
+
apply();
|
|
1667
|
+
receiptAsset.after_digest = exactPathDigest(asset.destination);
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
function applyUpgradePlan(target, plan, packageVersion) {
|
|
1671
|
+
const upgradeId = `UPGRADE-${new Date().toISOString().replace(/[-:.]/g, "")}-${crypto.randomBytes(4).toString("hex")}`;
|
|
1672
|
+
const upgradeRoot = path.join(target, UPGRADE_RUNTIME, upgradeId);
|
|
1673
|
+
const formalEvents = path.join(target, ".workflow", "control", "events");
|
|
1674
|
+
const formalEventFingerprint = exactTreeDigest(formalEvents);
|
|
1675
|
+
const receiptAssets = [];
|
|
1676
|
+
fs.mkdirSync(upgradeRoot, { recursive: true });
|
|
1677
|
+
try {
|
|
1678
|
+
const additions = new Set(plan.additions.map((item) => item.path));
|
|
1679
|
+
const changes = new Set([
|
|
1680
|
+
...plan.additions.map((item) => item.path),
|
|
1681
|
+
...plan.replacements.map((item) => item.path),
|
|
1682
|
+
...plan.removals.map((item) => item.path),
|
|
1683
|
+
]);
|
|
1684
|
+
changes.delete(INSTALL_MANIFEST.replaceAll("\\", "/"));
|
|
1685
|
+
for (const asset of plan.exactAssets) {
|
|
1686
|
+
if (!changes.has(asset.path)) continue;
|
|
1687
|
+
applyReceiptBackedAsset({
|
|
1688
|
+
target,
|
|
1689
|
+
upgradeRoot,
|
|
1690
|
+
asset,
|
|
1691
|
+
operation: additions.has(asset.path) ? "add" : "replace",
|
|
1692
|
+
receiptAssets,
|
|
1693
|
+
apply: () => copyUpgradeAsset(asset, asset.destination),
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
for (const asset of plan.mergedAssets) {
|
|
1697
|
+
if (!changes.has(asset.path)) continue;
|
|
1698
|
+
applyReceiptBackedAsset({
|
|
1699
|
+
target,
|
|
1700
|
+
upgradeRoot,
|
|
1701
|
+
asset,
|
|
1702
|
+
operation: additions.has(asset.path) ? "add" : "replace",
|
|
1703
|
+
receiptAssets,
|
|
1704
|
+
apply: () => atomicWrite(asset.destination, asset.desired),
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
for (const asset of plan.retiredAssets) {
|
|
1708
|
+
if (!changes.has(asset.path)) continue;
|
|
1709
|
+
applyReceiptBackedAsset({
|
|
1710
|
+
target,
|
|
1711
|
+
upgradeRoot,
|
|
1712
|
+
asset,
|
|
1713
|
+
operation: "remove",
|
|
1714
|
+
receiptAssets,
|
|
1715
|
+
apply: () => fs.rmSync(asset.destination, { recursive: true, force: true }),
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
const manifestRelative = INSTALL_MANIFEST.replaceAll("\\", "/");
|
|
1719
|
+
const manifestBackup = backupUpgradePath(target, upgradeRoot, manifestRelative);
|
|
1720
|
+
const nextManifest = buildInstallManifest(
|
|
1721
|
+
plan.sources,
|
|
1722
|
+
packageVersion,
|
|
1723
|
+
plan.codeRepository,
|
|
1724
|
+
plan.manifest,
|
|
1725
|
+
);
|
|
1726
|
+
nextManifest.last_upgrade = { upgrade_id: upgradeId, upgraded_at: new Date().toISOString() };
|
|
1727
|
+
const manifestReceipt = {
|
|
1728
|
+
path: manifestRelative,
|
|
1729
|
+
operation: "replace",
|
|
1730
|
+
backup_path: manifestBackup,
|
|
1731
|
+
before_digest: exactPathDigest(installPath(upgradeRoot, manifestBackup)),
|
|
1732
|
+
after_digest: null,
|
|
1733
|
+
};
|
|
1734
|
+
receiptAssets.push(manifestReceipt);
|
|
1735
|
+
atomicWrite(plan.manifestFile, `${JSON.stringify(nextManifest, null, 2)}\n`);
|
|
1736
|
+
manifestReceipt.after_digest = exactPathDigest(plan.manifestFile);
|
|
1737
|
+
const receipt = {
|
|
1738
|
+
schema_version: 1,
|
|
1739
|
+
upgrade_id: upgradeId,
|
|
1740
|
+
status: "applied",
|
|
1741
|
+
from_version: plan.manifest.version,
|
|
1742
|
+
to_version: packageVersion,
|
|
1743
|
+
applied_at: new Date().toISOString(),
|
|
1744
|
+
formal_event_tree_fingerprint: formalEventFingerprint,
|
|
1745
|
+
contract_retirement: {
|
|
1746
|
+
gate_id: plan.contractRetirement.gate_id,
|
|
1747
|
+
contract_version: UI_CONTRACT_VERSION,
|
|
1748
|
+
affected_iterations: plan.contractRetirement.affected_iterations,
|
|
1749
|
+
expected_scope_revisions: plan.contractRetirement.expected_scope_revisions,
|
|
1750
|
+
},
|
|
1751
|
+
assets: receiptAssets,
|
|
1752
|
+
};
|
|
1753
|
+
atomicWrite(path.join(upgradeRoot, "receipt.json"), `${JSON.stringify(receipt, null, 2)}\n`);
|
|
1754
|
+
const retirementArgs = [
|
|
1755
|
+
"contract", "retire", "--project", target,
|
|
1756
|
+
"--gate-id", "UX-LF",
|
|
1757
|
+
"--actor", "system:package-upgrade",
|
|
1758
|
+
"--reason", "prototype-first UI contract",
|
|
1759
|
+
"--contract-version", UI_CONTRACT_VERSION,
|
|
1760
|
+
];
|
|
1761
|
+
const expectedRevisions = Object.values(plan.contractRetirement.expected_scope_revisions);
|
|
1762
|
+
if (expectedRevisions.length === 1) {
|
|
1763
|
+
retirementArgs.push("--expected-scope-revision", String(expectedRevisions[0]));
|
|
1764
|
+
}
|
|
1765
|
+
const contractRetirement = executeDeliveryStateCommand(retirementArgs, {
|
|
1766
|
+
yaml: FORMAL_STATE_YAML,
|
|
1767
|
+
});
|
|
1768
|
+
return { upgradeId, contractRetirement };
|
|
1769
|
+
} catch (error) {
|
|
1770
|
+
restoreUpgradeAssets(target, upgradeRoot, receiptAssets);
|
|
1771
|
+
fs.rmSync(upgradeRoot, { recursive: true, force: true });
|
|
1772
|
+
throw error;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
function rollbackUpgrade(packageRoot, target, upgradeId) {
|
|
1777
|
+
validateTarget(target);
|
|
1778
|
+
if (!/^UPGRADE-[0-9A-Za-z_-]+$/.test(upgradeId || "")) {
|
|
1779
|
+
throw new Error("Invalid upgrade rollback identifier.");
|
|
1780
|
+
}
|
|
1781
|
+
const upgradeRoot = path.join(target, UPGRADE_RUNTIME, upgradeId);
|
|
1782
|
+
validateManagedPath(target, upgradeRoot);
|
|
1783
|
+
const receiptFile = path.join(upgradeRoot, "receipt.json");
|
|
1784
|
+
let receipt;
|
|
1785
|
+
try {
|
|
1786
|
+
receipt = JSON.parse(fs.readFileSync(receiptFile, "utf8"));
|
|
1787
|
+
} catch (error) {
|
|
1788
|
+
throw new Error(`Cannot read upgrade rollback receipt ${upgradeId}: ${error.message}`);
|
|
1789
|
+
}
|
|
1790
|
+
if (receipt.schema_version !== 1
|
|
1791
|
+
|| receipt.upgrade_id !== upgradeId
|
|
1792
|
+
|| receipt.status !== "applied"
|
|
1793
|
+
|| !Array.isArray(receipt.assets)) {
|
|
1794
|
+
throw new Error(`Upgrade ${upgradeId} is not in an applied rollback state.`);
|
|
1795
|
+
}
|
|
1796
|
+
const sources = installationSources(packageRoot);
|
|
1797
|
+
const allowedPaths = new Set([
|
|
1798
|
+
...managedAssetInventory(sources).map((asset) => asset.path),
|
|
1799
|
+
...RETIRED_MANAGED_ASSETS.map((asset) => asset.path),
|
|
1800
|
+
...MERGED_MANAGED_SURFACES.map((surface) => surface.path),
|
|
1801
|
+
INSTALL_MANIFEST.replaceAll("\\", "/"),
|
|
1802
|
+
]);
|
|
1803
|
+
for (const asset of receipt.assets) {
|
|
1804
|
+
if (!allowedPaths.has(asset.path)
|
|
1805
|
+
|| !new Set(["add", "replace", "remove"]).has(asset.operation)
|
|
1806
|
+
|| (new Set(["replace", "remove"]).has(asset.operation) && !asset.backup_path)) {
|
|
1807
|
+
throw new Error(`Upgrade receipt contains an unsafe managed asset entry: ${asset.path || "<missing>"}.`);
|
|
1808
|
+
}
|
|
1809
|
+
const destination = installPath(target, asset.path);
|
|
1810
|
+
validateManagedPath(target, destination);
|
|
1811
|
+
if (asset.backup_path) {
|
|
1812
|
+
const backup = installPath(upgradeRoot, asset.backup_path);
|
|
1813
|
+
if (!isInside(backup, upgradeRoot)
|
|
1814
|
+
|| !asset.backup_path.replaceAll("\\", "/").startsWith("backup/")) {
|
|
1815
|
+
throw new Error(`Upgrade receipt contains an unsafe backup path for ${asset.path}.`);
|
|
1816
|
+
}
|
|
1817
|
+
validateManagedPath(upgradeRoot, backup);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
const currentFingerprint = exactTreeDigest(path.join(target, ".workflow", "control", "events"));
|
|
1821
|
+
if (currentFingerprint !== receipt.formal_event_tree_fingerprint) {
|
|
1822
|
+
throw new Error(
|
|
1823
|
+
"A new-format formal transaction was written after this upgrade; rollback is blocked. Use forward repair.",
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
for (const asset of receipt.assets) {
|
|
1827
|
+
if (exactPathDigest(installPath(target, asset.path)) !== asset.after_digest) {
|
|
1828
|
+
throw new Error(`Managed asset changed after upgrade (${asset.path}); rollback is blocked. Use forward repair.`);
|
|
1829
|
+
}
|
|
1830
|
+
if (asset.operation === "replace" || asset.operation === "remove") {
|
|
1831
|
+
const backup = installPath(upgradeRoot, asset.backup_path);
|
|
1832
|
+
if (exactPathDigest(backup) !== asset.before_digest) {
|
|
1833
|
+
throw new Error(`Upgrade backup changed after apply (${asset.path}); rollback is blocked. Use forward repair.`);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
restoreUpgradeAssets(target, upgradeRoot, receipt.assets);
|
|
1838
|
+
receipt.status = "rolled_back";
|
|
1839
|
+
receipt.rolled_back_at = new Date().toISOString();
|
|
1840
|
+
atomicWrite(receiptFile, `${JSON.stringify(receipt, null, 2)}\n`);
|
|
1841
|
+
return {
|
|
1842
|
+
command: "upgrade rollback",
|
|
1843
|
+
upgrade_id: upgradeId,
|
|
1844
|
+
rolled_back: true,
|
|
1845
|
+
restored_version: receipt.from_version,
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
export function upgradeProject(packageRoot, target, options = {}) {
|
|
1850
|
+
if (options.rollback) return rollbackUpgrade(packageRoot, target, options.rollback);
|
|
1851
|
+
const { dryRun = false, force = false, packageVersion = "0.0.0" } = options;
|
|
1852
|
+
const plan = planUpgrade(packageRoot, target, { force, packageVersion });
|
|
1853
|
+
const report = {
|
|
1854
|
+
command: "upgrade",
|
|
1855
|
+
dry_run: dryRun,
|
|
1856
|
+
applied: false,
|
|
1857
|
+
upgrade_id: null,
|
|
1858
|
+
from_version: plan.manifest.version,
|
|
1859
|
+
to_version: packageVersion,
|
|
1860
|
+
changes: {
|
|
1861
|
+
additions: plan.additions,
|
|
1862
|
+
replacements: plan.replacements,
|
|
1863
|
+
removals: plan.removals,
|
|
1864
|
+
preserved: plan.preserved,
|
|
1865
|
+
preserved_summary: plan.preservedSummary,
|
|
1866
|
+
conflicts: plan.conflicts,
|
|
1867
|
+
backups: plan.backups,
|
|
1868
|
+
},
|
|
1869
|
+
compatibility: {
|
|
1870
|
+
installation_manifest: "compatible",
|
|
1871
|
+
package_version: plan.comparison > 0 ? "upgrade" : plan.comparison === 0 ? "same-version repair" : "forced downgrade",
|
|
1872
|
+
},
|
|
1873
|
+
code_repository: {
|
|
1874
|
+
path: plan.codeRepository.path,
|
|
1875
|
+
write_planned: false,
|
|
1876
|
+
},
|
|
1877
|
+
contract_retirement: {
|
|
1878
|
+
...plan.contractRetirement,
|
|
1879
|
+
contract_version: UI_CONTRACT_VERSION,
|
|
1880
|
+
write_planned: plan.contractRetirement.affected_iterations.length > 0,
|
|
1881
|
+
},
|
|
1882
|
+
prototype_migration: {
|
|
1883
|
+
...plan.prototypeMigration,
|
|
1884
|
+
required_follow_up: {
|
|
1885
|
+
command: "ai-delivery migrate",
|
|
1886
|
+
write_planned: false,
|
|
1887
|
+
},
|
|
1888
|
+
},
|
|
1889
|
+
};
|
|
1890
|
+
if (dryRun) return report;
|
|
1891
|
+
const blockingConflicts = plan.conflicts.filter((item) => !force || item.forceable === false);
|
|
1892
|
+
if (blockingConflicts.length) {
|
|
1893
|
+
throw new Error(
|
|
1894
|
+
`Managed files differ from the installed manifest:\n- ${blockingConflicts.map((item) =>
|
|
1895
|
+
`${item.path}: ${item.reason}`).join("\n- ")}\nRerun with --force only after explicit approval of forceable conflicts.`,
|
|
1896
|
+
);
|
|
1897
|
+
}
|
|
1898
|
+
const applied = applyUpgradePlan(target, plan, packageVersion);
|
|
1899
|
+
report.upgrade_id = applied.upgradeId;
|
|
1900
|
+
report.contract_retirement = {
|
|
1901
|
+
...report.contract_retirement,
|
|
1902
|
+
...applied.contractRetirement,
|
|
1903
|
+
contract_version: UI_CONTRACT_VERSION,
|
|
1904
|
+
write_planned: false,
|
|
1905
|
+
};
|
|
1906
|
+
report.applied = true;
|
|
1907
|
+
return report;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
function managedRuntimeDoctorCheck(target, options) {
|
|
1911
|
+
const installedRoot = path.join(target, ".workflow", "tools", options.directory);
|
|
1912
|
+
const expectedDigest = options.expectedSources.length
|
|
1913
|
+
? sourceSetDigest(options.expectedSources)
|
|
1914
|
+
: null;
|
|
1915
|
+
const integrity = Boolean(expectedDigest)
|
|
1916
|
+
&& fs.existsSync(installedRoot)
|
|
1917
|
+
&& directoryDigest(installedRoot) === expectedDigest
|
|
1918
|
+
&& options.manifestDigest === expectedDigest;
|
|
1919
|
+
const smoke = spawnSync(
|
|
1920
|
+
process.execPath,
|
|
1921
|
+
[path.join(installedRoot, options.cli), "--help"],
|
|
1922
|
+
{ cwd: target, encoding: "utf8", windowsHide: true },
|
|
1923
|
+
);
|
|
1924
|
+
const smokeOk = smoke.status === 0 && options.helpPattern.test(smoke.stdout);
|
|
1925
|
+
return {
|
|
1926
|
+
ok: integrity && smokeOk,
|
|
1927
|
+
scope: options.scope,
|
|
1928
|
+
message: !integrity
|
|
1929
|
+
? `project ${options.label} runtime integrity check failed`
|
|
1930
|
+
: smokeOk
|
|
1931
|
+
? `project ${options.label} runtime integrity and CLI smoke check pass`
|
|
1932
|
+
: `project ${options.label} CLI smoke check failed: ${smoke.stderr.trim() || smoke.error?.message || "no valid output"}`,
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
function rawGitAttributePolicy(gitattributes) {
|
|
1937
|
+
const rawTargets = new Set(RAW_GIT_ATTRIBUTE_TARGETS.map((target) => `/${target}`));
|
|
1938
|
+
const invalid = [];
|
|
1939
|
+
const legacyPatterns = [];
|
|
1940
|
+
for (const line of gitattributes.split(/\r?\n/).map((entry) => entry.trim())) {
|
|
1941
|
+
if (!line || line.startsWith("#") || !/(?:^|\s)-text(?:\s|$)/.test(line)) continue;
|
|
1942
|
+
const [pattern] = line.split(/\s+/);
|
|
1943
|
+
if (rawTargets.has(pattern)) continue;
|
|
1944
|
+
const normalized = pattern.replace(/^\//, "");
|
|
1945
|
+
const segments = normalized.split("/");
|
|
1946
|
+
const fixedDirectory = segments.length >= 4
|
|
1947
|
+
&& segments[0] === ".workflow"
|
|
1948
|
+
&& segments[1] === "iterations"
|
|
1949
|
+
&& !/[?*\[]/.test(segments[2])
|
|
1950
|
+
&& segments.slice(0, -1).every((segment) => !/[?*\[]/.test(segment));
|
|
1951
|
+
const narrowFilename = !pattern.includes("**") && !/[?\[]/.test(segments.at(-1));
|
|
1952
|
+
if (fixedDirectory && narrowFilename) legacyPatterns.push(normalized);
|
|
1953
|
+
else invalid.push(line);
|
|
1954
|
+
}
|
|
1955
|
+
return { invalid, legacyPatterns };
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
function matchesRegisteredLegacyPattern(relative, pattern) {
|
|
1959
|
+
const relativeSegments = relative.split("/");
|
|
1960
|
+
const patternSegments = pattern.split("/");
|
|
1961
|
+
if (relativeSegments.length !== patternSegments.length) return false;
|
|
1962
|
+
return patternSegments.every((segment, index) => {
|
|
1963
|
+
if (!segment.includes("*")) return segment === relativeSegments[index];
|
|
1964
|
+
const expression = segment.split("*")
|
|
1965
|
+
.map((part) => part.replace(/[.+^${}()|[\]\\]/g, "\\$&"))
|
|
1966
|
+
.join("[^/]*");
|
|
1967
|
+
return new RegExp(`^${expression}$`).test(relativeSegments[index]);
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
function isRawLegacyIterationEvidence(target, relative, text, eol, registeredPatterns) {
|
|
1972
|
+
return text === "unset"
|
|
1973
|
+
&& eol === "lf"
|
|
1974
|
+
&& relative.startsWith(".workflow/iterations/")
|
|
1975
|
+
&& fs.existsSync(installPath(target, relative))
|
|
1976
|
+
&& registeredPatterns.some((pattern) => matchesRegisteredLegacyPattern(relative, pattern));
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
function filesUnderExcluding(root, excludedRoots) {
|
|
1980
|
+
if (!fs.existsSync(root)) return [];
|
|
1981
|
+
const files = [];
|
|
1982
|
+
const pending = [root];
|
|
1983
|
+
while (pending.length) {
|
|
1984
|
+
const current = pending.pop();
|
|
1985
|
+
if (excludedRoots.some((excluded) => isInside(current, excluded))) continue;
|
|
1986
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
1987
|
+
const absolute = path.join(current, entry.name);
|
|
1988
|
+
if (entry.isDirectory()) pending.push(absolute);
|
|
1989
|
+
else if (entry.isFile()) files.push(absolute);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
return files.sort((left, right) => left.localeCompare(right));
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
function effectiveGitAttributeAudit(target, files, registeredLegacyPatterns) {
|
|
1996
|
+
const invalid = [];
|
|
1997
|
+
let invalidCount = 0;
|
|
1998
|
+
for (let index = 0; index < files.length; index += DOCTOR_ATTRIBUTE_BATCH_SIZE) {
|
|
1999
|
+
const batch = files.slice(index, index + DOCTOR_ATTRIBUTE_BATCH_SIZE);
|
|
2000
|
+
const result = spawnSync(
|
|
2001
|
+
"git",
|
|
2002
|
+
["-c", "core.quotePath=false", "check-attr", "-z", "--stdin", "text", "eol"],
|
|
2003
|
+
{
|
|
2004
|
+
cwd: target,
|
|
2005
|
+
encoding: "utf8",
|
|
2006
|
+
input: `${batch.join("\0")}\0`,
|
|
2007
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
2008
|
+
windowsHide: true,
|
|
2009
|
+
},
|
|
2010
|
+
);
|
|
2011
|
+
if (result.status !== 0) {
|
|
2012
|
+
return {
|
|
2013
|
+
failed: true,
|
|
2014
|
+
error: (result.stderr || result.error?.message || "git check-attr failed").trim(),
|
|
2015
|
+
invalid,
|
|
2016
|
+
invalidCount,
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
const values = new Map();
|
|
2020
|
+
const tokens = result.stdout.split("\0");
|
|
2021
|
+
for (let offset = 0; offset + 2 < tokens.length; offset += 3) {
|
|
2022
|
+
const [file, attribute, value] = tokens.slice(offset, offset + 3);
|
|
2023
|
+
if (!file || !attribute) continue;
|
|
2024
|
+
if (!values.has(file)) values.set(file, {});
|
|
2025
|
+
values.get(file)[attribute] = value;
|
|
2026
|
+
}
|
|
2027
|
+
for (const file of batch) {
|
|
2028
|
+
const text = values.get(file)?.text;
|
|
2029
|
+
const eol = values.get(file)?.eol;
|
|
2030
|
+
const rawAttribution = RAW_GIT_ATTRIBUTE_TARGETS.includes(file);
|
|
2031
|
+
const valid = rawAttribution
|
|
2032
|
+
? text === "unset" && eol === "unspecified"
|
|
2033
|
+
: isRawLegacyIterationEvidence(target, file, text, eol, registeredLegacyPatterns)
|
|
2034
|
+
|| (text === "set" || text === "auto") && eol === "lf";
|
|
2035
|
+
if (valid) continue;
|
|
2036
|
+
invalidCount += 1;
|
|
2037
|
+
if (invalid.length < DOCTOR_DETAIL_LIMIT) invalid.push(file);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
return { failed: false, error: null, invalid, invalidCount };
|
|
791
2041
|
}
|
|
792
2042
|
|
|
793
2043
|
export function doctorProject(target, options = {}) {
|
|
@@ -803,12 +2053,19 @@ export function doctorProject(target, options = {}) {
|
|
|
803
2053
|
} catch (error) {
|
|
804
2054
|
checks.push({ ok: false, message: error.message });
|
|
805
2055
|
}
|
|
2056
|
+
if (workspace) {
|
|
2057
|
+
checks.push(inspectCodeGraph(target, {
|
|
2058
|
+
runner: options.codeGraphRunner,
|
|
2059
|
+
command: options.codeGraphCommand,
|
|
2060
|
+
env: options.codeGraphEnv,
|
|
2061
|
+
}));
|
|
2062
|
+
}
|
|
806
2063
|
const manifestFile = path.join(target, INSTALL_MANIFEST);
|
|
807
2064
|
let manifest = null;
|
|
808
2065
|
try {
|
|
809
2066
|
manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
|
|
810
2067
|
checks.push({
|
|
811
|
-
ok: manifest.scope === "workspace" && manifest.schema_version ===
|
|
2068
|
+
ok: manifest.scope === "workspace" && manifest.schema_version === INSTALL_MANIFEST_SCHEMA,
|
|
812
2069
|
message: "installation manifest uses workspace scope",
|
|
813
2070
|
});
|
|
814
2071
|
} catch (error) {
|
|
@@ -824,11 +2081,21 @@ export function doctorProject(target, options = {}) {
|
|
|
824
2081
|
message: "workflow repository has an independent Git root",
|
|
825
2082
|
});
|
|
826
2083
|
const missingControlDirectories = CONTROL_DIRECTORIES.filter((directory) =>
|
|
827
|
-
!fs.existsSync(path.join(target, ".workflow", "control", directory))
|
|
2084
|
+
!fs.existsSync(path.join(target, ".workflow", "control", directory))
|
|
2085
|
+
|| !fs.statSync(path.join(target, ".workflow", "control", directory)).isDirectory());
|
|
2086
|
+
const missingControlMarkers = ENSURED_CONTROL_DIRECTORIES.filter((directory) => {
|
|
2087
|
+
const directoryPath = path.join(target, ".workflow", "control", directory);
|
|
2088
|
+
const marker = path.join(directoryPath, ".gitkeep");
|
|
2089
|
+
return fs.existsSync(directoryPath)
|
|
2090
|
+
&& fs.statSync(directoryPath).isDirectory()
|
|
2091
|
+
&& (!fs.existsSync(marker) || !fs.statSync(marker).isFile());
|
|
2092
|
+
}).map((directory) => `${directory}/.gitkeep`);
|
|
828
2093
|
checks.push({
|
|
829
|
-
ok: missingControlDirectories.length === 0,
|
|
2094
|
+
ok: missingControlDirectories.length === 0 && missingControlMarkers.length === 0,
|
|
830
2095
|
message: missingControlDirectories.length
|
|
831
2096
|
? `shared control shard directories are missing: ${missingControlDirectories.join(", ")}`
|
|
2097
|
+
: missingControlMarkers.length
|
|
2098
|
+
? `shared control shard directories have missing markers: ${missingControlMarkers.join(", ")}`
|
|
832
2099
|
: "shared task, artifact, Gate, version, and release shard directories are present",
|
|
833
2100
|
});
|
|
834
2101
|
checks.push({
|
|
@@ -845,12 +2112,17 @@ export function doctorProject(target, options = {}) {
|
|
|
845
2112
|
});
|
|
846
2113
|
|
|
847
2114
|
const packageRoot = options.packageRoot || PACKAGE_ROOT;
|
|
2115
|
+
const packageSurface = packageSurfaceDoctorCheck(packageRoot);
|
|
2116
|
+
checks.push(packageSurface);
|
|
848
2117
|
let expectedSkills = ESSENTIAL_STARTUP_SKILLS;
|
|
849
2118
|
let expectedSkillDigests = {};
|
|
850
2119
|
let expectedHook = null;
|
|
851
2120
|
let expectedBootstrapSources = [];
|
|
852
2121
|
let expectedStateSources = [];
|
|
853
2122
|
let expectedEvolutionSources = [];
|
|
2123
|
+
let expectedMockSources = [];
|
|
2124
|
+
let expectedFrontendGuardSources = [];
|
|
2125
|
+
let expectedUiAcceptanceSources = [];
|
|
854
2126
|
try {
|
|
855
2127
|
expectedSkills = packagedSkillNames(packageRoot);
|
|
856
2128
|
expectedSkillDigests = Object.fromEntries(expectedSkills.map((name) => [
|
|
@@ -858,10 +2130,15 @@ export function doctorProject(target, options = {}) {
|
|
|
858
2130
|
directoryDigest(path.join(packageRoot, "skills", name)),
|
|
859
2131
|
]));
|
|
860
2132
|
expectedHook = path.join(packageRoot, "skills", "ai-delivery-checkpoint-task", "scripts", "hook-event.mjs");
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
2133
|
+
if (packageSurface.ok) {
|
|
2134
|
+
const sourceTemplate = path.join(packageRoot, "skills", "ai-delivery-orchestrate", "assets", "project-template");
|
|
2135
|
+
expectedBootstrapSources = bootstrapRuntimeSources(packageRoot, sourceTemplate);
|
|
2136
|
+
expectedStateSources = stateRuntimeSources(packageRoot, sourceTemplate);
|
|
2137
|
+
expectedEvolutionSources = evolutionRuntimeSources(packageRoot, sourceTemplate);
|
|
2138
|
+
expectedMockSources = mockRuntimeSources(packageRoot, sourceTemplate);
|
|
2139
|
+
expectedFrontendGuardSources = frontendGuardRuntimeSources(packageRoot, sourceTemplate);
|
|
2140
|
+
expectedUiAcceptanceSources = uiAcceptanceRuntimeSources(packageRoot, sourceTemplate);
|
|
2141
|
+
}
|
|
865
2142
|
} catch (error) {
|
|
866
2143
|
checks.push({ ok: false, message: `packaged managed-asset baseline is unavailable: ${error.message}` });
|
|
867
2144
|
}
|
|
@@ -890,6 +2167,20 @@ export function doctorProject(target, options = {}) {
|
|
|
890
2167
|
? `managed skill integrity failed: damaged=${damagedSkills.join(", ") || "none"}; manifest=${inconsistentSkillDigests.join(", ") || "none"}`
|
|
891
2168
|
: "managed skill integrity matches the fixed package baseline",
|
|
892
2169
|
});
|
|
2170
|
+
if (!packageSurface.ok) return { checks, ok: false };
|
|
2171
|
+
checks.push(designReferenceDoctorCheck(
|
|
2172
|
+
target,
|
|
2173
|
+
path.join(
|
|
2174
|
+
packageRoot,
|
|
2175
|
+
"skills",
|
|
2176
|
+
"ai-delivery-orchestrate",
|
|
2177
|
+
"assets",
|
|
2178
|
+
"project-template",
|
|
2179
|
+
"ref",
|
|
2180
|
+
"ui",
|
|
2181
|
+
),
|
|
2182
|
+
manifest,
|
|
2183
|
+
));
|
|
893
2184
|
try {
|
|
894
2185
|
const config = inspectHookConfig(fs.readFileSync(path.join(target, ".codex", "config.toml"), "utf8"));
|
|
895
2186
|
checks.push({ ok: config.hooksEnabled, message: "project Codex hooks feature is enabled" });
|
|
@@ -908,28 +2199,11 @@ export function doctorProject(target, options = {}) {
|
|
|
908
2199
|
ok: hookIntegrity,
|
|
909
2200
|
message: hookIntegrity ? "project Hook integrity matches the package baseline" : "project Hook integrity check failed",
|
|
910
2201
|
});
|
|
911
|
-
const viewerFiles = [
|
|
912
|
-
"server.mjs",
|
|
913
|
-
"start.cmd",
|
|
914
|
-
"start.ps1",
|
|
915
|
-
"start.sh",
|
|
916
|
-
"public/index.html",
|
|
917
|
-
"public/app.js",
|
|
918
|
-
"public/styles.css",
|
|
919
|
-
];
|
|
920
|
-
const missingViewerFiles = viewerFiles.filter((file) =>
|
|
921
|
-
!fs.existsSync(path.join(target, ".workflow", "tools", "viewer", file)));
|
|
922
|
-
checks.push({
|
|
923
|
-
ok: missingViewerFiles.length === 0,
|
|
924
|
-
required: false,
|
|
925
|
-
scope: "viewer",
|
|
926
|
-
message: missingViewerFiles.length
|
|
927
|
-
? `workflow viewer is incomplete: ${missingViewerFiles.join(", ")}`
|
|
928
|
-
: "project workflow viewer and startup scripts are present",
|
|
929
|
-
});
|
|
930
2202
|
const stateFiles = [
|
|
931
2203
|
"state.mjs",
|
|
932
2204
|
"delivery-state.mjs",
|
|
2205
|
+
"history-anchor.mjs",
|
|
2206
|
+
"fs-utils.mjs",
|
|
933
2207
|
"vendor/yaml/package.json",
|
|
934
2208
|
"vendor/yaml/dist/index.js",
|
|
935
2209
|
];
|
|
@@ -960,6 +2234,8 @@ export function doctorProject(target, options = {}) {
|
|
|
960
2234
|
"workspace.mjs",
|
|
961
2235
|
"yaml-runtime.mjs",
|
|
962
2236
|
"delivery-state.mjs",
|
|
2237
|
+
"history-anchor.mjs",
|
|
2238
|
+
"fs-utils.mjs",
|
|
963
2239
|
];
|
|
964
2240
|
const missingBootstrapFiles = bootstrapFiles.filter((file) =>
|
|
965
2241
|
!fs.existsSync(path.join(installedBootstrapRoot, file)));
|
|
@@ -999,7 +2275,13 @@ export function doctorProject(target, options = {}) {
|
|
|
999
2275
|
const result = spawnSync(
|
|
1000
2276
|
process.execPath,
|
|
1001
2277
|
[path.join(installedStateRoot, "state.mjs"), command],
|
|
1002
|
-
{
|
|
2278
|
+
{
|
|
2279
|
+
cwd: target,
|
|
2280
|
+
encoding: "utf8",
|
|
2281
|
+
maxBuffer: command === "inspect" ? FORMAL_STATE_INSPECT_MAX_BUFFER : undefined,
|
|
2282
|
+
windowsHide: true,
|
|
2283
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2284
|
+
},
|
|
1003
2285
|
);
|
|
1004
2286
|
let output = null;
|
|
1005
2287
|
try {
|
|
@@ -1007,7 +2289,9 @@ export function doctorProject(target, options = {}) {
|
|
|
1007
2289
|
} catch {
|
|
1008
2290
|
output = null;
|
|
1009
2291
|
}
|
|
1010
|
-
const validOutput = command === "verify"
|
|
2292
|
+
const validOutput = command === "verify"
|
|
2293
|
+
? output?.valid === true
|
|
2294
|
+
: output?.command === "inspect";
|
|
1011
2295
|
return { ...result, command, validOutput };
|
|
1012
2296
|
});
|
|
1013
2297
|
const smokeOk = smokeResults.every((result) => result.status === 0 && result.validOutput);
|
|
@@ -1042,6 +2326,47 @@ export function doctorProject(target, options = {}) {
|
|
|
1042
2326
|
? "workflow evolution CLI verify smoke check passes"
|
|
1043
2327
|
: `workflow evolution CLI smoke check failed: ${evolutionSmoke.stderr.trim() || "no valid output"}`,
|
|
1044
2328
|
});
|
|
2329
|
+
const installedMockRoot = path.join(target, ".workflow", "tools", "mock");
|
|
2330
|
+
const expectedMockDigest = expectedMockSources.length ? sourceSetDigest(expectedMockSources) : null;
|
|
2331
|
+
const mockIntegrity = Boolean(expectedMockDigest)
|
|
2332
|
+
&& fs.existsSync(installedMockRoot)
|
|
2333
|
+
&& directoryDigest(installedMockRoot) === expectedMockDigest
|
|
2334
|
+
&& manifest?.managed_assets?.mock_runtime?.sha256 === expectedMockDigest;
|
|
2335
|
+
const mockSmoke = spawnSync(
|
|
2336
|
+
process.execPath,
|
|
2337
|
+
[path.join(installedMockRoot, "mock.mjs"), "--help"],
|
|
2338
|
+
{ cwd: target, encoding: "utf8", windowsHide: true },
|
|
2339
|
+
);
|
|
2340
|
+
const mockSmokeOk = mockSmoke.status === 0
|
|
2341
|
+
&& /mock <setup\|start\|stop\|status\|sync>/.test(mockSmoke.stdout)
|
|
2342
|
+
&& /mock freeze.*--scenario-pack/.test(mockSmoke.stdout);
|
|
2343
|
+
checks.push({
|
|
2344
|
+
ok: mockIntegrity && mockSmokeOk,
|
|
2345
|
+
scope: "mock-system",
|
|
2346
|
+
message: !mockIntegrity
|
|
2347
|
+
? "project Mock System runtime integrity check failed"
|
|
2348
|
+
: mockSmokeOk
|
|
2349
|
+
? "project Mock System runtime integrity and CLI smoke check pass"
|
|
2350
|
+
: `project Mock System CLI smoke check failed: ${mockSmoke.stderr.trim() || "no valid output"}`,
|
|
2351
|
+
});
|
|
2352
|
+
checks.push(managedRuntimeDoctorCheck(target, {
|
|
2353
|
+
directory: "frontend-guard",
|
|
2354
|
+
expectedSources: expectedFrontendGuardSources,
|
|
2355
|
+
manifestDigest: manifest?.managed_assets?.frontend_guard_runtime?.sha256,
|
|
2356
|
+
cli: "frontend-guard.mjs",
|
|
2357
|
+
helpPattern: /frontend-guard <check-file\|verify-diff>/,
|
|
2358
|
+
scope: "frontend-guard",
|
|
2359
|
+
label: "frontend guard",
|
|
2360
|
+
}));
|
|
2361
|
+
checks.push(managedRuntimeDoctorCheck(target, {
|
|
2362
|
+
directory: "ui-acceptance",
|
|
2363
|
+
expectedSources: expectedUiAcceptanceSources,
|
|
2364
|
+
manifestDigest: manifest?.managed_assets?.ui_acceptance_runtime?.sha256,
|
|
2365
|
+
cli: "ui-acceptance.mjs",
|
|
2366
|
+
helpPattern: /ui-acceptance <plan\|verify>/,
|
|
2367
|
+
scope: "ui-acceptance",
|
|
2368
|
+
label: "UI Acceptance",
|
|
2369
|
+
}));
|
|
1045
2370
|
const evolutionVerify = spawnSync(
|
|
1046
2371
|
process.execPath,
|
|
1047
2372
|
[path.join(installedEvolutionRoot, "evolve.mjs"), "verify"],
|
|
@@ -1083,6 +2408,7 @@ export function doctorProject(target, options = {}) {
|
|
|
1083
2408
|
: "";
|
|
1084
2409
|
const missingAttributes = GIT_ATTRIBUTE_ENTRIES.filter((entry) =>
|
|
1085
2410
|
!gitattributes.split(/\r?\n/).some((line) => line.trim() === entry));
|
|
2411
|
+
const rawAttributePolicy = rawGitAttributePolicy(gitattributes);
|
|
1086
2412
|
const attributeTargets = [...new Set([
|
|
1087
2413
|
".workflow/delivery/workflow-state.yaml",
|
|
1088
2414
|
".agents/skills/ai-delivery-orchestrate/SKILL.md",
|
|
@@ -1090,43 +2416,37 @@ export function doctorProject(target, options = {}) {
|
|
|
1090
2416
|
"AGENTS.md",
|
|
1091
2417
|
".gitignore",
|
|
1092
2418
|
".gitattributes",
|
|
1093
|
-
...
|
|
2419
|
+
...RAW_GIT_ATTRIBUTE_TARGETS,
|
|
2420
|
+
...filesUnderExcluding(
|
|
2421
|
+
path.join(target, ".workflow"),
|
|
2422
|
+
PRESERVED_TRANSIENT_ROOTS.map((root) => installPath(target, root)),
|
|
2423
|
+
)
|
|
1094
2424
|
.map((file) => path.relative(target, file).replaceAll("\\", "/")),
|
|
1095
2425
|
...filesUnder(path.join(target, ".agents", "skills"))
|
|
1096
2426
|
.map((file) => path.relative(target, file).replaceAll("\\", "/"))
|
|
1097
2427
|
.filter((file) => file.startsWith(".agents/skills/ai-delivery-")),
|
|
1098
2428
|
])];
|
|
1099
|
-
const
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
["-c", "core.quotePath=false", "check-attr", "text", "eol", "--", ...batch],
|
|
1106
|
-
{ cwd: target, encoding: "utf8", windowsHide: true },
|
|
1107
|
-
);
|
|
1108
|
-
if (effectiveAttributes.status !== 0) {
|
|
1109
|
-
effectiveAttributesFailed = true;
|
|
1110
|
-
break;
|
|
1111
|
-
}
|
|
1112
|
-
for (const line of effectiveAttributes.stdout.split(/\r?\n/).filter(Boolean)) {
|
|
1113
|
-
const match = line.match(/^(.*): (text|eol): (.*)$/);
|
|
1114
|
-
if (match) attributeValues.set(`${match[1]}:${match[2]}`, match[3]);
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
const invalidEffectiveAttributes = !effectiveAttributesFailed
|
|
1118
|
-
? attributeTargets.filter((file) => {
|
|
1119
|
-
const text = attributeValues.get(`${file}:text`);
|
|
1120
|
-
return !new Set(["set", "auto"]).has(text) || attributeValues.get(`${file}:eol`) !== "lf";
|
|
1121
|
-
})
|
|
1122
|
-
: [...attributeTargets];
|
|
2429
|
+
const attributeAudit = effectiveGitAttributeAudit(
|
|
2430
|
+
target,
|
|
2431
|
+
attributeTargets,
|
|
2432
|
+
rawAttributePolicy.legacyPatterns,
|
|
2433
|
+
);
|
|
2434
|
+
const omittedInvalidAttributes = attributeAudit.invalidCount - attributeAudit.invalid.length;
|
|
1123
2435
|
checks.push({
|
|
1124
|
-
ok: missingAttributes.length === 0
|
|
2436
|
+
ok: missingAttributes.length === 0
|
|
2437
|
+
&& rawAttributePolicy.invalid.length === 0
|
|
2438
|
+
&& !attributeAudit.failed
|
|
2439
|
+
&& attributeAudit.invalidCount === 0,
|
|
1125
2440
|
message: missingAttributes.length
|
|
1126
2441
|
? `project .gitattributes is missing byte-stable workflow rules: ${missingAttributes.join(", ")}`
|
|
1127
|
-
:
|
|
1128
|
-
? `project .gitattributes is overridden
|
|
1129
|
-
|
|
2442
|
+
: rawAttributePolicy.invalid.length
|
|
2443
|
+
? `project .gitattributes is overridden by broad raw-byte rules or unsupported raw-byte rules: ${rawAttributePolicy.invalid.join(", ")}`
|
|
2444
|
+
: attributeAudit.failed
|
|
2445
|
+
? `project .gitattributes effective check failed: ${attributeAudit.error}`
|
|
2446
|
+
: attributeAudit.invalidCount
|
|
2447
|
+
? `project .gitattributes is overridden for checksummed files: ${attributeAudit.invalid.join(", ")}`
|
|
2448
|
+
+ (omittedInvalidAttributes ? ` (+${omittedInvalidAttributes} omitted)` : "")
|
|
2449
|
+
: "project .gitattributes keeps managed checksummed files byte-stable",
|
|
1130
2450
|
});
|
|
1131
2451
|
if (workspace) {
|
|
1132
2452
|
const expectedIgnore = `/${workspace.configuration.code_repository.path}/`;
|