@deftai/directive 0.126.0-beta.1 → 0.126.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/dist/agents-refresh.d.ts +11 -0
- package/dist/agents-refresh.js +90 -0
- package/dist/authz.d.ts +7 -0
- package/dist/authz.js +732 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.js +3 -4333
- package/dist/branch-fixtures.d.ts +45 -0
- package/dist/branch-fixtures.js +156 -0
- package/dist/cache.d.ts +4 -0
- package/dist/cache.js +10 -0
- package/dist/capacity-backfill.d.ts +3 -0
- package/dist/capacity-backfill.js +9 -0
- package/dist/capacity-show.d.ts +3 -0
- package/dist/capacity-show.js +9 -0
- package/dist/change-init.d.ts +3 -0
- package/dist/change-init.js +59 -0
- package/dist/changelog-check.d.ts +3 -0
- package/dist/changelog-check.js +52 -0
- package/dist/check.d.ts +15 -0
- package/dist/check.js +150 -0
- package/dist/cli-package-version.d.ts +3 -0
- package/dist/cli-package-version.js +23 -0
- package/dist/cli-router/index.d.ts +8 -0
- package/dist/cli-router/index.js +44 -0
- package/dist/cli-router/route-argv.d.ts +40 -0
- package/dist/cli-router/route-argv.js +371 -0
- package/dist/codebase-default-extractor.d.ts +3 -0
- package/dist/codebase-default-extractor.js +9 -0
- package/dist/codebase-map-fresh.d.ts +3 -0
- package/dist/codebase-map-fresh.js +9 -0
- package/dist/codebase-map.d.ts +3 -0
- package/dist/codebase-map.js +9 -0
- package/dist/codebase-projection-registry.d.ts +3 -0
- package/dist/codebase-projection-registry.js +9 -0
- package/dist/codebase-provider.d.ts +3 -0
- package/dist/codebase-provider.js +9 -0
- package/dist/commit-lint.d.ts +3 -0
- package/dist/commit-lint.js +44 -0
- package/dist/content-validate-cli/validate-links.d.ts +11 -0
- package/dist/content-validate-cli/validate-links.js +37 -0
- package/dist/content-validate-cli/validate-strategy-output.d.ts +13 -0
- package/dist/content-validate-cli/validate-strategy-output.js +56 -0
- package/dist/content-validate-cli/verify-capacity.d.ts +12 -0
- package/dist/content-validate-cli/verify-capacity.js +54 -0
- package/dist/coverage-hotspots.d.ts +21 -0
- package/dist/coverage-hotspots.js +198 -0
- package/dist/design-critique-spend-resolve.d.ts +18 -0
- package/dist/design-critique-spend-resolve.js +191 -0
- package/dist/directive-finish-loop.d.ts +3 -0
- package/dist/directive-finish-loop.js +10 -0
- package/dist/dispatch.d.ts +193 -0
- package/dist/dispatch.js +3091 -0
- package/dist/doctor.d.ts +19 -0
- package/dist/doctor.js +155 -0
- package/dist/entrypoint.d.ts +6 -0
- package/dist/entrypoint.js +19 -0
- package/dist/escalation-cli.d.ts +4 -0
- package/dist/escalation-cli.js +301 -0
- package/dist/eval-health.d.ts +13 -0
- package/dist/eval-health.js +63 -0
- package/dist/eval-report.d.ts +15 -0
- package/dist/eval-report.js +97 -0
- package/dist/eval-run.d.ts +17 -0
- package/dist/eval-run.js +122 -0
- package/dist/eval-triggers.d.ts +12 -0
- package/dist/eval-triggers.js +56 -0
- package/dist/framework-check-updates.d.ts +10 -0
- package/dist/framework-check-updates.js +68 -0
- package/dist/gates-cli/_helpers.d.ts +24 -0
- package/dist/gates-cli/_helpers.js +115 -0
- package/dist/gates-cli/verify-bridge-drift.d.ts +11 -0
- package/dist/gates-cli/verify-bridge-drift.js +47 -0
- package/dist/gates-cli/verify-go-freeze.d.ts +11 -0
- package/dist/gates-cli/verify-go-freeze.js +48 -0
- package/dist/hook-bin.d.ts +3 -0
- package/dist/hook-bin.js +11 -0
- package/dist/hook-dispatch.d.ts +80 -0
- package/dist/hook-dispatch.js +186 -0
- package/dist/human-presence-mint.d.ts +70 -0
- package/dist/human-presence-mint.js +204 -0
- package/dist/identity-banner.d.ts +13 -0
- package/dist/identity-banner.js +19 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +14 -0
- package/dist/init-cli/constants.d.ts +42 -0
- package/dist/init-cli/constants.js +42 -0
- package/dist/init-cli/help.d.ts +9 -0
- package/dist/init-cli/help.js +43 -0
- package/dist/init-cli/init.d.ts +26 -0
- package/dist/init-cli/init.js +80 -0
- package/dist/init-cli/migrate.d.ts +3 -0
- package/dist/init-cli/migrate.js +51 -0
- package/dist/init-cli/resolve-binary.d.ts +34 -0
- package/dist/init-cli/resolve-binary.js +78 -0
- package/dist/init-cli/run-deft-install.d.ts +23 -0
- package/dist/init-cli/run-deft-install.js +71 -0
- package/dist/init-cli/update.d.ts +5 -0
- package/dist/init-cli/update.js +38 -0
- package/dist/install-cli/coverage-map.d.ts +14 -0
- package/dist/install-cli/coverage-map.js +113 -0
- package/dist/install-uninstall.d.ts +3 -0
- package/dist/install-uninstall.js +44 -0
- package/dist/install-upgrade.d.ts +41 -0
- package/dist/install-upgrade.js +86 -0
- package/dist/lifecycle-cli/coverage-map.d.ts +16 -0
- package/dist/lifecycle-cli/coverage-map.js +254 -0
- package/dist/lifecycle-cli/helpers.d.ts +8 -0
- package/dist/lifecycle-cli/helpers.js +20 -0
- package/dist/lifecycle-event.d.ts +11 -0
- package/dist/lifecycle-event.js +45 -0
- package/dist/lifecycle-stats.d.ts +12 -0
- package/dist/lifecycle-stats.js +100 -0
- package/dist/migrate-category-b.d.ts +9 -0
- package/dist/migrate-category-b.js +52 -0
- package/dist/migrate-preflight.d.ts +10 -0
- package/dist/migrate-preflight.js +85 -0
- package/dist/migrate-xbrief.d.ts +11 -0
- package/dist/migrate-xbrief.js +88 -0
- package/dist/occupancy-grant.d.ts +18 -0
- package/dist/occupancy-grant.js +121 -0
- package/dist/occupancy-heartbeat.d.ts +8 -0
- package/dist/occupancy-heartbeat.js +63 -0
- package/dist/occupancy-release.d.ts +23 -0
- package/dist/occupancy-release.js +102 -0
- package/dist/occupancy-steal.d.ts +10 -0
- package/dist/occupancy-steal.js +83 -0
- package/dist/occupancy-unrecognized.d.ts +3 -0
- package/dist/occupancy-unrecognized.js +5 -0
- package/dist/orchestration-cli/coverage-map.d.ts +21 -0
- package/dist/orchestration-cli/coverage-map.js +225 -0
- package/dist/orchestration-cli/helpers.d.ts +11 -0
- package/dist/orchestration-cli/helpers.js +35 -0
- package/dist/ownership-doctor.d.ts +10 -0
- package/dist/ownership-doctor.js +96 -0
- package/dist/ownership-fix.d.ts +10 -0
- package/dist/ownership-fix.js +93 -0
- package/dist/plan-sequence.d.ts +14 -0
- package/dist/plan-sequence.js +258 -0
- package/dist/policy-fixtures.d.ts +33 -0
- package/dist/policy-fixtures.js +118 -0
- package/dist/policy.d.ts +37 -0
- package/dist/policy.js +744 -0
- package/dist/pr-closing-keywords.d.ts +3 -0
- package/dist/pr-closing-keywords.js +10 -0
- package/dist/pr-finish-loop.d.ts +3 -0
- package/dist/pr-finish-loop.js +10 -0
- package/dist/pr-merge-readiness.d.ts +3 -0
- package/dist/pr-merge-readiness.js +10 -0
- package/dist/pr-monitor.d.ts +3 -0
- package/dist/pr-monitor.js +10 -0
- package/dist/pr-protected-issues.d.ts +3 -0
- package/dist/pr-protected-issues.js +10 -0
- package/dist/pr-wait-mergeable.d.ts +3 -0
- package/dist/pr-wait-mergeable.js +10 -0
- package/dist/pr-watch.d.ts +3 -0
- package/dist/pr-watch.js +10 -0
- package/dist/pre-pr-run.d.ts +39 -0
- package/dist/pre-pr-run.js +514 -0
- package/dist/preflight-cache.d.ts +18 -0
- package/dist/preflight-cache.js +113 -0
- package/dist/preflight-gh.d.ts +15 -0
- package/dist/preflight-gh.js +143 -0
- package/dist/probe-session.d.ts +3 -0
- package/dist/probe-session.js +10 -0
- package/dist/release-e2e-fixtures.d.ts +29 -0
- package/dist/release-e2e-fixtures.js +38 -0
- package/dist/release-e2e.d.ts +3 -0
- package/dist/release-e2e.js +10 -0
- package/dist/release-publish.d.ts +3 -0
- package/dist/release-publish.js +10 -0
- package/dist/release-rollback.d.ts +3 -0
- package/dist/release-rollback.js +10 -0
- package/dist/release-wait-npm.d.ts +3 -0
- package/dist/release-wait-npm.js +10 -0
- package/dist/release.d.ts +3 -0
- package/dist/release.js +10 -0
- package/dist/render-cli/deft-ts-runner.d.ts +17 -0
- package/dist/render-cli/deft-ts-runner.js +19 -0
- package/dist/render-cli/export-spec-cli.d.ts +2 -0
- package/dist/render-cli/export-spec-cli.js +6 -0
- package/dist/render-cli/prd-render-cli.d.ts +2 -0
- package/dist/render-cli/prd-render-cli.js +37 -0
- package/dist/render-cli/project-render-cli.d.ts +2 -0
- package/dist/render-cli/project-render-cli.js +6 -0
- package/dist/render-cli/rule-map-cli.d.ts +2 -0
- package/dist/render-cli/rule-map-cli.js +5 -0
- package/dist/render-cli/spec-reconstruct-cli.d.ts +3 -0
- package/dist/render-cli/spec-reconstruct-cli.js +15 -0
- package/dist/render-cli/spec-render-cli.d.ts +2 -0
- package/dist/render-cli/spec-render-cli.js +39 -0
- package/dist/render-cli/spec-validate-cli.d.ts +2 -0
- package/dist/render-cli/spec-validate-cli.js +38 -0
- package/dist/review-monitor-register.d.ts +19 -0
- package/dist/review-monitor-register.js +183 -0
- package/dist/review-monitor-release.d.ts +14 -0
- package/dist/review-monitor-release.js +123 -0
- package/dist/scm-sync-default.d.ts +34 -0
- package/dist/scm-sync-default.js +210 -0
- package/dist/scope-lifecycle.d.ts +4 -0
- package/dist/scope-lifecycle.js +34 -0
- package/dist/scope-record-approved-scope.d.ts +29 -0
- package/dist/scope-record-approved-scope.js +319 -0
- package/dist/scope-record-intent-constraint.d.ts +16 -0
- package/dist/scope-record-intent-constraint.js +202 -0
- package/dist/scope-record-observable-scope.d.ts +16 -0
- package/dist/scope-record-observable-scope.js +204 -0
- package/dist/session-ready.d.ts +15 -0
- package/dist/session-ready.js +144 -0
- package/dist/session-start.d.ts +56 -0
- package/dist/session-start.js +581 -0
- package/dist/slice.d.ts +4 -0
- package/dist/slice.js +34 -0
- package/dist/story-ready-fixtures.d.ts +47 -0
- package/dist/story-ready-fixtures.js +137 -0
- package/dist/subagent-monitor.d.ts +3 -0
- package/dist/subagent-monitor.js +10 -0
- package/dist/subagent-pre-cancel.d.ts +21 -0
- package/dist/subagent-pre-cancel.js +240 -0
- package/dist/subagent-steer-write.d.ts +41 -0
- package/dist/subagent-steer-write.js +333 -0
- package/dist/toolchain-check.d.ts +8 -0
- package/dist/toolchain-check.js +54 -0
- package/dist/triage-actions.d.ts +23 -0
- package/dist/triage-actions.js +233 -0
- package/dist/triage-aux-a-fixtures.d.ts +33 -0
- package/dist/triage-aux-a-fixtures.js +107 -0
- package/dist/triage-aux-b-fixtures.d.ts +30 -0
- package/dist/triage-aux-b-fixtures.js +167 -0
- package/dist/triage-bootstrap-fixtures.d.ts +42 -0
- package/dist/triage-bootstrap-fixtures.js +139 -0
- package/dist/triage-bootstrap.d.ts +24 -0
- package/dist/triage-bootstrap.js +175 -0
- package/dist/triage-bulk.d.ts +17 -0
- package/dist/triage-bulk.js +154 -0
- package/dist/triage-classify-fixtures.d.ts +38 -0
- package/dist/triage-classify-fixtures.js +137 -0
- package/dist/triage-classify.d.ts +45 -0
- package/dist/triage-classify.js +287 -0
- package/dist/triage-evaluate.d.ts +14 -0
- package/dist/triage-evaluate.js +153 -0
- package/dist/triage-help.d.ts +3 -0
- package/dist/triage-help.js +10 -0
- package/dist/triage-queue-fixtures.d.ts +59 -0
- package/dist/triage-queue-fixtures.js +284 -0
- package/dist/triage-queue.d.ts +38 -0
- package/dist/triage-queue.js +391 -0
- package/dist/triage-reconcile.d.ts +11 -0
- package/dist/triage-reconcile.js +76 -0
- package/dist/triage-refresh.d.ts +7 -0
- package/dist/triage-refresh.js +37 -0
- package/dist/triage-scope-drift.d.ts +12 -0
- package/dist/triage-scope-drift.js +117 -0
- package/dist/triage-scope-fixtures.d.ts +32 -0
- package/dist/triage-scope-fixtures.js +131 -0
- package/dist/triage-scope.d.ts +4 -0
- package/dist/triage-scope.js +34 -0
- package/dist/triage-smoketest.d.ts +3 -0
- package/dist/triage-smoketest.js +43 -0
- package/dist/triage-strip-withdrawn.d.ts +21 -0
- package/dist/triage-strip-withdrawn.js +102 -0
- package/dist/triage-subscribe.d.ts +15 -0
- package/dist/triage-subscribe.js +157 -0
- package/dist/triage-summary.d.ts +14 -0
- package/dist/triage-summary.js +83 -0
- package/dist/triage-welcome.d.ts +14 -0
- package/dist/triage-welcome.js +121 -0
- package/dist/ts-check-lane.d.ts +9 -0
- package/dist/ts-check-lane.js +49 -0
- package/dist/umbrella-current-shape.d.ts +9 -0
- package/dist/umbrella-current-shape.js +56 -0
- package/dist/vbrief-activate.d.ts +3 -0
- package/dist/vbrief-activate.js +7 -0
- package/dist/vbrief-build.d.ts +3 -0
- package/dist/vbrief-build.js +8 -0
- package/dist/vbrief-preflight-fixtures.d.ts +32 -0
- package/dist/vbrief-preflight-fixtures.js +79 -0
- package/dist/vbrief-preflight.d.ts +13 -0
- package/dist/vbrief-preflight.js +141 -0
- package/dist/vbrief-reconcile.d.ts +3 -0
- package/dist/vbrief-reconcile.js +7 -0
- package/dist/vbrief-validate.d.ts +3 -0
- package/dist/vbrief-validate.js +8 -0
- package/dist/vbrief-validation.d.ts +3 -0
- package/dist/vbrief-validation.js +8 -0
- package/dist/verify-ac.d.ts +28 -0
- package/dist/verify-ac.js +439 -0
- package/dist/verify-agents-md-advisory.d.ts +19 -0
- package/dist/verify-agents-md-advisory.js +65 -0
- package/dist/verify-agents-md-budget.d.ts +12 -0
- package/dist/verify-agents-md-budget.js +71 -0
- package/dist/verify-branch.d.ts +14 -0
- package/dist/verify-branch.js +81 -0
- package/dist/verify-class-checks.d.ts +11 -0
- package/dist/verify-class-checks.js +71 -0
- package/dist/verify-cohort-review-monitors.d.ts +5 -0
- package/dist/verify-cohort-review-monitors.js +11 -0
- package/dist/verify-completed-tracked.d.ts +19 -0
- package/dist/verify-completed-tracked.js +202 -0
- package/dist/verify-completed-write-guard.d.ts +13 -0
- package/dist/verify-completed-write-guard.js +84 -0
- package/dist/verify-consumer-check-contract.d.ts +12 -0
- package/dist/verify-consumer-check-contract.js +70 -0
- package/dist/verify-consumer-header-placeholder.d.ts +12 -0
- package/dist/verify-consumer-header-placeholder.js +73 -0
- package/dist/verify-consumer-test-lane.d.ts +12 -0
- package/dist/verify-consumer-test-lane.js +57 -0
- package/dist/verify-durable-effect-acquisition.d.ts +13 -0
- package/dist/verify-durable-effect-acquisition.js +70 -0
- package/dist/verify-encoding.d.ts +15 -0
- package/dist/verify-encoding.js +83 -0
- package/dist/verify-eval-health-relocation.d.ts +16 -0
- package/dist/verify-eval-health-relocation.js +93 -0
- package/dist/verify-eval-triggers-relocation.d.ts +14 -0
- package/dist/verify-eval-triggers-relocation.js +74 -0
- package/dist/verify-evaluator-surface.d.ts +15 -0
- package/dist/verify-evaluator-surface.js +87 -0
- package/dist/verify-forward-coverage.d.ts +19 -0
- package/dist/verify-forward-coverage.js +144 -0
- package/dist/verify-hooks-installed.d.ts +24 -0
- package/dist/verify-hooks-installed.js +140 -0
- package/dist/verify-intent-constraint.d.ts +15 -0
- package/dist/verify-intent-constraint.js +86 -0
- package/dist/verify-investigation.d.ts +3 -0
- package/dist/verify-investigation.js +10 -0
- package/dist/verify-judgment-gates.d.ts +3 -0
- package/dist/verify-judgment-gates.js +10 -0
- package/dist/verify-l4-owner.d.ts +15 -0
- package/dist/verify-l4-owner.js +132 -0
- package/dist/verify-lifecycle-visible.d.ts +13 -0
- package/dist/verify-lifecycle-visible.js +68 -0
- package/dist/verify-literal-ac.d.ts +14 -0
- package/dist/verify-literal-ac.js +168 -0
- package/dist/verify-no-task-runtime.d.ts +3 -0
- package/dist/verify-no-task-runtime.js +16 -0
- package/dist/verify-observable-scope.d.ts +15 -0
- package/dist/verify-observable-scope.js +86 -0
- package/dist/verify-operator-scope-limit.d.ts +17 -0
- package/dist/verify-operator-scope-limit.js +285 -0
- package/dist/verify-orphan-active.d.ts +17 -0
- package/dist/verify-orphan-active.js +135 -0
- package/dist/verify-ownership.d.ts +10 -0
- package/dist/verify-ownership.js +90 -0
- package/dist/verify-persisted-planning-narratives.d.ts +12 -0
- package/dist/verify-persisted-planning-narratives.js +82 -0
- package/dist/verify-plan-sequence.d.ts +14 -0
- package/dist/verify-plan-sequence.js +138 -0
- package/dist/verify-pr-closeout-attestable.d.ts +15 -0
- package/dist/verify-pr-closeout-attestable.js +114 -0
- package/dist/verify-presentation-ceiling.d.ts +11 -0
- package/dist/verify-presentation-ceiling.js +71 -0
- package/dist/verify-presentation-coverage.d.ts +14 -0
- package/dist/verify-presentation-coverage.js +90 -0
- package/dist/verify-review-monitor.d.ts +25 -0
- package/dist/verify-review-monitor.js +341 -0
- package/dist/verify-scope-provenance.d.ts +16 -0
- package/dist/verify-scope-provenance.js +91 -0
- package/dist/verify-session-ritual.d.ts +23 -0
- package/dist/verify-session-ritual.js +144 -0
- package/dist/verify-source-cli/rule-ownership-lint.d.ts +12 -0
- package/dist/verify-source-cli/rule-ownership-lint.js +61 -0
- package/dist/verify-source-cli/verify-biome-config.d.ts +11 -0
- package/dist/verify-source-cli/verify-biome-config.js +46 -0
- package/dist/verify-source-cli/verify-closing-keywords.d.ts +35 -0
- package/dist/verify-source-cli/verify-closing-keywords.js +139 -0
- package/dist/verify-source-cli/verify-consumer-hard-stops.d.ts +16 -0
- package/dist/verify-source-cli/verify-consumer-hard-stops.js +99 -0
- package/dist/verify-source-cli/verify-contained-writes.d.ts +13 -0
- package/dist/verify-source-cli/verify-contained-writes.js +67 -0
- package/dist/verify-source-cli/verify-content-manifest.d.ts +12 -0
- package/dist/verify-source-cli/verify-content-manifest.js +61 -0
- package/dist/verify-source-cli/verify-contract-drift.d.ts +11 -0
- package/dist/verify-source-cli/verify-contract-drift.js +47 -0
- package/dist/verify-source-cli/verify-cursor-tier1.d.ts +12 -0
- package/dist/verify-source-cli/verify-cursor-tier1.js +51 -0
- package/dist/verify-source-cli/verify-deposit-closure.d.ts +12 -0
- package/dist/verify-source-cli/verify-deposit-closure.js +52 -0
- package/dist/verify-source-cli/verify-openclaw-tier1.d.ts +12 -0
- package/dist/verify-source-cli/verify-openclaw-tier1.js +51 -0
- package/dist/verify-source-cli/verify-scm-boundary.d.ts +13 -0
- package/dist/verify-source-cli/verify-scm-boundary.js +66 -0
- package/dist/verify-source-cli/verify-semantic-single-source.d.ts +11 -0
- package/dist/verify-source-cli/verify-semantic-single-source.js +49 -0
- package/dist/verify-source-cli/verify-skill-external-fetch-gate.d.ts +11 -0
- package/dist/verify-source-cli/verify-skill-external-fetch-gate.js +46 -0
- package/dist/verify-source-cli/verify-spec-drift.d.ts +3 -0
- package/dist/verify-source-cli/verify-spec-drift.js +15 -0
- package/dist/verify-source-cli/verify-spec-prd-fresh.d.ts +3 -0
- package/dist/verify-source-cli/verify-spec-prd-fresh.js +15 -0
- package/dist/verify-source-cli/verify-stubs.d.ts +11 -0
- package/dist/verify-source-cli/verify-stubs.js +27 -0
- package/dist/verify-source-cli/verify-telemetry-coverage.d.ts +13 -0
- package/dist/verify-source-cli/verify-telemetry-coverage.js +67 -0
- package/dist/verify-source-cli/verify-xbrief-drift.d.ts +15 -0
- package/dist/verify-source-cli/verify-xbrief-drift.js +80 -0
- package/dist/verify-story-ready.d.ts +18 -0
- package/dist/verify-story-ready.js +190 -0
- package/dist/verify-subagent-alive.d.ts +21 -0
- package/dist/verify-subagent-alive.js +219 -0
- package/dist/verify-subagent-steer.d.ts +19 -0
- package/dist/verify-subagent-steer.js +136 -0
- package/dist/verify-test-boundary.d.ts +12 -0
- package/dist/verify-test-boundary.js +80 -0
- package/dist/verify-tools.d.ts +13 -0
- package/dist/verify-tools.js +67 -0
- package/dist/verify-wip-cap.d.ts +13 -0
- package/dist/verify-wip-cap.js +62 -0
- package/dist/windows-bin-ps1.d.ts +39 -0
- package/dist/windows-bin-ps1.js +226 -0
- package/dist/wip-cap-fixtures.d.ts +37 -0
- package/dist/wip-cap-fixtures.js +122 -0
- package/dist/wsl-ownership-guard.d.ts +2 -0
- package/dist/wsl-ownership-guard.js +15 -0
- package/dist/xbrief-create.d.ts +3 -0
- package/dist/xbrief-create.js +9 -0
- package/dist/xbrief-verify.d.ts +3 -0
- package/dist/xbrief-verify.js +9 -0
- package/package.json +23 -12
- package/scripts/remove-windows-ps1-shims.mjs +26 -0
- package/bin/deft-hook +0 -14
- package/bin/deft-hook.cmd +0 -14
package/dist/bin.js
CHANGED
|
@@ -1,4335 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { basename } from "node:path";
|
|
5
|
-
|
|
6
|
-
// packages/cli/src/hook-launcher.ts
|
|
7
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
8
|
-
import { join as join2 } from "node:path";
|
|
9
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
10
|
-
|
|
11
|
-
// packages/cli/src/constants.ts
|
|
12
|
-
var LAUNCHER_PACKAGE_NAME = "@deftai/directive";
|
|
13
|
-
var RUNTIME_PACKAGE_NAME = "@deftai/directive-runtime";
|
|
14
|
-
var CONTRACT_NUMBER = 1;
|
|
15
|
-
var LAUNCHER_MAX_SUPPORTED_CONTRACT = CONTRACT_NUMBER;
|
|
16
|
-
var CONTRACT_PACKAGE_JSON_KEY = "directive";
|
|
17
|
-
var EXIT_CODES = {
|
|
18
|
-
SUCCESS: 0,
|
|
19
|
-
FAILURE: 1,
|
|
20
|
-
USAGE: 2,
|
|
21
|
-
INTERRUPTED: 130
|
|
22
|
-
};
|
|
23
|
-
var NODE_ENGINES_FLOOR_MAJOR = 20;
|
|
24
|
-
var NODE_SUPPORTED_MAJOR = 22;
|
|
25
|
-
var NODE_EOL_NOTE = "Node 20 reached end of life on 30 April 2026. Directive is tested on Node 22 and 24 and runs on 20 on a best-effort basis.";
|
|
26
|
-
var RECOVERY_GUIDANCE = "npm i -g @deftai/directive, then directive install --repair";
|
|
27
|
-
var ENV_PROJECT_ROOT = "DIRECTIVE_PROJECT_ROOT";
|
|
28
|
-
var ENV_LAUNCHER_VERSION = "DIRECTIVE_LAUNCHER_VERSION";
|
|
29
|
-
var INSTALL_MANAGEMENT_COMMANDS = [
|
|
30
|
-
"install",
|
|
31
|
-
"upgrade",
|
|
32
|
-
"uninstall",
|
|
33
|
-
"doctor"
|
|
34
|
-
];
|
|
35
|
-
var INSTALL_ALIASES = {
|
|
36
|
-
init: "install",
|
|
37
|
-
update: "upgrade",
|
|
38
|
-
remove: "uninstall"
|
|
39
|
-
};
|
|
40
|
-
var RETIRED_INSTALL_VERBS = /* @__PURE__ */ new Set([
|
|
41
|
-
"sync",
|
|
42
|
-
"bootstrap",
|
|
43
|
-
"migrate",
|
|
44
|
-
"install-upgrade",
|
|
45
|
-
"install-uninstall"
|
|
46
|
-
]);
|
|
47
|
-
|
|
48
|
-
// packages/cli/src/discover.ts
|
|
49
|
-
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
50
|
-
import { homedir } from "node:os";
|
|
51
|
-
import { isAbsolute, resolve } from "node:path";
|
|
52
|
-
import { spawnSync } from "node:child_process";
|
|
53
|
-
function realpathOrResolve(path) {
|
|
54
|
-
const resolved = resolve(path);
|
|
55
|
-
try {
|
|
56
|
-
return realpathSync(resolved);
|
|
57
|
-
} catch {
|
|
58
|
-
return resolved;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
function isFilesystemRoot(path) {
|
|
62
|
-
const resolved = realpathOrResolve(path);
|
|
63
|
-
let rootReal;
|
|
64
|
-
try {
|
|
65
|
-
rootReal = realpathSync("/");
|
|
66
|
-
} catch {
|
|
67
|
-
rootReal = resolve("/");
|
|
68
|
-
}
|
|
69
|
-
if (resolved === rootReal || resolved === resolve("/")) return true;
|
|
70
|
-
return /^[A-Za-z]:[\\/]?$/.test(resolved);
|
|
71
|
-
}
|
|
72
|
-
function isUserHome(path) {
|
|
73
|
-
try {
|
|
74
|
-
return realpathOrResolve(path) === realpathOrResolve(homedir());
|
|
75
|
-
} catch {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
function runGitShowToplevel(cwd) {
|
|
80
|
-
const result = spawnSync("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
|
|
81
|
-
encoding: "utf8",
|
|
82
|
-
env: process.env
|
|
83
|
-
});
|
|
84
|
-
return {
|
|
85
|
-
code: result.status ?? 1,
|
|
86
|
-
stdout: (result.stdout ?? "").trim(),
|
|
87
|
-
stderr: (result.stderr ?? "").trim()
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
function discoverProject(options = {}) {
|
|
91
|
-
const cwd = resolve(options.cwd ?? process.cwd());
|
|
92
|
-
const rootFlagLexical = options.rootFlag ? resolve(options.rootFlag) : null;
|
|
93
|
-
if (rootFlagLexical !== null) {
|
|
94
|
-
if (!existsSync(rootFlagLexical) || !statSync(rootFlagLexical).isDirectory()) {
|
|
95
|
-
return {
|
|
96
|
-
ok: false,
|
|
97
|
-
kind: "missing_root",
|
|
98
|
-
message: `--root ${rootFlagLexical} is not an existing directory.`
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
const rootFlag = realpathOrResolve(rootFlagLexical);
|
|
102
|
-
if (!options.allowRootOrHome) {
|
|
103
|
-
if (isFilesystemRoot(rootFlag)) {
|
|
104
|
-
return {
|
|
105
|
-
ok: false,
|
|
106
|
-
kind: "refused_root",
|
|
107
|
-
message: "Refusing filesystem root as install destination. Pass a project path deliberately."
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
if (isUserHome(rootFlag)) {
|
|
111
|
-
return {
|
|
112
|
-
ok: false,
|
|
113
|
-
kind: "refused_home",
|
|
114
|
-
message: "Refusing home directory as install destination. Pass a project path deliberately."
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
const git2 = runGitShowToplevel(rootFlagLexical);
|
|
119
|
-
if (git2.code === 0 && git2.stdout.length > 0) {
|
|
120
|
-
const toplevel = realpathOrResolve(git2.stdout);
|
|
121
|
-
if (toplevel !== rootFlag) {
|
|
122
|
-
return {
|
|
123
|
-
ok: false,
|
|
124
|
-
kind: "root_not_git_toplevel",
|
|
125
|
-
message: `--root must be the Git working-tree root (${toplevel}); got ${rootFlagLexical}.`
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
ok: true,
|
|
131
|
-
root: rootFlag,
|
|
132
|
-
movedUp: rootFlag !== realpathOrResolve(cwd) && rootFlagLexical !== cwd,
|
|
133
|
-
viaGit: git2.code === 0
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
const git = runGitShowToplevel(cwd);
|
|
137
|
-
if (git.code === 0 && git.stdout.length > 0) {
|
|
138
|
-
const root2 = resolve(git.stdout);
|
|
139
|
-
if (!options.allowRootOrHome) {
|
|
140
|
-
if (isFilesystemRoot(root2) || isUserHome(root2)) {
|
|
141
|
-
return {
|
|
142
|
-
ok: false,
|
|
143
|
-
kind: isFilesystemRoot(root2) ? "refused_root" : "refused_home",
|
|
144
|
-
message: `Refusing ${isFilesystemRoot(root2) ? "filesystem root" : "home directory"} as project root.`
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
ok: true,
|
|
150
|
-
root: root2,
|
|
151
|
-
movedUp: root2 !== cwd,
|
|
152
|
-
viaGit: true
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
if (git.code !== 0 && git.code !== 128 && git.stderr.length > 0) {
|
|
156
|
-
const missingGit = (git.stderr.includes("ENOENT") || git.code === 127) && !existsSync(resolve(cwd, ".git"));
|
|
157
|
-
if (!missingGit && git.code !== 128) {
|
|
158
|
-
if (existsSync(resolve(cwd, ".git"))) {
|
|
159
|
-
return {
|
|
160
|
-
ok: false,
|
|
161
|
-
kind: "git_error",
|
|
162
|
-
message: `Git error during discovery: ${git.stderr}`
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
let root = cwd;
|
|
168
|
-
let probe = cwd;
|
|
169
|
-
for (let i = 0; i < 8; i++) {
|
|
170
|
-
if (existsSync(resolve(probe, "package.json"))) {
|
|
171
|
-
root = probe;
|
|
172
|
-
break;
|
|
173
|
-
}
|
|
174
|
-
const parent = resolve(probe, "..");
|
|
175
|
-
if (parent === probe) break;
|
|
176
|
-
probe = parent;
|
|
177
|
-
}
|
|
178
|
-
if (!options.allowRootOrHome) {
|
|
179
|
-
if (isFilesystemRoot(root)) {
|
|
180
|
-
return {
|
|
181
|
-
ok: false,
|
|
182
|
-
kind: "refused_root",
|
|
183
|
-
message: "Refusing filesystem root as project root."
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
if (isUserHome(root)) {
|
|
187
|
-
return {
|
|
188
|
-
ok: false,
|
|
189
|
-
kind: "refused_home",
|
|
190
|
-
message: "Refusing home directory as project root."
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return {
|
|
195
|
-
ok: true,
|
|
196
|
-
root,
|
|
197
|
-
movedUp: root !== cwd,
|
|
198
|
-
viaGit: false
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
function takeRootFlag(argv) {
|
|
202
|
-
const rest = [];
|
|
203
|
-
let root = null;
|
|
204
|
-
for (let i = 0; i < argv.length; i++) {
|
|
205
|
-
const a = argv[i];
|
|
206
|
-
if (a === "--root") {
|
|
207
|
-
const next = argv[i + 1];
|
|
208
|
-
if (next === void 0 || next.startsWith("-")) {
|
|
209
|
-
root = "";
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
root = isAbsolute(next) ? next : resolve(next);
|
|
213
|
-
i++;
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (a.startsWith("--root=")) {
|
|
217
|
-
root = a.slice("--root=".length);
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
rest.push(a);
|
|
221
|
-
}
|
|
222
|
-
return { root, rest };
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// packages/cli/src/node-check.ts
|
|
226
|
-
function checkNodeVersion(version = process.versions.node) {
|
|
227
|
-
const major = Number.parseInt(version.split(".")[0] ?? "", 10);
|
|
228
|
-
if (!Number.isFinite(major) || major < NODE_ENGINES_FLOOR_MAJOR) {
|
|
229
|
-
return {
|
|
230
|
-
ok: false,
|
|
231
|
-
warning: null,
|
|
232
|
-
message: `Directive needs Node ${NODE_ENGINES_FLOOR_MAJOR} or later; you have ${version}.`,
|
|
233
|
-
exitCode: EXIT_CODES.FAILURE
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
if (major < NODE_SUPPORTED_MAJOR) {
|
|
237
|
-
return {
|
|
238
|
-
ok: true,
|
|
239
|
-
warning: NODE_EOL_NOTE,
|
|
240
|
-
message: null,
|
|
241
|
-
exitCode: EXIT_CODES.SUCCESS
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
return { ok: true, warning: null, message: null, exitCode: EXIT_CODES.SUCCESS };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// packages/cli/src/pin.ts
|
|
248
|
-
import { existsSync as existsSync2, readFileSync, statSync as statSync2 } from "node:fs";
|
|
249
|
-
import { join } from "node:path";
|
|
250
|
-
function readJsonObject(path) {
|
|
251
|
-
try {
|
|
252
|
-
const raw = readFileSync(path, "utf8");
|
|
253
|
-
const parsed = JSON.parse(raw);
|
|
254
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
return parsed;
|
|
258
|
-
} catch {
|
|
259
|
-
return null;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
function depMap(pkg) {
|
|
263
|
-
const out = {};
|
|
264
|
-
for (const key of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
265
|
-
const block = pkg[key];
|
|
266
|
-
if (block !== null && typeof block === "object" && !Array.isArray(block)) {
|
|
267
|
-
for (const [name, ver] of Object.entries(block)) {
|
|
268
|
-
if (typeof ver === "string") out[name] = ver;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return out;
|
|
273
|
-
}
|
|
274
|
-
function classifyPinVersion(raw) {
|
|
275
|
-
if (raw === null || raw === void 0 || raw.length === 0) {
|
|
276
|
-
return { kind: "none", raw: null, version: null };
|
|
277
|
-
}
|
|
278
|
-
const trimmed = raw.trim();
|
|
279
|
-
const exact = trimmed.startsWith("=") ? trimmed.slice(1) : trimmed;
|
|
280
|
-
if (/^\d+\.\d+\.\d+([-+][0-9A-Za-z.-]+)?$/.test(exact)) {
|
|
281
|
-
return { kind: "exact", raw: trimmed, version: exact };
|
|
282
|
-
}
|
|
283
|
-
if (/^[~^><*x]|workspace:|file:|npm:|\|\|/.test(trimmed) || trimmed.includes(" ")) {
|
|
284
|
-
return { kind: "range", raw: trimmed, version: null };
|
|
285
|
-
}
|
|
286
|
-
return { kind: "invalid", raw: trimmed, version: null };
|
|
287
|
-
}
|
|
288
|
-
function isRegularFile(path) {
|
|
289
|
-
try {
|
|
290
|
-
return statSync2(path).isFile();
|
|
291
|
-
} catch {
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
function hasCopiedCoreEvidence(projectRoot) {
|
|
296
|
-
const core = join(projectRoot, ".deft", "core");
|
|
297
|
-
if (isRegularFile(join(core, "VERSION"))) return true;
|
|
298
|
-
if (isRegularFile(join(core, "main.md"))) return true;
|
|
299
|
-
const pkgPath = join(core, "package.json");
|
|
300
|
-
if (!isRegularFile(pkgPath)) return false;
|
|
301
|
-
const pkg = readJsonObject(pkgPath);
|
|
302
|
-
return pkg !== null && pkg.deftConsumerDeposit === true;
|
|
303
|
-
}
|
|
304
|
-
function readPin(projectRoot) {
|
|
305
|
-
const pkgPath = join(projectRoot, "package.json");
|
|
306
|
-
const pkg = readJsonObject(pkgPath);
|
|
307
|
-
if (pkg === null) {
|
|
308
|
-
return { kind: "none", raw: null, version: null, oldStyle: false };
|
|
309
|
-
}
|
|
310
|
-
const deps = depMap(pkg);
|
|
311
|
-
const runtimeRaw = deps[RUNTIME_PACKAGE_NAME];
|
|
312
|
-
if (runtimeRaw !== void 0) {
|
|
313
|
-
return { ...classifyPinVersion(runtimeRaw), oldStyle: false };
|
|
314
|
-
}
|
|
315
|
-
const legacy = deps[LAUNCHER_PACKAGE_NAME];
|
|
316
|
-
if (legacy !== void 0) {
|
|
317
|
-
const classified = classifyPinVersion(legacy);
|
|
318
|
-
if (classified.kind === "exact" && hasCopiedCoreEvidence(projectRoot)) {
|
|
319
|
-
return { ...classified, oldStyle: true };
|
|
320
|
-
}
|
|
321
|
-
return { kind: "none", raw: null, version: null, oldStyle: false };
|
|
322
|
-
}
|
|
323
|
-
return { kind: "none", raw: null, version: null, oldStyle: false };
|
|
324
|
-
}
|
|
325
|
-
function readInstalledRuntime(projectRoot) {
|
|
326
|
-
const packageJsonPath = join(
|
|
327
|
-
projectRoot,
|
|
328
|
-
"node_modules",
|
|
329
|
-
...RUNTIME_PACKAGE_NAME.split("/"),
|
|
330
|
-
"package.json"
|
|
331
|
-
);
|
|
332
|
-
if (!existsSync2(packageJsonPath)) return null;
|
|
333
|
-
const pkg = readJsonObject(packageJsonPath);
|
|
334
|
-
if (pkg === null) return null;
|
|
335
|
-
const version = pkg.version;
|
|
336
|
-
if (typeof version !== "string" || version.length === 0) return null;
|
|
337
|
-
const directive = pkg[CONTRACT_PACKAGE_JSON_KEY];
|
|
338
|
-
if (directive === null || typeof directive !== "object" || Array.isArray(directive)) {
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
const contract = directive.contract;
|
|
342
|
-
if (typeof contract !== "number" || !Number.isInteger(contract) || contract < 1) {
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
const entryPath = join(
|
|
346
|
-
projectRoot,
|
|
347
|
-
"node_modules",
|
|
348
|
-
...RUNTIME_PACKAGE_NAME.split("/"),
|
|
349
|
-
"dist",
|
|
350
|
-
"bin.js"
|
|
351
|
-
);
|
|
352
|
-
return { version, contract, packageJsonPath, entryPath };
|
|
353
|
-
}
|
|
354
|
-
function isContractSupported(runtimeContract) {
|
|
355
|
-
return Number.isInteger(runtimeContract) && runtimeContract >= 1 && runtimeContract <= LAUNCHER_MAX_SUPPORTED_CONTRACT;
|
|
356
|
-
}
|
|
357
|
-
function readProjectInstallState(projectRoot) {
|
|
358
|
-
const pinRead = readPin(projectRoot);
|
|
359
|
-
const installed = readInstalledRuntime(projectRoot);
|
|
360
|
-
const pinMatchesInstalled = pinRead.kind === "exact" && pinRead.version !== null && installed !== null && installed.version === pinRead.version;
|
|
361
|
-
const contractSupported = installed === null ? true : isContractSupported(installed.contract);
|
|
362
|
-
return {
|
|
363
|
-
pin: pinRead,
|
|
364
|
-
installed,
|
|
365
|
-
pinMatchesInstalled,
|
|
366
|
-
contractSupported,
|
|
367
|
-
oldStyle: pinRead.oldStyle
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// packages/cli/src/hook-launcher.ts
|
|
372
|
-
function runHookLauncher(argv) {
|
|
373
|
-
const node = checkNodeVersion();
|
|
374
|
-
if (!node.ok) {
|
|
375
|
-
process.stderr.write(`${node.message}
|
|
376
|
-
`);
|
|
377
|
-
return node.exitCode;
|
|
378
|
-
}
|
|
379
|
-
const discovered = discoverProject({ cwd: process.cwd() });
|
|
380
|
-
if (!discovered.ok) {
|
|
381
|
-
process.stderr.write(
|
|
382
|
-
`deft-hook: no project root. Host hooks should call node node_modules/${RUNTIME_PACKAGE_NAME}/dist/hook-bin.js
|
|
383
|
-
Recovery: ${RECOVERY_GUIDANCE}
|
|
384
|
-
`
|
|
385
|
-
);
|
|
386
|
-
return EXIT_CODES.FAILURE;
|
|
387
|
-
}
|
|
388
|
-
const runtime = readInstalledRuntime(discovered.root);
|
|
389
|
-
if (runtime === null) {
|
|
390
|
-
process.stderr.write(
|
|
391
|
-
`deft-hook: ${RUNTIME_PACKAGE_NAME} not installed in ${discovered.root}.
|
|
392
|
-
Recovery: ${RECOVERY_GUIDANCE}
|
|
393
|
-
`
|
|
394
|
-
);
|
|
395
|
-
return EXIT_CODES.FAILURE;
|
|
396
|
-
}
|
|
397
|
-
const hookEntry = join2(
|
|
398
|
-
discovered.root,
|
|
399
|
-
"node_modules",
|
|
400
|
-
...RUNTIME_PACKAGE_NAME.split("/"),
|
|
401
|
-
"dist",
|
|
402
|
-
"hook-bin.js"
|
|
403
|
-
);
|
|
404
|
-
if (!existsSync3(hookEntry)) {
|
|
405
|
-
process.stderr.write(`deft-hook: missing ${hookEntry}
|
|
406
|
-
Recovery: ${RECOVERY_GUIDANCE}
|
|
407
|
-
`);
|
|
408
|
-
return EXIT_CODES.FAILURE;
|
|
409
|
-
}
|
|
410
|
-
const result = spawnSync2(process.execPath, [hookEntry, ...argv], {
|
|
411
|
-
cwd: discovered.root,
|
|
412
|
-
env: process.env,
|
|
413
|
-
stdio: "inherit"
|
|
414
|
-
});
|
|
415
|
-
if (result.error) {
|
|
416
|
-
process.stderr.write(`deft-hook: failed to start runtime hook: ${result.error.message}
|
|
417
|
-
`);
|
|
418
|
-
return EXIT_CODES.FAILURE;
|
|
419
|
-
}
|
|
420
|
-
if (result.status === null && result.signal) return EXIT_CODES.INTERRUPTED;
|
|
421
|
-
return result.status ?? EXIT_CODES.FAILURE;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// packages/cli/src/contract-invoke.ts
|
|
425
|
-
import { existsSync as existsSync4 } from "node:fs";
|
|
426
|
-
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
427
|
-
function invokeRuntime(options) {
|
|
428
|
-
const { projectRoot, launcherVersion, runtime, argv } = options;
|
|
429
|
-
if (!existsSync4(runtime.entryPath)) {
|
|
430
|
-
return {
|
|
431
|
-
exitCode: EXIT_CODES.FAILURE,
|
|
432
|
-
stdout: "",
|
|
433
|
-
stderr: "",
|
|
434
|
-
spawned: false,
|
|
435
|
-
message: `Runtime entry missing at ${runtime.entryPath}. ${RECOVERY_GUIDANCE}`
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
const stdio = options.stdio ?? "inherit";
|
|
439
|
-
const result = spawnSync3(process.execPath, [runtime.entryPath, ...argv], {
|
|
440
|
-
cwd: process.cwd(),
|
|
441
|
-
env: {
|
|
442
|
-
...process.env,
|
|
443
|
-
[ENV_PROJECT_ROOT]: projectRoot,
|
|
444
|
-
[ENV_LAUNCHER_VERSION]: launcherVersion
|
|
445
|
-
},
|
|
446
|
-
encoding: "utf8",
|
|
447
|
-
stdio: stdio === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"]
|
|
448
|
-
});
|
|
449
|
-
if (result.error) {
|
|
450
|
-
return {
|
|
451
|
-
exitCode: EXIT_CODES.FAILURE,
|
|
452
|
-
stdout: "",
|
|
453
|
-
stderr: String(result.error),
|
|
454
|
-
spawned: false,
|
|
455
|
-
message: `Failed to start runtime: ${result.error.message}`
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
const status = result.status;
|
|
459
|
-
if (status === null && result.signal) {
|
|
460
|
-
return {
|
|
461
|
-
exitCode: EXIT_CODES.INTERRUPTED,
|
|
462
|
-
stdout: result.stdout ?? "",
|
|
463
|
-
stderr: result.stderr ?? "",
|
|
464
|
-
spawned: true,
|
|
465
|
-
message: `Runtime interrupted (${result.signal})`
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
return {
|
|
469
|
-
exitCode: status ?? EXIT_CODES.FAILURE,
|
|
470
|
-
stdout: result.stdout ?? "",
|
|
471
|
-
stderr: result.stderr ?? "",
|
|
472
|
-
spawned: true,
|
|
473
|
-
message: null
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
function setupArgv(operation, from, root) {
|
|
477
|
-
return ["setup", "--operation", operation, "--from", from, "--root", root];
|
|
478
|
-
}
|
|
479
|
-
function migrateArgv(from, root) {
|
|
480
|
-
return ["migrate", "--from", from, "--root", root];
|
|
481
|
-
}
|
|
482
|
-
function uninstallArgv(root) {
|
|
483
|
-
return ["uninstall", "--root", root];
|
|
484
|
-
}
|
|
485
|
-
function doctorArgv() {
|
|
486
|
-
return ["doctor", "--data-status"];
|
|
487
|
-
}
|
|
488
|
-
function doctorDiagnoseArgv() {
|
|
489
|
-
return ["doctor"];
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// packages/cli/src/bookends.ts
|
|
493
|
-
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
494
|
-
import { join as join6 } from "node:path";
|
|
495
|
-
|
|
496
|
-
// packages/cli/src/interactive-stdin.ts
|
|
497
|
-
import { readSync } from "node:fs";
|
|
498
|
-
var RETRY_ERRNOS = /* @__PURE__ */ new Set(["EAGAIN", "EWOULDBLOCK", "EINTR"]);
|
|
499
|
-
function defaultSyncWait(ms) {
|
|
500
|
-
const sab = new SharedArrayBuffer(4);
|
|
501
|
-
const ia = new Int32Array(sab);
|
|
502
|
-
Atomics.wait(ia, 0, 0, ms);
|
|
503
|
-
}
|
|
504
|
-
function defaultRead(buffer, offset, length) {
|
|
505
|
-
return readSync(0, buffer, offset, length, null);
|
|
506
|
-
}
|
|
507
|
-
function readInteractiveLine(options) {
|
|
508
|
-
const read = options?.read ?? defaultRead;
|
|
509
|
-
const wait = options?.wait ?? defaultSyncWait;
|
|
510
|
-
const waitMs = options?.waitMs ?? 15;
|
|
511
|
-
const chunks = [];
|
|
512
|
-
const buf = Buffer.alloc(256);
|
|
513
|
-
for (; ; ) {
|
|
514
|
-
let n = 0;
|
|
515
|
-
try {
|
|
516
|
-
n = read(buf, 0, buf.length);
|
|
517
|
-
} catch (err) {
|
|
518
|
-
const code2 = err?.code;
|
|
519
|
-
if (code2 && RETRY_ERRNOS.has(code2)) {
|
|
520
|
-
wait(waitMs);
|
|
521
|
-
continue;
|
|
522
|
-
}
|
|
523
|
-
if (code2 === "EIO") {
|
|
524
|
-
if (chunks.length === 0) return { line: "", eof: true };
|
|
525
|
-
break;
|
|
526
|
-
}
|
|
527
|
-
throw err;
|
|
528
|
-
}
|
|
529
|
-
if (n === 0) {
|
|
530
|
-
if (chunks.length === 0) return { line: "", eof: true };
|
|
531
|
-
break;
|
|
532
|
-
}
|
|
533
|
-
const slice = Buffer.from(buf.subarray(0, n));
|
|
534
|
-
chunks.push(slice);
|
|
535
|
-
if (slice.includes(10)) break;
|
|
536
|
-
}
|
|
537
|
-
const line = Buffer.concat(chunks).toString("utf8").split(/\r?\n/, 1)[0] ?? "";
|
|
538
|
-
return { line, eof: false };
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// packages/cli/src/pm.ts
|
|
542
|
-
import { createHash } from "node:crypto";
|
|
543
|
-
import { existsSync as existsSync5, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "node:fs";
|
|
544
|
-
import { join as join3 } from "node:path";
|
|
545
|
-
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
546
|
-
var PARTIAL_CHANGES_MAY_REMAIN = "Partial changes may remain.";
|
|
547
|
-
var INSTALL_SCRIPT_POLICY_PLAN_LINE = "install-script policy: PM exact-add uses --ignore-scripts (pin/wiring do not require package lifecycle scripts); project registry, authentication, proxy, and lockfile settings are honoured";
|
|
548
|
-
var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+([-+][0-9A-Za-z.-]+)?$/;
|
|
549
|
-
function isExactSemver(version) {
|
|
550
|
-
return EXACT_SEMVER_RE.test(version.trim());
|
|
551
|
-
}
|
|
552
|
-
function isPrereleaseVersion(version) {
|
|
553
|
-
const v = version.trim();
|
|
554
|
-
if (!isExactSemver(v)) return false;
|
|
555
|
-
return v.includes("-");
|
|
556
|
-
}
|
|
557
|
-
function readJsonObject2(path) {
|
|
558
|
-
try {
|
|
559
|
-
const raw = readFileSync2(path, "utf8");
|
|
560
|
-
const parsed = JSON.parse(raw);
|
|
561
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
562
|
-
return parsed;
|
|
563
|
-
} catch {
|
|
564
|
-
return null;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
function parsePmNameToken(raw) {
|
|
568
|
-
const token = raw.trim();
|
|
569
|
-
if (token === "") return { kind: "absent" };
|
|
570
|
-
if (token.startsWith("npm@") || token === "npm") return { kind: "supported", pm: "npm" };
|
|
571
|
-
if (token.startsWith("pnpm@") || token === "pnpm") return { kind: "supported", pm: "pnpm" };
|
|
572
|
-
if (token.startsWith("yarn@") || token === "yarn") return { kind: "unsupported", name: "Yarn" };
|
|
573
|
-
if (token.startsWith("bun@") || token === "bun") return { kind: "unsupported", name: "Bun" };
|
|
574
|
-
const name = token.split("@")[0]?.trim() || token;
|
|
575
|
-
return { kind: "unsupported", name };
|
|
576
|
-
}
|
|
577
|
-
function readPackageManagerField(projectRoot) {
|
|
578
|
-
const pkg = readJsonObject2(join3(projectRoot, "package.json"));
|
|
579
|
-
if (pkg === null) return { kind: "absent" };
|
|
580
|
-
const raw = pkg.packageManager;
|
|
581
|
-
if (typeof raw !== "string" || raw.trim() === "") return { kind: "absent" };
|
|
582
|
-
return parsePmNameToken(raw);
|
|
583
|
-
}
|
|
584
|
-
function readDevEnginesPackageManager(projectRoot) {
|
|
585
|
-
const pkg = readJsonObject2(join3(projectRoot, "package.json"));
|
|
586
|
-
if (pkg === null) return { kind: "absent" };
|
|
587
|
-
const devEngines = pkg.devEngines;
|
|
588
|
-
if (devEngines === null || typeof devEngines !== "object" || Array.isArray(devEngines)) {
|
|
589
|
-
return { kind: "absent" };
|
|
590
|
-
}
|
|
591
|
-
const raw = devEngines.packageManager;
|
|
592
|
-
if (typeof raw === "string") return parsePmNameToken(raw);
|
|
593
|
-
if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
594
|
-
const name = raw.name;
|
|
595
|
-
if (typeof name !== "string" || name.trim() === "") return { kind: "absent" };
|
|
596
|
-
return parsePmNameToken(name.trim());
|
|
597
|
-
}
|
|
598
|
-
return { kind: "absent" };
|
|
599
|
-
}
|
|
600
|
-
function readDeclaredPackageManagerSlot(projectRoot) {
|
|
601
|
-
const field = readPackageManagerField(projectRoot);
|
|
602
|
-
const dev = readDevEnginesPackageManager(projectRoot);
|
|
603
|
-
if (field.kind === "unsupported") return field;
|
|
604
|
-
if (dev.kind === "unsupported") return { kind: "unsupported", name: dev.name };
|
|
605
|
-
if (field.kind === "supported" && dev.kind === "supported") {
|
|
606
|
-
if (field.pm !== dev.pm) {
|
|
607
|
-
return {
|
|
608
|
-
kind: "conflict",
|
|
609
|
-
message: `packageManager (${field.pm}) and devEngines.packageManager (${dev.pm}) disagree. Pass --package-manager npm|pnpm after aligning the declarations, or remove one.`
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
return {
|
|
613
|
-
kind: "supported",
|
|
614
|
-
pm: field.pm,
|
|
615
|
-
evidence: `packageManager/devEngines (${field.pm})`
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
if (field.kind === "supported") {
|
|
619
|
-
return {
|
|
620
|
-
kind: "supported",
|
|
621
|
-
pm: field.pm,
|
|
622
|
-
evidence: `packageManager field (${field.pm})`
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
if (dev.kind === "supported") {
|
|
626
|
-
return {
|
|
627
|
-
kind: "supported",
|
|
628
|
-
pm: dev.pm,
|
|
629
|
-
evidence: `devEngines.packageManager (${dev.pm})`
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
return { kind: "absent" };
|
|
633
|
-
}
|
|
634
|
-
function detectPackageManager(projectRoot, flag) {
|
|
635
|
-
const hasYarn = existsSync5(join3(projectRoot, "yarn.lock"));
|
|
636
|
-
const hasBun = existsSync5(join3(projectRoot, "bun.lock")) || existsSync5(join3(projectRoot, "bun.lockb"));
|
|
637
|
-
if (hasYarn) {
|
|
638
|
-
return {
|
|
639
|
-
ok: false,
|
|
640
|
-
message: "Yarn projects are not supported (AC5.7). Use npm or pnpm, or remove yarn.lock."
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
if (hasBun) {
|
|
644
|
-
return {
|
|
645
|
-
ok: false,
|
|
646
|
-
message: "Bun projects are not supported (AC5.7). Use npm or pnpm, or remove bun.lock."
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
const declared = readDeclaredPackageManagerSlot(projectRoot);
|
|
650
|
-
if (declared.kind === "unsupported") {
|
|
651
|
-
return {
|
|
652
|
-
ok: false,
|
|
653
|
-
message: `${declared.name} projects are not supported (AC5.7). Use npm or pnpm.`
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
if (declared.kind === "conflict") {
|
|
657
|
-
return { ok: false, message: declared.message };
|
|
658
|
-
}
|
|
659
|
-
if (flag !== null) {
|
|
660
|
-
if (flag !== "npm" && flag !== "pnpm") {
|
|
661
|
-
return {
|
|
662
|
-
ok: false,
|
|
663
|
-
message: `--package-manager must be npm or pnpm (got ${flag}).`
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
const hasPnpmLock = existsSync5(join3(projectRoot, "pnpm-lock.yaml"));
|
|
668
|
-
const hasNpmLock = existsSync5(join3(projectRoot, "package-lock.json"));
|
|
669
|
-
const fieldPm = declared.kind === "supported" ? declared.pm : null;
|
|
670
|
-
const fieldEvidence = declared.kind === "supported" ? declared.evidence : null;
|
|
671
|
-
if (hasPnpmLock && hasNpmLock) {
|
|
672
|
-
if (flag === "npm" || flag === "pnpm") {
|
|
673
|
-
return { ok: true, pm: flag, evidence: `--package-manager ${flag} (both lockfiles present)` };
|
|
674
|
-
}
|
|
675
|
-
if (fieldPm !== null && fieldEvidence !== null) {
|
|
676
|
-
return {
|
|
677
|
-
ok: true,
|
|
678
|
-
pm: fieldPm,
|
|
679
|
-
evidence: `${fieldEvidence} with both lockfiles`
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
return {
|
|
683
|
-
ok: false,
|
|
684
|
-
message: "Both package-lock.json and pnpm-lock.yaml present. Pass --package-manager npm|pnpm."
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
if (hasPnpmLock) {
|
|
688
|
-
if (flag === "npm") {
|
|
689
|
-
return {
|
|
690
|
-
ok: false,
|
|
691
|
-
message: "pnpm-lock.yaml present; refusing --package-manager npm."
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
return { ok: true, pm: "pnpm", evidence: "pnpm-lock.yaml" };
|
|
695
|
-
}
|
|
696
|
-
if (hasNpmLock) {
|
|
697
|
-
if (flag === "pnpm") {
|
|
698
|
-
return {
|
|
699
|
-
ok: false,
|
|
700
|
-
message: "package-lock.json present; refusing --package-manager pnpm."
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
return { ok: true, pm: "npm", evidence: "package-lock.json" };
|
|
704
|
-
}
|
|
705
|
-
if (flag === "npm" || flag === "pnpm") {
|
|
706
|
-
return { ok: true, pm: flag, evidence: `--package-manager ${flag}` };
|
|
707
|
-
}
|
|
708
|
-
if (fieldPm !== null && fieldEvidence !== null) {
|
|
709
|
-
return { ok: true, pm: fieldPm, evidence: fieldEvidence };
|
|
710
|
-
}
|
|
711
|
-
return { ok: true, pm: "npm", evidence: "default (no lockfile / packageManager)" };
|
|
712
|
-
}
|
|
713
|
-
function pnpmRootImporterBlock(text) {
|
|
714
|
-
const lines = text.split(/\r?\n/);
|
|
715
|
-
let i = 0;
|
|
716
|
-
while (i < lines.length && !/^importers:\s*$/.test(lines[i])) i++;
|
|
717
|
-
if (i >= lines.length) return null;
|
|
718
|
-
i++;
|
|
719
|
-
while (i < lines.length && !/^ \.:\s*$/.test(lines[i])) {
|
|
720
|
-
const line = lines[i];
|
|
721
|
-
if (line !== "" && !line.startsWith(" ")) return null;
|
|
722
|
-
i++;
|
|
723
|
-
}
|
|
724
|
-
if (i >= lines.length) return null;
|
|
725
|
-
i++;
|
|
726
|
-
const block = [];
|
|
727
|
-
while (i < lines.length) {
|
|
728
|
-
const line = lines[i];
|
|
729
|
-
if (line !== "" && (/^ \S/.test(line) || /^[^ \t]/.test(line))) break;
|
|
730
|
-
block.push(line);
|
|
731
|
-
i++;
|
|
732
|
-
}
|
|
733
|
-
return block.join("\n");
|
|
734
|
-
}
|
|
735
|
-
function pnpmRootRecordsExactPin(text, exactVersion) {
|
|
736
|
-
const block = pnpmRootImporterBlock(text);
|
|
737
|
-
if (block === null) return false;
|
|
738
|
-
const pkg = RUNTIME_PACKAGE_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
739
|
-
const re = new RegExp(
|
|
740
|
-
`['"]?${pkg}['"]?:\\s*\\n\\s+specifier:\\s*['"]?([^\\s'"]+)['"]?\\s*\\n\\s+version:\\s*['"]?([^\\s'"]+)['"]?`
|
|
741
|
-
);
|
|
742
|
-
const match = block.match(re);
|
|
743
|
-
if (match === null) return false;
|
|
744
|
-
return match[1] === exactVersion && match[2] === exactVersion;
|
|
745
|
-
}
|
|
746
|
-
function npmRootRequest(packages) {
|
|
747
|
-
const root = packages[""];
|
|
748
|
-
if (typeof root !== "object" || root === null || Array.isArray(root)) return null;
|
|
749
|
-
const rec = root;
|
|
750
|
-
for (const key of ["devDependencies", "dependencies", "optionalDependencies"]) {
|
|
751
|
-
const deps = rec[key];
|
|
752
|
-
if (typeof deps !== "object" || deps === null || Array.isArray(deps)) continue;
|
|
753
|
-
const v = deps[RUNTIME_PACKAGE_NAME];
|
|
754
|
-
if (typeof v === "string") return v;
|
|
755
|
-
}
|
|
756
|
-
return null;
|
|
757
|
-
}
|
|
758
|
-
function lockfileRecordsExactPin(projectRoot, exactVersion, pm) {
|
|
759
|
-
if (pm === "pnpm") {
|
|
760
|
-
const pnpmLock = join3(projectRoot, "pnpm-lock.yaml");
|
|
761
|
-
if (!existsSync5(pnpmLock)) return false;
|
|
762
|
-
try {
|
|
763
|
-
return pnpmRootRecordsExactPin(readFileSync2(pnpmLock, "utf8"), exactVersion);
|
|
764
|
-
} catch {
|
|
765
|
-
return false;
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
const npmLock = join3(projectRoot, "package-lock.json");
|
|
769
|
-
if (!existsSync5(npmLock)) return false;
|
|
770
|
-
const lock = readJsonObject2(npmLock);
|
|
771
|
-
if (lock === null) return false;
|
|
772
|
-
const packages = lock.packages;
|
|
773
|
-
if (typeof packages === "object" && packages !== null && !Array.isArray(packages)) {
|
|
774
|
-
const pkgs = packages;
|
|
775
|
-
const request = npmRootRequest(pkgs);
|
|
776
|
-
if (request !== exactVersion) return false;
|
|
777
|
-
const entry = pkgs[`node_modules/${RUNTIME_PACKAGE_NAME}`];
|
|
778
|
-
if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
|
|
779
|
-
return entry.version === exactVersion;
|
|
780
|
-
}
|
|
781
|
-
return false;
|
|
782
|
-
}
|
|
783
|
-
return false;
|
|
784
|
-
}
|
|
785
|
-
function ensurePackageJson(projectRoot) {
|
|
786
|
-
const path = join3(projectRoot, "package.json");
|
|
787
|
-
if (existsSync5(path)) {
|
|
788
|
-
return { created: false, path };
|
|
789
|
-
}
|
|
790
|
-
writeFileSync(path, `${JSON.stringify({ private: true }, null, 2)}
|
|
791
|
-
`);
|
|
792
|
-
return { created: true, path };
|
|
793
|
-
}
|
|
794
|
-
function versionFromPmToken(raw) {
|
|
795
|
-
const at = raw.indexOf("@");
|
|
796
|
-
if (at < 0) return null;
|
|
797
|
-
const ver = raw.slice(at + 1).trim();
|
|
798
|
-
return ver.length > 0 ? ver : null;
|
|
799
|
-
}
|
|
800
|
-
function readDeclaredPackageManagerVersion(projectRoot) {
|
|
801
|
-
const pkg = readJsonObject2(join3(projectRoot, "package.json"));
|
|
802
|
-
if (pkg === null) return null;
|
|
803
|
-
if (typeof pkg.packageManager === "string" && pkg.packageManager.trim() !== "") {
|
|
804
|
-
const fromField = versionFromPmToken(pkg.packageManager.trim());
|
|
805
|
-
if (fromField !== null) return fromField;
|
|
806
|
-
}
|
|
807
|
-
const devEngines = pkg.devEngines;
|
|
808
|
-
if (devEngines !== null && typeof devEngines === "object" && !Array.isArray(devEngines)) {
|
|
809
|
-
const raw = devEngines.packageManager;
|
|
810
|
-
if (typeof raw === "string") {
|
|
811
|
-
const fromDev = versionFromPmToken(raw.trim());
|
|
812
|
-
if (fromDev !== null) return fromDev;
|
|
813
|
-
} else if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
814
|
-
const ver = raw.version;
|
|
815
|
-
if (typeof ver === "string" && ver.trim() !== "") return ver.trim();
|
|
816
|
-
const name = raw.name;
|
|
817
|
-
if (typeof name === "string") {
|
|
818
|
-
const fromName = versionFromPmToken(name.trim());
|
|
819
|
-
if (fromName !== null) return fromName;
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
return null;
|
|
824
|
-
}
|
|
825
|
-
function formatMissingPmGuidance(pm, declaredVersion = null) {
|
|
826
|
-
const ver = declaredVersion && declaredVersion.length > 0 ? declaredVersion : "latest";
|
|
827
|
-
const prepare = pm === "pnpm" ? `corepack enable && corepack prepare pnpm@${ver} --activate` : `corepack enable && corepack prepare npm@${ver} --activate`;
|
|
828
|
-
return `${pm} is required for this project but was not found on PATH.
|
|
829
|
-
To make ${pm} available:
|
|
830
|
-
- If your Node install includes Corepack: ${prepare}
|
|
831
|
-
- Or install ${pm} yourself and ensure its directory is on PATH
|
|
832
|
-
Directive does not install, upgrade, or switch package managers.`;
|
|
833
|
-
}
|
|
834
|
-
function ensurePackageManagerAvailable(pm, env = process.env, declaredVersion = null, projectRoot) {
|
|
835
|
-
const result = spawnSync4(pm, ["--version"], {
|
|
836
|
-
...projectRoot !== void 0 ? { cwd: projectRoot } : {},
|
|
837
|
-
env,
|
|
838
|
-
encoding: "utf8",
|
|
839
|
-
shell: process.platform === "win32"
|
|
840
|
-
});
|
|
841
|
-
if (result.error) {
|
|
842
|
-
const err = result.error;
|
|
843
|
-
if (err.code === "ENOENT" || /ENOENT/i.test(err.message)) {
|
|
844
|
-
return { ok: false, message: formatMissingPmGuidance(pm, declaredVersion) };
|
|
845
|
-
}
|
|
846
|
-
return {
|
|
847
|
-
ok: false,
|
|
848
|
-
message: formatMissingPmGuidance(pm, declaredVersion)
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
if (result.status === null && result.signal) {
|
|
852
|
-
return { ok: false, message: formatMissingPmGuidance(pm, declaredVersion) };
|
|
853
|
-
}
|
|
854
|
-
if (result.status !== 0) {
|
|
855
|
-
return { ok: false, message: formatMissingPmGuidance(pm, declaredVersion) };
|
|
856
|
-
}
|
|
857
|
-
return { ok: true };
|
|
858
|
-
}
|
|
859
|
-
function fileDigest(absPath) {
|
|
860
|
-
if (!existsSync5(absPath)) return null;
|
|
861
|
-
try {
|
|
862
|
-
return createHash("sha256").update(readFileSync2(absPath)).digest("hex");
|
|
863
|
-
} catch {
|
|
864
|
-
return null;
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
function planCriticalRelPaths(_pm) {
|
|
868
|
-
return [
|
|
869
|
-
"package.json",
|
|
870
|
-
"package-lock.json",
|
|
871
|
-
"pnpm-lock.yaml",
|
|
872
|
-
"pnpm-workspace.yaml",
|
|
873
|
-
"yarn.lock",
|
|
874
|
-
"bun.lock",
|
|
875
|
-
"bun.lockb"
|
|
876
|
-
];
|
|
877
|
-
}
|
|
878
|
-
function fingerprintPlanCriticalFiles(projectRoot, pm) {
|
|
879
|
-
const entries = planCriticalRelPaths(pm).map((relPath) => ({
|
|
880
|
-
relPath,
|
|
881
|
-
digest: fileDigest(join3(projectRoot, relPath))
|
|
882
|
-
}));
|
|
883
|
-
return { entries };
|
|
884
|
-
}
|
|
885
|
-
function planFingerprintChanged(projectRoot, pm, prior) {
|
|
886
|
-
const now = fingerprintPlanCriticalFiles(projectRoot, pm);
|
|
887
|
-
if (now.entries.length !== prior.entries.length) return true;
|
|
888
|
-
for (let i = 0; i < prior.entries.length; i++) {
|
|
889
|
-
const a = prior.entries[i];
|
|
890
|
-
const b = now.entries[i];
|
|
891
|
-
if (a.relPath !== b.relPath || a.digest !== b.digest) return true;
|
|
892
|
-
}
|
|
893
|
-
return false;
|
|
894
|
-
}
|
|
895
|
-
function interpretPmSpawnResult(pm, args, result) {
|
|
896
|
-
if (result.error) {
|
|
897
|
-
const err = result.error;
|
|
898
|
-
if (err.code === "ENOENT" || /ENOENT/i.test(String(err.message))) {
|
|
899
|
-
return {
|
|
900
|
-
ok: false,
|
|
901
|
-
message: formatMissingPmGuidance(pm === "pnpm" ? "pnpm" : "npm"),
|
|
902
|
-
stderr: String(result.error)
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
return {
|
|
906
|
-
ok: false,
|
|
907
|
-
message: `Failed to spawn ${pm}: ${result.error.message}`,
|
|
908
|
-
stderr: String(result.error)
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
if (result.status === null && result.signal) {
|
|
912
|
-
const signal = String(result.signal);
|
|
913
|
-
return {
|
|
914
|
-
ok: false,
|
|
915
|
-
interrupted: true,
|
|
916
|
-
signal,
|
|
917
|
-
message: `${pm} interrupted (${signal}). ${PARTIAL_CHANGES_MAY_REMAIN}`,
|
|
918
|
-
stderr: `${result.stderr ?? ""}${result.stdout ?? ""}`
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
if (result.status !== 0) {
|
|
922
|
-
return {
|
|
923
|
-
ok: false,
|
|
924
|
-
message: `${pm} ${args.join(" ")} failed (exit ${result.status ?? "?"})`,
|
|
925
|
-
stderr: `${result.stderr ?? ""}${result.stdout ?? ""}`
|
|
926
|
-
};
|
|
927
|
-
}
|
|
928
|
-
return { ok: true };
|
|
929
|
-
}
|
|
930
|
-
function isPnpmWorkspaceRoot(projectRoot) {
|
|
931
|
-
return existsSync5(join3(projectRoot, "pnpm-workspace.yaml"));
|
|
932
|
-
}
|
|
933
|
-
function buildPmExactAddArgs(pm, version, projectRoot) {
|
|
934
|
-
const spec = `${RUNTIME_PACKAGE_NAME}@${version}`;
|
|
935
|
-
if (pm === "npm") {
|
|
936
|
-
return [
|
|
937
|
-
"install",
|
|
938
|
-
"--save-dev",
|
|
939
|
-
"--save-exact",
|
|
940
|
-
"--ignore-scripts",
|
|
941
|
-
"--include=dev",
|
|
942
|
-
spec
|
|
943
|
-
];
|
|
944
|
-
}
|
|
945
|
-
const args = [
|
|
946
|
-
"add",
|
|
947
|
-
"-D",
|
|
948
|
-
"--save-exact",
|
|
949
|
-
"--ignore-scripts",
|
|
950
|
-
"--config.production=false"
|
|
951
|
-
];
|
|
952
|
-
if (isPnpmWorkspaceRoot(projectRoot)) {
|
|
953
|
-
args.push("-w");
|
|
954
|
-
}
|
|
955
|
-
args.push(spec);
|
|
956
|
-
return args;
|
|
957
|
-
}
|
|
958
|
-
function formatPmExactAddCommand(pm, version, projectRoot) {
|
|
959
|
-
return `${pm} ${buildPmExactAddArgs(pm, version, projectRoot).join(" ")}`;
|
|
960
|
-
}
|
|
961
|
-
var RETIRED_DIRECTIVE_PACKAGE_NAMES = [
|
|
962
|
-
LAUNCHER_PACKAGE_NAME,
|
|
963
|
-
"@deftai/directive-core",
|
|
964
|
-
"@deftai/directive-content",
|
|
965
|
-
"@deftai/directive-types"
|
|
966
|
-
];
|
|
967
|
-
function removeLegacyDirectivePackages(projectRoot) {
|
|
968
|
-
const path = join3(projectRoot, "package.json");
|
|
969
|
-
const pkg = readJsonObject2(path);
|
|
970
|
-
if (pkg === null) return false;
|
|
971
|
-
let removed = false;
|
|
972
|
-
for (const key of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
973
|
-
const block = pkg[key];
|
|
974
|
-
if (block === null || typeof block !== "object" || Array.isArray(block)) continue;
|
|
975
|
-
const deps = block;
|
|
976
|
-
for (const name of RETIRED_DIRECTIVE_PACKAGE_NAMES) {
|
|
977
|
-
if (!(name in deps)) continue;
|
|
978
|
-
delete deps[name];
|
|
979
|
-
removed = true;
|
|
980
|
-
}
|
|
981
|
-
if (Object.keys(deps).length === 0) {
|
|
982
|
-
delete pkg[key];
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
if (!removed) return false;
|
|
986
|
-
writeFileSync(path, `${JSON.stringify(pkg, null, 2)}
|
|
987
|
-
`);
|
|
988
|
-
return true;
|
|
989
|
-
}
|
|
990
|
-
function removeLegacyDirectivePin(projectRoot) {
|
|
991
|
-
return removeLegacyDirectivePackages(projectRoot);
|
|
992
|
-
}
|
|
993
|
-
var LOCKFILE_CANDIDATES = ["pnpm-lock.yaml", "package-lock.json", "npm-shrinkwrap.json"];
|
|
994
|
-
function lockfileReferencesRetiredDirective(contents) {
|
|
995
|
-
for (const name of RETIRED_DIRECTIVE_PACKAGE_NAMES) {
|
|
996
|
-
if (contents.includes(`"${name}"`) || contents.includes(`${name}@`) || contents.includes(`${name}:`)) {
|
|
997
|
-
return true;
|
|
998
|
-
}
|
|
999
|
-
if (contents.includes(name)) {
|
|
1000
|
-
return true;
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
return false;
|
|
1004
|
-
}
|
|
1005
|
-
function purgeRetiredDirectiveLockfiles(projectRoot) {
|
|
1006
|
-
const purged = [];
|
|
1007
|
-
for (const rel of LOCKFILE_CANDIDATES) {
|
|
1008
|
-
const abs = join3(projectRoot, rel);
|
|
1009
|
-
if (!existsSync5(abs)) continue;
|
|
1010
|
-
let contents;
|
|
1011
|
-
try {
|
|
1012
|
-
contents = readFileSync2(abs, "utf8");
|
|
1013
|
-
} catch {
|
|
1014
|
-
continue;
|
|
1015
|
-
}
|
|
1016
|
-
if (!lockfileReferencesRetiredDirective(contents)) continue;
|
|
1017
|
-
try {
|
|
1018
|
-
unlinkSync(abs);
|
|
1019
|
-
purged.push(rel);
|
|
1020
|
-
} catch {
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
return purged;
|
|
1024
|
-
}
|
|
1025
|
-
function runPmExactAdd(options) {
|
|
1026
|
-
const { projectRoot, pm, version } = options;
|
|
1027
|
-
const args = buildPmExactAddArgs(pm, version, projectRoot);
|
|
1028
|
-
const result = spawnSync4(pm, args, {
|
|
1029
|
-
cwd: projectRoot,
|
|
1030
|
-
env: options.env ?? process.env,
|
|
1031
|
-
encoding: "utf8",
|
|
1032
|
-
shell: process.platform === "win32"
|
|
1033
|
-
});
|
|
1034
|
-
return interpretPmSpawnResult(pm, args, result);
|
|
1035
|
-
}
|
|
1036
|
-
function buildPmRemoveArgs(pm, packageName, projectRoot) {
|
|
1037
|
-
if (pm === "npm") {
|
|
1038
|
-
return ["uninstall", "--save-dev", packageName];
|
|
1039
|
-
}
|
|
1040
|
-
const args = ["remove", "-D"];
|
|
1041
|
-
if (isPnpmWorkspaceRoot(projectRoot)) {
|
|
1042
|
-
args.push("-w");
|
|
1043
|
-
}
|
|
1044
|
-
args.push(packageName);
|
|
1045
|
-
return args;
|
|
1046
|
-
}
|
|
1047
|
-
function formatPmRemoveCommand(pm, packageName, projectRoot) {
|
|
1048
|
-
return `${pm} ${buildPmRemoveArgs(pm, packageName, projectRoot).join(" ")}`;
|
|
1049
|
-
}
|
|
1050
|
-
function runPmRemove(options) {
|
|
1051
|
-
const { projectRoot, pm, packageName } = options;
|
|
1052
|
-
const args = buildPmRemoveArgs(pm, packageName, projectRoot);
|
|
1053
|
-
const result = spawnSync4(pm, args, {
|
|
1054
|
-
cwd: projectRoot,
|
|
1055
|
-
env: options.env ?? process.env,
|
|
1056
|
-
encoding: "utf8",
|
|
1057
|
-
shell: process.platform === "win32"
|
|
1058
|
-
});
|
|
1059
|
-
return interpretPmSpawnResult(pm, args, result);
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
// packages/cli/src/plan-present.ts
|
|
1063
|
-
var WRAP_FLOOR = 40;
|
|
1064
|
-
var WRAP_CEILING = 100;
|
|
1065
|
-
var WRAP_FALLBACK = 80;
|
|
1066
|
-
var HUMAN_INSTALL_SCRIPT_SETTINGS_LINE = "Installation may run scripts allowed by your project's settings.";
|
|
1067
|
-
var INSTALL_COMMAND_LABEL = "Install command:";
|
|
1068
|
-
var HUMAN_GIT_HOOKS_INACTIVE_LINE = "Incomplete install: this folder is not a git repository yet, so git hooks will not be installed. After you run git init, run directive install --repair so hooks get installed.";
|
|
1069
|
-
function lockfileNameFor(pm) {
|
|
1070
|
-
return pm === "pnpm" ? "pnpm-lock.yaml" : "package-lock.json";
|
|
1071
|
-
}
|
|
1072
|
-
function parseVerbosityFlags(argv) {
|
|
1073
|
-
const rest = [];
|
|
1074
|
-
let verbosity = 0;
|
|
1075
|
-
for (const a of argv) {
|
|
1076
|
-
if (a === "-v" || a === "--verbose") {
|
|
1077
|
-
verbosity = Math.max(verbosity, 1);
|
|
1078
|
-
continue;
|
|
1079
|
-
}
|
|
1080
|
-
if (a === "-vv" || a === "--debug") {
|
|
1081
|
-
verbosity = 2;
|
|
1082
|
-
continue;
|
|
1083
|
-
}
|
|
1084
|
-
if (/^-v{2,}$/.test(a)) {
|
|
1085
|
-
verbosity = 2;
|
|
1086
|
-
continue;
|
|
1087
|
-
}
|
|
1088
|
-
rest.push(a);
|
|
1089
|
-
}
|
|
1090
|
-
return { verbosity, rest };
|
|
1091
|
-
}
|
|
1092
|
-
function writeBlank(io) {
|
|
1093
|
-
io.writeOut("\n");
|
|
1094
|
-
}
|
|
1095
|
-
function writeLabeled(io, label, value, width = 18) {
|
|
1096
|
-
const pad = label.length >= width ? `${label} ` : `${label}${" ".repeat(width - label.length)}`;
|
|
1097
|
-
io.writeOut(`${pad}${value}
|
|
1098
|
-
`);
|
|
1099
|
-
}
|
|
1100
|
-
function resolveWrapWidth(opts) {
|
|
1101
|
-
const isTTY = opts?.isTTY ?? Boolean(process.stdout.isTTY);
|
|
1102
|
-
if (!isTTY) return null;
|
|
1103
|
-
let cols = opts?.columns;
|
|
1104
|
-
if (cols == null || !Number.isFinite(cols) || cols <= 0) {
|
|
1105
|
-
const fromStdout = process.stdout.columns;
|
|
1106
|
-
const fromEnv = Number(process.env.COLUMNS);
|
|
1107
|
-
if (typeof fromStdout === "number" && fromStdout > 0) cols = fromStdout;
|
|
1108
|
-
else if (Number.isFinite(fromEnv) && fromEnv > 0) cols = fromEnv;
|
|
1109
|
-
else cols = WRAP_FALLBACK;
|
|
1110
|
-
}
|
|
1111
|
-
return Math.max(WRAP_FLOOR, Math.min(WRAP_CEILING, Math.floor(cols)));
|
|
1112
|
-
}
|
|
1113
|
-
function wrapWords(text, width) {
|
|
1114
|
-
const normalized = text.replace(/\s+/g, " ").trim();
|
|
1115
|
-
if (normalized.length === 0) return "";
|
|
1116
|
-
if (width < 1) return normalized;
|
|
1117
|
-
const words = normalized.split(" ");
|
|
1118
|
-
const lines = [];
|
|
1119
|
-
let current = "";
|
|
1120
|
-
for (const word of words) {
|
|
1121
|
-
if (current.length === 0) {
|
|
1122
|
-
current = word;
|
|
1123
|
-
continue;
|
|
1124
|
-
}
|
|
1125
|
-
if (current.length + 1 + word.length <= width) {
|
|
1126
|
-
current = `${current} ${word}`;
|
|
1127
|
-
} else {
|
|
1128
|
-
lines.push(current);
|
|
1129
|
-
current = word;
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
if (current.length > 0) lines.push(current);
|
|
1133
|
-
return lines.join("\n");
|
|
1134
|
-
}
|
|
1135
|
-
function writeHumanParagraph(io, text, wrapOpts) {
|
|
1136
|
-
const width = resolveWrapWidth(wrapOpts);
|
|
1137
|
-
const body = width == null ? text.replace(/\s+/g, " ").trim() : wrapWords(text, width);
|
|
1138
|
-
if (body.length === 0) return;
|
|
1139
|
-
io.writeOut(`${body}
|
|
1140
|
-
`);
|
|
1141
|
-
}
|
|
1142
|
-
function writeBullet(io, text) {
|
|
1143
|
-
const width = resolveWrapWidth();
|
|
1144
|
-
if (width == null) {
|
|
1145
|
-
io.writeOut(` \u2022 ${text}
|
|
1146
|
-
`);
|
|
1147
|
-
return;
|
|
1148
|
-
}
|
|
1149
|
-
const inner = Math.max(WRAP_FLOOR - 4, width - 4);
|
|
1150
|
-
const wrapped = wrapWords(text, inner);
|
|
1151
|
-
const parts = wrapped.split("\n");
|
|
1152
|
-
io.writeOut(` \u2022 ${parts[0]}
|
|
1153
|
-
`);
|
|
1154
|
-
for (let i = 1; i < parts.length; i++) {
|
|
1155
|
-
io.writeOut(` ${parts[i]}
|
|
1156
|
-
`);
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
function writeGitHooksDisclosure(io, inactive) {
|
|
1160
|
-
if (!inactive) return;
|
|
1161
|
-
writeHumanParagraph(io, HUMAN_GIT_HOOKS_INACTIVE_LINE);
|
|
1162
|
-
}
|
|
1163
|
-
function writeNodeWarning(io, warning) {
|
|
1164
|
-
if (!warning) return;
|
|
1165
|
-
writeHumanParagraph(io, warning);
|
|
1166
|
-
}
|
|
1167
|
-
function writeDisclosures(io, fields) {
|
|
1168
|
-
writeGitHooksDisclosure(io, fields.gitHooksInactive);
|
|
1169
|
-
writeNodeWarning(io, fields.nodeWarning);
|
|
1170
|
-
}
|
|
1171
|
-
function writeKeyedBlock(io, lines) {
|
|
1172
|
-
io.writeOut("Plan (verbose):\n");
|
|
1173
|
-
for (const line of lines) {
|
|
1174
|
-
io.writeOut(` ${line}
|
|
1175
|
-
`);
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
function writeInstallCommandAndScriptDisclosure(io, command) {
|
|
1179
|
-
if (!command) return;
|
|
1180
|
-
writeBlank(io);
|
|
1181
|
-
io.writeOut(`${INSTALL_COMMAND_LABEL}
|
|
1182
|
-
`);
|
|
1183
|
-
io.writeOut(` ${command}
|
|
1184
|
-
`);
|
|
1185
|
-
writeBlank(io);
|
|
1186
|
-
io.writeOut(`${HUMAN_INSTALL_SCRIPT_SETTINGS_LINE}
|
|
1187
|
-
`);
|
|
1188
|
-
}
|
|
1189
|
-
function writePmCommandVerbose(io, label, command, verbosity) {
|
|
1190
|
-
if (verbosity < 1 || !command) return;
|
|
1191
|
-
writeBlank(io);
|
|
1192
|
-
io.writeOut(`${label}
|
|
1193
|
-
`);
|
|
1194
|
-
io.writeOut(` ${command}
|
|
1195
|
-
`);
|
|
1196
|
-
}
|
|
1197
|
-
function presentFirstInstallPlan(io, fields) {
|
|
1198
|
-
const lock = lockfileNameFor(fields.pm);
|
|
1199
|
-
io.writeOut("Set up Deft Directive\n");
|
|
1200
|
-
writeBlank(io);
|
|
1201
|
-
writeLabeled(io, "Project:", fields.projectRoot);
|
|
1202
|
-
writeLabeled(io, "Package manager:", `${fields.pm} (${fields.pmEvidence})`);
|
|
1203
|
-
writeLabeled(io, "Directive version:", fields.version);
|
|
1204
|
-
writeBlank(io);
|
|
1205
|
-
io.writeOut("If you approve, Directive will:\n");
|
|
1206
|
-
if (fields.willCreatePkg) {
|
|
1207
|
-
writeBullet(io, "create a minimal package.json for this project");
|
|
1208
|
-
}
|
|
1209
|
-
writeBullet(
|
|
1210
|
-
io,
|
|
1211
|
-
`add Directive ${fields.version} to this project's development tools with ${fields.pm}`
|
|
1212
|
-
);
|
|
1213
|
-
writeBullet(io, `update ${lock}`);
|
|
1214
|
-
writeBullet(io, "set up Directive's project files");
|
|
1215
|
-
writeBlank(io);
|
|
1216
|
-
writeDisclosures(io, fields);
|
|
1217
|
-
writeInstallCommandAndScriptDisclosure(io, fields.pmCommand);
|
|
1218
|
-
if (fields.verbosity >= 1) {
|
|
1219
|
-
writeBlank(io);
|
|
1220
|
-
const keyed = [
|
|
1221
|
-
`operation: install (first-time)`,
|
|
1222
|
-
`root: ${fields.projectRoot}`,
|
|
1223
|
-
`package manager: ${fields.pm} (${fields.pmEvidence})`,
|
|
1224
|
-
`target: @deftai/directive-runtime@${fields.version}`,
|
|
1225
|
-
fields.willCreatePkg ? `manifest: create package.json { "private": true }` : `manifest: add exact pin to existing package.json`,
|
|
1226
|
-
`PM command: ${fields.pmCommand}`,
|
|
1227
|
-
`expected: package.json pin + lockfile update via PM`,
|
|
1228
|
-
`setup: runtime setup --operation install --from none (wiring, GENERATION.json, readiness; may create missing seed placeholders)`,
|
|
1229
|
-
INSTALL_SCRIPT_POLICY_PLAN_LINE
|
|
1230
|
-
];
|
|
1231
|
-
if (fields.gitHooksInactive) {
|
|
1232
|
-
keyed.push(
|
|
1233
|
-
"Git hooks: not installed until git init + directive install --repair (cannot set core.hooksPath=.githooks yet)"
|
|
1234
|
-
);
|
|
1235
|
-
}
|
|
1236
|
-
if (fields.nodeWarning) keyed.push(`node: ${fields.nodeWarning}`);
|
|
1237
|
-
writeKeyedBlock(io, keyed);
|
|
1238
|
-
writeBlank(io);
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
function presentRepairPlan(io, fields) {
|
|
1242
|
-
io.writeOut("Repair Directive\n");
|
|
1243
|
-
writeBlank(io);
|
|
1244
|
-
writeLabeled(io, "Project:", fields.projectRoot);
|
|
1245
|
-
writeLabeled(io, "Package manager:", `${fields.pm} (${fields.pmEvidence})`);
|
|
1246
|
-
writeLabeled(io, "Directive version:", `${fields.version} (unchanged)`);
|
|
1247
|
-
writeBlank(io);
|
|
1248
|
-
if (!fields.needsPmRestore) {
|
|
1249
|
-
writeHumanParagraph(io, "The installed package is healthy. No reinstall is needed.");
|
|
1250
|
-
writeBlank(io);
|
|
1251
|
-
io.writeOut("If you approve, Directive will:\n");
|
|
1252
|
-
writeBullet(io, `rerun setup using Directive ${fields.version}`);
|
|
1253
|
-
writeBullet(io, "recreate missing managed project files as needed");
|
|
1254
|
-
writeBlank(io);
|
|
1255
|
-
writeHumanParagraph(io, "Your existing settings and other files will be preserved.");
|
|
1256
|
-
} else {
|
|
1257
|
-
writeHumanParagraph(
|
|
1258
|
-
io,
|
|
1259
|
-
`The installed package needs restoration (${fields.restoreReason ?? "mismatch"}).`
|
|
1260
|
-
);
|
|
1261
|
-
writeBlank(io);
|
|
1262
|
-
io.writeOut("If you approve, Directive will:\n");
|
|
1263
|
-
writeBullet(io, `restore the Directive ${fields.version} pin with ${fields.pm}`);
|
|
1264
|
-
writeBullet(io, "rerun project setup");
|
|
1265
|
-
writeBlank(io);
|
|
1266
|
-
writeHumanParagraph(io, "Your existing settings and other files will be preserved.");
|
|
1267
|
-
}
|
|
1268
|
-
writeBlank(io);
|
|
1269
|
-
writeDisclosures(io, fields);
|
|
1270
|
-
writeInstallCommandAndScriptDisclosure(io, fields.pmCommand);
|
|
1271
|
-
if (fields.verbosity >= 1) {
|
|
1272
|
-
writeBlank(io);
|
|
1273
|
-
const keyed = [
|
|
1274
|
-
`operation: repair`,
|
|
1275
|
-
`root: ${fields.projectRoot}`,
|
|
1276
|
-
`package manager: ${fields.pm} (${fields.pmEvidence})`,
|
|
1277
|
-
`pin (unchanged): @deftai/directive-runtime@${fields.version}`,
|
|
1278
|
-
fields.needsPmRestore ? `PM: restore pin via exact add (${fields.restoreReason ?? "needed"})` : `PM: none (lockfile records pin; installed package matches)`,
|
|
1279
|
-
fields.needsPmRestore && fields.pmCommand ? `PM command: ${fields.pmCommand}` : `PM command: (none)`,
|
|
1280
|
-
`setup: runtime setup --operation repair --from ${fields.version} (wiring/GENERATION/readiness; does not create seed files; never migrates xbrief)`,
|
|
1281
|
-
INSTALL_SCRIPT_POLICY_PLAN_LINE
|
|
1282
|
-
];
|
|
1283
|
-
if (fields.gitHooksInactive) {
|
|
1284
|
-
keyed.push(
|
|
1285
|
-
"Git hooks: not installed until git init + directive install --repair (cannot set core.hooksPath=.githooks yet)"
|
|
1286
|
-
);
|
|
1287
|
-
}
|
|
1288
|
-
if (fields.nodeWarning) keyed.push(`node: ${fields.nodeWarning}`);
|
|
1289
|
-
writeKeyedBlock(io, keyed);
|
|
1290
|
-
writeBlank(io);
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
function presentUpgradePlan(io, fields) {
|
|
1294
|
-
const lock = lockfileNameFor(fields.pm);
|
|
1295
|
-
io.writeOut("Upgrade Directive\n");
|
|
1296
|
-
writeBlank(io);
|
|
1297
|
-
writeLabeled(io, "Project:", fields.projectRoot);
|
|
1298
|
-
writeLabeled(io, "Package manager:", `${fields.pm} (${fields.pmEvidence})`);
|
|
1299
|
-
writeLabeled(io, "Current version:", fields.currentVersion);
|
|
1300
|
-
writeLabeled(io, "Target version:", fields.targetVersion);
|
|
1301
|
-
writeBlank(io);
|
|
1302
|
-
if (fields.oldStyle) {
|
|
1303
|
-
writeHumanParagraph(io, "This project uses an older Directive layout (old-style).");
|
|
1304
|
-
} else if (fields.convertShapeId) {
|
|
1305
|
-
writeHumanParagraph(
|
|
1306
|
-
io,
|
|
1307
|
-
`This project uses a historical Directive layout ('${fields.convertShapeId}').`
|
|
1308
|
-
);
|
|
1309
|
-
}
|
|
1310
|
-
io.writeOut("If you approve, Directive will:\n");
|
|
1311
|
-
if (fields.oldStyle) {
|
|
1312
|
-
writeBullet(io, "remove the legacy `.deft/core` content copy (including any local edits)");
|
|
1313
|
-
writeBullet(
|
|
1314
|
-
io,
|
|
1315
|
-
`replace the legacy launcher pin with Directive ${fields.targetVersion}`
|
|
1316
|
-
);
|
|
1317
|
-
writeBullet(io, `update ${lock}`);
|
|
1318
|
-
writeBullet(io, "run setup and any required project-data migrations");
|
|
1319
|
-
} else if (fields.convertShapeId) {
|
|
1320
|
-
writeBullet(
|
|
1321
|
-
io,
|
|
1322
|
-
"remove the legacy install footprint (keeping xbrief/ and your files)"
|
|
1323
|
-
);
|
|
1324
|
-
writeBullet(io, `add Directive ${fields.targetVersion} with ${fields.pm}`);
|
|
1325
|
-
writeBullet(io, `update ${lock}`);
|
|
1326
|
-
writeBullet(io, "run setup and any required project-data migrations");
|
|
1327
|
-
} else if (fields.versionChange) {
|
|
1328
|
-
writeBullet(io, `update the exact version in package.json to ${fields.targetVersion}`);
|
|
1329
|
-
writeBullet(io, `update ${lock}`);
|
|
1330
|
-
writeBullet(io, "run project setup and any required project-data migrations");
|
|
1331
|
-
} else {
|
|
1332
|
-
writeBullet(
|
|
1333
|
-
io,
|
|
1334
|
-
`finish equal-version work for Directive ${fields.targetVersion}` + (fields.needSetup ? " (setup, then migration as needed)" : " (migration as needed)")
|
|
1335
|
-
);
|
|
1336
|
-
}
|
|
1337
|
-
writeBlank(io);
|
|
1338
|
-
writeHumanParagraph(io, "Your existing project settings will be preserved.");
|
|
1339
|
-
writeBlank(io);
|
|
1340
|
-
writeDisclosures(io, fields);
|
|
1341
|
-
if (fields.versionChange) {
|
|
1342
|
-
writeInstallCommandAndScriptDisclosure(io, fields.pmCommand);
|
|
1343
|
-
}
|
|
1344
|
-
if (fields.verbosity >= 1) {
|
|
1345
|
-
writeBlank(io);
|
|
1346
|
-
const keyed = [
|
|
1347
|
-
`operation: upgrade`,
|
|
1348
|
-
`root: ${fields.projectRoot}`,
|
|
1349
|
-
`package manager: ${fields.pm} (${fields.pmEvidence})`,
|
|
1350
|
-
`current: @deftai/directive-runtime@${fields.currentVersion}${fields.oldStyle ? " (old-style)" : ""}`,
|
|
1351
|
-
`target: @deftai/directive-runtime@${fields.targetVersion}`,
|
|
1352
|
-
fields.versionChange ? `PM: exact add target (pin + lockfile change)` : `PM: none (equal version; package already matches pin)`,
|
|
1353
|
-
fields.versionChange && fields.pmCommand ? `PM command: ${fields.pmCommand}` : `PM command: (none)`,
|
|
1354
|
-
fields.needSetup ? `setup: runtime setup --operation upgrade --from ${fields.currentVersion}` : `setup: none (wiring current for pin)`,
|
|
1355
|
-
fields.needMigrate ? `migrate: runtime migrate --from ${fields.currentVersion}` : `migrate: none`,
|
|
1356
|
-
`disclosure: target runtime performs required setup/migrations; project settings/data preserved`,
|
|
1357
|
-
`global launcher: unchanged`,
|
|
1358
|
-
INSTALL_SCRIPT_POLICY_PLAN_LINE
|
|
1359
|
-
];
|
|
1360
|
-
if (fields.oldStyle) {
|
|
1361
|
-
keyed.splice(
|
|
1362
|
-
5,
|
|
1363
|
-
0,
|
|
1364
|
-
`remove: .deft/core (legacy content copy, even if edited)`,
|
|
1365
|
-
`replace: @deftai/directive pin \u2192 @deftai/directive-runtime@${fields.targetVersion}`
|
|
1366
|
-
);
|
|
1367
|
-
}
|
|
1368
|
-
if (fields.gitHooksInactive) {
|
|
1369
|
-
keyed.push(
|
|
1370
|
-
"Git hooks: not installed until git init + directive install --repair (cannot set core.hooksPath=.githooks yet)"
|
|
1371
|
-
);
|
|
1372
|
-
}
|
|
1373
|
-
if (fields.nodeWarning) keyed.push(`node: ${fields.nodeWarning}`);
|
|
1374
|
-
writeKeyedBlock(io, keyed);
|
|
1375
|
-
writeBlank(io);
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
function classifyUninstallPath(pathLabel) {
|
|
1379
|
-
const p = pathLabel.replace(/\s*\(.*\)$/, "");
|
|
1380
|
-
if (p === ".deft/core" || p.startsWith(".deft/core/")) return "legacy-core";
|
|
1381
|
-
if (p.includes("/skills/") || /\/SKILL\.md$/i.test(p)) return "skill";
|
|
1382
|
-
if (p === ".claude/settings.json" || p === ".cursor/hooks.json" || p === ".codex/hooks.json" || p === ".grok/hooks/deft.json" || p.endsWith("/hooks.json") || p.includes("/hooks/")) {
|
|
1383
|
-
return "host-hook";
|
|
1384
|
-
}
|
|
1385
|
-
if (p.startsWith(".githooks/") || p.includes("githooks")) return "git-hook";
|
|
1386
|
-
if (p === ".deft/GENERATION.json" || p.startsWith(".deft/directive/") || p === "AGENTS.md" || p.startsWith("AGENTS.md") || p === "Taskfile.yml" || p.startsWith("Taskfile.yml") || p === ".gitignore") {
|
|
1387
|
-
return "wiring";
|
|
1388
|
-
}
|
|
1389
|
-
return "other";
|
|
1390
|
-
}
|
|
1391
|
-
function summarizeUninstallRemovals(pathInventory, opts) {
|
|
1392
|
-
let skillPointers = 0;
|
|
1393
|
-
let hostHooks = 0;
|
|
1394
|
-
let gitHooks = 0;
|
|
1395
|
-
let projectWiring = 0;
|
|
1396
|
-
let other = 0;
|
|
1397
|
-
let legacyCore = Boolean(opts.legacyCore);
|
|
1398
|
-
for (const p of pathInventory) {
|
|
1399
|
-
switch (classifyUninstallPath(p)) {
|
|
1400
|
-
case "skill":
|
|
1401
|
-
skillPointers += 1;
|
|
1402
|
-
break;
|
|
1403
|
-
case "host-hook":
|
|
1404
|
-
hostHooks += 1;
|
|
1405
|
-
break;
|
|
1406
|
-
case "git-hook":
|
|
1407
|
-
gitHooks += 1;
|
|
1408
|
-
break;
|
|
1409
|
-
case "wiring":
|
|
1410
|
-
projectWiring += 1;
|
|
1411
|
-
break;
|
|
1412
|
-
case "legacy-core":
|
|
1413
|
-
legacyCore = true;
|
|
1414
|
-
break;
|
|
1415
|
-
default:
|
|
1416
|
-
other += 1;
|
|
1417
|
-
break;
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
return {
|
|
1421
|
-
skillPointers,
|
|
1422
|
-
hostHooks,
|
|
1423
|
-
gitHooks,
|
|
1424
|
-
projectWiring,
|
|
1425
|
-
other,
|
|
1426
|
-
legacyCore,
|
|
1427
|
-
willRemovePin: opts.willRemovePin,
|
|
1428
|
-
pathInventory: [...pathInventory],
|
|
1429
|
-
preserveLabels: [...opts.preserveLabels ?? []]
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
function writeUninstallActionBullets(io, removal, pm) {
|
|
1433
|
-
io.writeOut("If you approve, Directive will:\n");
|
|
1434
|
-
if (removal.skillPointers > 0) {
|
|
1435
|
-
writeBullet(
|
|
1436
|
-
io,
|
|
1437
|
-
`remove ${removal.skillPointers} skill pointer${removal.skillPointers === 1 ? "" : "s"} across agent hosts`
|
|
1438
|
-
);
|
|
1439
|
-
}
|
|
1440
|
-
if (removal.hostHooks > 0) {
|
|
1441
|
-
writeBullet(
|
|
1442
|
-
io,
|
|
1443
|
-
`clear Directive entries from ${removal.hostHooks} host hook config${removal.hostHooks === 1 ? "" : "s"}`
|
|
1444
|
-
);
|
|
1445
|
-
}
|
|
1446
|
-
const wiringBits = [];
|
|
1447
|
-
if (removal.gitHooks > 0) wiringBits.push("git hooks");
|
|
1448
|
-
if (removal.projectWiring > 0) wiringBits.push("GENERATION and project wiring");
|
|
1449
|
-
if (removal.legacyCore) wiringBits.push("legacy .deft/core");
|
|
1450
|
-
if (wiringBits.length > 0) {
|
|
1451
|
-
writeBullet(io, `remove ${wiringBits.join(", ")}`);
|
|
1452
|
-
} else if (removal.other > 0) {
|
|
1453
|
-
writeBullet(io, `remove ${removal.other} other Directive-owned path${removal.other === 1 ? "" : "s"}`);
|
|
1454
|
-
}
|
|
1455
|
-
if (removal.willRemovePin) {
|
|
1456
|
-
writeBullet(
|
|
1457
|
-
io,
|
|
1458
|
-
pm ? `remove the Directive runtime pin with ${pm}` : "remove the Directive runtime pin from package.json"
|
|
1459
|
-
);
|
|
1460
|
-
}
|
|
1461
|
-
if (removal.skillPointers === 0 && removal.hostHooks === 0 && wiringBits.length === 0 && !removal.willRemovePin && removal.other === 0) {
|
|
1462
|
-
writeBullet(io, "remove remaining Directive-owned wiring from this project");
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
function presentUninstallPlan(io, fields) {
|
|
1466
|
-
io.writeOut("Uninstall Directive\n");
|
|
1467
|
-
writeBlank(io);
|
|
1468
|
-
writeLabeled(io, "Project:", fields.projectRoot);
|
|
1469
|
-
if (fields.pm && fields.pmEvidence) {
|
|
1470
|
-
writeLabeled(io, "Package manager:", `${fields.pm} (${fields.pmEvidence})`);
|
|
1471
|
-
}
|
|
1472
|
-
if (fields.pinVersion) {
|
|
1473
|
-
writeLabeled(io, "Pinned version:", fields.pinVersion);
|
|
1474
|
-
}
|
|
1475
|
-
writeBlank(io);
|
|
1476
|
-
if (fields.removal) {
|
|
1477
|
-
writeUninstallActionBullets(io, fields.removal, fields.pm);
|
|
1478
|
-
writeBlank(io);
|
|
1479
|
-
const keep = fields.removal.preserveLabels.length > 0 ? fields.removal.preserveLabels.join(", ") : "xbrief/ project data and unrelated files";
|
|
1480
|
-
writeHumanParagraph(io, `Your ${keep} stay in place.`);
|
|
1481
|
-
} else {
|
|
1482
|
-
writeHumanParagraph(
|
|
1483
|
-
io,
|
|
1484
|
-
"I'll remove Directive-owned wiring and the runtime pin from this project. Your xbrief/ project data and unrelated files stay in place."
|
|
1485
|
-
);
|
|
1486
|
-
}
|
|
1487
|
-
writeBlank(io);
|
|
1488
|
-
writePmCommandVerbose(
|
|
1489
|
-
io,
|
|
1490
|
-
"Package manager command (Directive runs this):",
|
|
1491
|
-
fields.pmCommand,
|
|
1492
|
-
fields.verbosity
|
|
1493
|
-
);
|
|
1494
|
-
if (fields.verbosity >= 1) {
|
|
1495
|
-
writeBlank(io);
|
|
1496
|
-
if (fields.removal && fields.removal.pathInventory.length > 0) {
|
|
1497
|
-
io.writeOut("Remove paths:\n");
|
|
1498
|
-
for (const p of fields.removal.pathInventory) {
|
|
1499
|
-
io.writeOut(` ${p}
|
|
1500
|
-
`);
|
|
1501
|
-
}
|
|
1502
|
-
writeBlank(io);
|
|
1503
|
-
}
|
|
1504
|
-
if (fields.extraHumanLines) {
|
|
1505
|
-
for (const line of fields.extraHumanLines) {
|
|
1506
|
-
io.writeOut(`${line}
|
|
1507
|
-
`);
|
|
1508
|
-
}
|
|
1509
|
-
writeBlank(io);
|
|
1510
|
-
}
|
|
1511
|
-
const keyed = [
|
|
1512
|
-
`operation: uninstall`,
|
|
1513
|
-
`root: ${fields.projectRoot}`,
|
|
1514
|
-
fields.pm ? `package manager: ${fields.pm} (${fields.pmEvidence})` : `package manager: (none)`,
|
|
1515
|
-
fields.pinVersion ? `pin: ${fields.pinVersion}` : `pin: (none)`,
|
|
1516
|
-
fields.pmCommand ? `PM command: ${fields.pmCommand}` : `PM command: (none)`
|
|
1517
|
-
];
|
|
1518
|
-
if (fields.removal) {
|
|
1519
|
-
keyed.push(
|
|
1520
|
-
`counts: skills=${fields.removal.skillPointers} hostHooks=${fields.removal.hostHooks} gitHooks=${fields.removal.gitHooks} wiring=${fields.removal.projectWiring} other=${fields.removal.other}`
|
|
1521
|
-
);
|
|
1522
|
-
}
|
|
1523
|
-
writeKeyedBlock(io, keyed);
|
|
1524
|
-
writeBlank(io);
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
function presentPathSummary(io, label, summary, verbosity) {
|
|
1528
|
-
const created = summary.created ?? [];
|
|
1529
|
-
const modified = summary.modified ?? [];
|
|
1530
|
-
const removed = summary.removed ?? [];
|
|
1531
|
-
const migrated = summary.migrated ?? [];
|
|
1532
|
-
const archived = summary.archived ?? [];
|
|
1533
|
-
if (verbosity >= 2) {
|
|
1534
|
-
io.writeOut(
|
|
1535
|
-
`${label}: created=${JSON.stringify(created)} modified=${JSON.stringify(modified)} removed=${JSON.stringify(removed)}` + (migrated.length > 0 ? ` migrated=${JSON.stringify(migrated)}` : "") + (archived.length > 0 ? ` archived=${JSON.stringify(archived)}` : "") + (summary.conflicts && summary.conflicts.length > 0 ? ` conflicts=${JSON.stringify(summary.conflicts)}` : "") + "\n"
|
|
1536
|
-
);
|
|
1537
|
-
} else if (verbosity >= 1) {
|
|
1538
|
-
io.writeOut(
|
|
1539
|
-
`${label}: created=${created.length} modified=${modified.length} removed=${removed.length}` + (migrated.length > 0 ? ` migrated=${migrated.length}` : "") + (archived.length > 0 ? ` archived=${archived.length}` : "") + "\n"
|
|
1540
|
-
);
|
|
1541
|
-
}
|
|
1542
|
-
if (summary.message && verbosity >= 1) {
|
|
1543
|
-
io.writeOut(`${summary.message}
|
|
1544
|
-
`);
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
function gitHooksInactiveFromSummary(summary) {
|
|
1548
|
-
const msg = summary?.message ?? "";
|
|
1549
|
-
return /Git hooks inactive/i.test(msg);
|
|
1550
|
-
}
|
|
1551
|
-
function presentInstallSuccess(io, opts) {
|
|
1552
|
-
const lock = lockfileNameFor(opts.pm);
|
|
1553
|
-
const hooksInactive = gitHooksInactiveFromSummary(opts.summary);
|
|
1554
|
-
io.writeOut(`\u2713 Installed Directive ${opts.version}
|
|
1555
|
-
`);
|
|
1556
|
-
io.writeOut(`\u2713 Created Directive's project files
|
|
1557
|
-
`);
|
|
1558
|
-
if (hooksInactive) {
|
|
1559
|
-
io.writeOut(`\u2713 Verified the project-local installation (git hooks not installed yet)
|
|
1560
|
-
`);
|
|
1561
|
-
} else {
|
|
1562
|
-
io.writeOut(`\u2713 Verified the project-local installation
|
|
1563
|
-
`);
|
|
1564
|
-
}
|
|
1565
|
-
writeBlank(io);
|
|
1566
|
-
if (hooksInactive) {
|
|
1567
|
-
writeHumanParagraph(io, `Directive ${opts.version} is installed in ${opts.projectRoot}.`);
|
|
1568
|
-
writeHumanParagraph(io, HUMAN_GIT_HOOKS_INACTIVE_LINE);
|
|
1569
|
-
} else {
|
|
1570
|
-
writeHumanParagraph(io, `Directive ${opts.version} is ready in ${opts.projectRoot}.`);
|
|
1571
|
-
}
|
|
1572
|
-
writeBlank(io);
|
|
1573
|
-
io.writeOut("Next:\n");
|
|
1574
|
-
if (hooksInactive) {
|
|
1575
|
-
io.writeOut(" git init\n");
|
|
1576
|
-
io.writeOut(" directive install --repair\n");
|
|
1577
|
-
} else {
|
|
1578
|
-
io.writeOut(" directive --help\n");
|
|
1579
|
-
}
|
|
1580
|
-
writeBlank(io);
|
|
1581
|
-
io.writeOut("Review and commit:\n");
|
|
1582
|
-
io.writeOut(" package.json\n");
|
|
1583
|
-
io.writeOut(` ${lock}
|
|
1584
|
-
`);
|
|
1585
|
-
const review = collectReviewPaths(opts.summary);
|
|
1586
|
-
for (const p of review) {
|
|
1587
|
-
io.writeOut(` ${p}
|
|
1588
|
-
`);
|
|
1589
|
-
}
|
|
1590
|
-
writeBlank(io);
|
|
1591
|
-
writeHumanParagraph(io, "Commands in this project use its pinned Directive version.");
|
|
1592
|
-
writeHumanParagraph(io, "Updating the global launcher will not upgrade this project.");
|
|
1593
|
-
if (opts.verbosity >= 1 && opts.summary) {
|
|
1594
|
-
writeBlank(io);
|
|
1595
|
-
presentPathSummary(io, "setup install", opts.summary, opts.verbosity);
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
function presentRepairSuccess(io, opts) {
|
|
1599
|
-
const hooksInactive = gitHooksInactiveFromSummary(opts.summary);
|
|
1600
|
-
if (opts.restoredPackage) {
|
|
1601
|
-
io.writeOut(`\u2713 Restored the installed Directive ${opts.version} package
|
|
1602
|
-
`);
|
|
1603
|
-
} else {
|
|
1604
|
-
io.writeOut(`\u2713 Verified the installed Directive ${opts.version} package
|
|
1605
|
-
`);
|
|
1606
|
-
}
|
|
1607
|
-
io.writeOut(`\u2713 Reran project setup
|
|
1608
|
-
`);
|
|
1609
|
-
if (opts.summary && opts.summary.created.length + opts.summary.modified.length > 0) {
|
|
1610
|
-
io.writeOut(`\u2713 Restored managed project files
|
|
1611
|
-
`);
|
|
1612
|
-
}
|
|
1613
|
-
writeBlank(io);
|
|
1614
|
-
if (hooksInactive) {
|
|
1615
|
-
writeHumanParagraph(io, `Directive ${opts.version} setup finished.`);
|
|
1616
|
-
writeHumanParagraph(io, HUMAN_GIT_HOOKS_INACTIVE_LINE);
|
|
1617
|
-
writeBlank(io);
|
|
1618
|
-
io.writeOut("Next:\n");
|
|
1619
|
-
io.writeOut(" git init\n");
|
|
1620
|
-
io.writeOut(" directive install --repair\n");
|
|
1621
|
-
} else {
|
|
1622
|
-
writeHumanParagraph(io, `Directive ${opts.version} is ready.`);
|
|
1623
|
-
}
|
|
1624
|
-
writeHumanParagraph(io, "The project version has not changed.");
|
|
1625
|
-
const review = collectReviewPaths(opts.summary);
|
|
1626
|
-
if (opts.restoredPackage && opts.pm) {
|
|
1627
|
-
const lock = lockfileNameFor(opts.pm);
|
|
1628
|
-
if (!review.includes("package.json")) review.unshift("package.json");
|
|
1629
|
-
if (!review.includes(lock)) review.splice(1, 0, lock);
|
|
1630
|
-
}
|
|
1631
|
-
if (review.length > 0) {
|
|
1632
|
-
writeBlank(io);
|
|
1633
|
-
io.writeOut("Review and commit:\n");
|
|
1634
|
-
for (const p of review) {
|
|
1635
|
-
io.writeOut(` ${p}
|
|
1636
|
-
`);
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
if (opts.verbosity >= 1 && opts.summary) {
|
|
1640
|
-
writeBlank(io);
|
|
1641
|
-
presentPathSummary(io, "setup repair", opts.summary, opts.verbosity);
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
function presentUpgradeSuccess(io, opts) {
|
|
1645
|
-
if (opts.versionChange) {
|
|
1646
|
-
io.writeOut(`\u2713 Installed Directive ${opts.version}
|
|
1647
|
-
`);
|
|
1648
|
-
}
|
|
1649
|
-
io.writeOut(`\u2713 Completed project setup
|
|
1650
|
-
`);
|
|
1651
|
-
io.writeOut(`\u2713 Verified the project-local installation
|
|
1652
|
-
`);
|
|
1653
|
-
writeBlank(io);
|
|
1654
|
-
if (opts.versionChange) {
|
|
1655
|
-
writeHumanParagraph(io, `This project now uses Directive ${opts.version}.`);
|
|
1656
|
-
writeHumanParagraph(io, "The global launcher was not changed.");
|
|
1657
|
-
} else {
|
|
1658
|
-
writeHumanParagraph(io, `Directive ${opts.version} is ready.`);
|
|
1659
|
-
writeHumanParagraph(io, "The project version has not changed.");
|
|
1660
|
-
}
|
|
1661
|
-
writeBlank(io);
|
|
1662
|
-
io.writeOut("Review and commit:\n");
|
|
1663
|
-
if (opts.versionChange) {
|
|
1664
|
-
io.writeOut(" package.json\n");
|
|
1665
|
-
io.writeOut(` ${lockfileNameFor(opts.pm)}
|
|
1666
|
-
`);
|
|
1667
|
-
}
|
|
1668
|
-
const review = [
|
|
1669
|
-
...collectReviewPaths(opts.summary),
|
|
1670
|
-
...collectReviewPaths(opts.migrateSummary)
|
|
1671
|
-
];
|
|
1672
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1673
|
-
for (const p of review) {
|
|
1674
|
-
if (seen.has(p)) continue;
|
|
1675
|
-
seen.add(p);
|
|
1676
|
-
io.writeOut(` ${p}
|
|
1677
|
-
`);
|
|
1678
|
-
}
|
|
1679
|
-
if (opts.verbosity >= 1) {
|
|
1680
|
-
writeBlank(io);
|
|
1681
|
-
if (opts.summary) presentPathSummary(io, "setup upgrade", opts.summary, opts.verbosity);
|
|
1682
|
-
if (opts.migrateSummary) {
|
|
1683
|
-
presentPathSummary(io, "migrate", opts.migrateSummary, opts.verbosity);
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
function presentUninstallSuccess(io, opts) {
|
|
1688
|
-
if (opts.complete) {
|
|
1689
|
-
io.writeOut(`\u2713 Uninstalled Directive from ${opts.projectRoot}
|
|
1690
|
-
`);
|
|
1691
|
-
} else {
|
|
1692
|
-
io.writeOut(`\u26A0 Uninstall partially finished in ${opts.projectRoot}
|
|
1693
|
-
`);
|
|
1694
|
-
}
|
|
1695
|
-
writeBlank(io);
|
|
1696
|
-
const parts = [];
|
|
1697
|
-
if (opts.removal) {
|
|
1698
|
-
if (opts.removal.skillPointers > 0) parts.push("skill pointers");
|
|
1699
|
-
if (opts.removal.hostHooks > 0) parts.push("host hooks");
|
|
1700
|
-
if (opts.removal.gitHooks > 0 || opts.removal.projectWiring > 0) parts.push("project wiring");
|
|
1701
|
-
if (opts.removal.legacyCore) parts.push("legacy core");
|
|
1702
|
-
if (opts.removal.willRemovePin) parts.push("the runtime pin");
|
|
1703
|
-
}
|
|
1704
|
-
if (parts.length > 0) {
|
|
1705
|
-
writeHumanParagraph(io, `Removed ${parts.join(", ")}.`);
|
|
1706
|
-
} else {
|
|
1707
|
-
writeHumanParagraph(io, "Removed Directive-owned wiring from this project.");
|
|
1708
|
-
}
|
|
1709
|
-
const preserved = opts.preserved ?? opts.removal?.preserveLabels ?? [];
|
|
1710
|
-
if (preserved.length > 0) {
|
|
1711
|
-
writeHumanParagraph(io, `Preserved: ${preserved.join(", ")}.`);
|
|
1712
|
-
} else {
|
|
1713
|
-
writeHumanParagraph(io, "Preserved xbrief/ project data and unrelated files.");
|
|
1714
|
-
}
|
|
1715
|
-
if (!opts.complete && opts.leftovers && opts.leftovers.length > 0) {
|
|
1716
|
-
writeBlank(io);
|
|
1717
|
-
writeHumanParagraph(io, `Known leftovers: ${opts.leftovers.join(", ")}.`);
|
|
1718
|
-
}
|
|
1719
|
-
writeBlank(io);
|
|
1720
|
-
writeHumanParagraph(
|
|
1721
|
-
io,
|
|
1722
|
-
"To uninstall the global launcher (other repos may still need it), run: npm uninstall -g @deftai/directive"
|
|
1723
|
-
);
|
|
1724
|
-
if (opts.verbosity >= 1) {
|
|
1725
|
-
writeBlank(io);
|
|
1726
|
-
const paths = opts.removedPaths ?? opts.removal?.pathInventory ?? [];
|
|
1727
|
-
if (paths.length > 0) {
|
|
1728
|
-
io.writeOut("Removed paths:\n");
|
|
1729
|
-
for (const p of paths) io.writeOut(` ${p}
|
|
1730
|
-
`);
|
|
1731
|
-
}
|
|
1732
|
-
if (preserved.length > 0) {
|
|
1733
|
-
io.writeOut(`preserved: ${JSON.stringify(preserved)}
|
|
1734
|
-
`);
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
function presentAmbiguousYes(io, pinVersion) {
|
|
1739
|
-
io.writeErr(
|
|
1740
|
-
`Directive is already configured at version ${pinVersion}.
|
|
1741
|
-
To repair this installation, select repair explicitly:
|
|
1742
|
-
|
|
1743
|
-
directive install --repair --yes
|
|
1744
|
-
|
|
1745
|
-
For a deliberate version or data upgrade, use directive upgrade --yes.
|
|
1746
|
-
No project files were changed.
|
|
1747
|
-
`
|
|
1748
|
-
);
|
|
1749
|
-
}
|
|
1750
|
-
function presentSetupFailedAfterInstall(io, opts) {
|
|
1751
|
-
if (opts.operation === "install" || opts.operation === "upgrade") {
|
|
1752
|
-
io.writeErr(
|
|
1753
|
-
`Directive ${opts.version} is installed, but project setup did not finish.
|
|
1754
|
-
`
|
|
1755
|
-
);
|
|
1756
|
-
} else {
|
|
1757
|
-
io.writeErr(`Directive ${opts.version} repair ran, but project setup did not finish.
|
|
1758
|
-
`);
|
|
1759
|
-
}
|
|
1760
|
-
writeBlankErr(io);
|
|
1761
|
-
if (opts.failedPaths && opts.failedPaths.length > 0) {
|
|
1762
|
-
io.writeErr("Setup could not update:\n");
|
|
1763
|
-
for (const p of opts.failedPaths) {
|
|
1764
|
-
io.writeErr(` ${p}
|
|
1765
|
-
`);
|
|
1766
|
-
}
|
|
1767
|
-
writeBlankErr(io);
|
|
1768
|
-
}
|
|
1769
|
-
if (opts.reason) {
|
|
1770
|
-
io.writeErr("Reason:\n");
|
|
1771
|
-
io.writeErr(` ${opts.reason}
|
|
1772
|
-
`);
|
|
1773
|
-
writeBlankErr(io);
|
|
1774
|
-
}
|
|
1775
|
-
if (opts.operation === "install" || opts.operation === "upgrade") {
|
|
1776
|
-
io.writeErr(
|
|
1777
|
-
`The project pin and lockfile now reference Directive ${opts.version}.
|
|
1778
|
-
`
|
|
1779
|
-
);
|
|
1780
|
-
writeBlankErr(io);
|
|
1781
|
-
}
|
|
1782
|
-
io.writeErr("Correct the cause, then run:\n");
|
|
1783
|
-
io.writeErr(" directive install --repair\n");
|
|
1784
|
-
if (opts.operation === "upgrade") {
|
|
1785
|
-
io.writeErr(" (or directive upgrade again for data migration)\n");
|
|
1786
|
-
}
|
|
1787
|
-
writeBlankErr(io);
|
|
1788
|
-
io.writeErr("Review the existing changes before committing.\n");
|
|
1789
|
-
}
|
|
1790
|
-
function presentMigrateFailedAfterUpgrade(io, opts) {
|
|
1791
|
-
if (opts.versionChange) {
|
|
1792
|
-
io.writeErr(
|
|
1793
|
-
`Directive ${opts.version} is installed, but project data migration did not finish.
|
|
1794
|
-
`
|
|
1795
|
-
);
|
|
1796
|
-
} else {
|
|
1797
|
-
io.writeErr(
|
|
1798
|
-
`Directive ${opts.version} data migration did not finish.
|
|
1799
|
-
`
|
|
1800
|
-
);
|
|
1801
|
-
}
|
|
1802
|
-
writeBlankErr(io);
|
|
1803
|
-
const failed = opts.summary?.conflicts ?? [];
|
|
1804
|
-
const migrated = opts.summary?.migrated ?? [];
|
|
1805
|
-
if (failed.length > 0) {
|
|
1806
|
-
io.writeErr("Migration could not update:\n");
|
|
1807
|
-
for (const p of failed) {
|
|
1808
|
-
io.writeErr(` ${p}
|
|
1809
|
-
`);
|
|
1810
|
-
}
|
|
1811
|
-
writeBlankErr(io);
|
|
1812
|
-
}
|
|
1813
|
-
if (migrated.length > 0) {
|
|
1814
|
-
io.writeErr("Migrated:\n");
|
|
1815
|
-
for (const p of migrated) {
|
|
1816
|
-
io.writeErr(` ${p}
|
|
1817
|
-
`);
|
|
1818
|
-
}
|
|
1819
|
-
writeBlankErr(io);
|
|
1820
|
-
}
|
|
1821
|
-
const reason = opts.reason ?? opts.summary?.message ?? null;
|
|
1822
|
-
if (reason) {
|
|
1823
|
-
io.writeErr("Reason:\n");
|
|
1824
|
-
io.writeErr(` ${reason}
|
|
1825
|
-
`);
|
|
1826
|
-
writeBlankErr(io);
|
|
1827
|
-
}
|
|
1828
|
-
if (opts.versionChange) {
|
|
1829
|
-
io.writeErr(
|
|
1830
|
-
`The project pin and lockfile now reference Directive ${opts.version}.
|
|
1831
|
-
`
|
|
1832
|
-
);
|
|
1833
|
-
writeBlankErr(io);
|
|
1834
|
-
}
|
|
1835
|
-
io.writeErr("Correct the cause, then run:\n");
|
|
1836
|
-
io.writeErr(" directive upgrade\n");
|
|
1837
|
-
writeBlankErr(io);
|
|
1838
|
-
io.writeErr("Review the existing changes before committing.\n");
|
|
1839
|
-
}
|
|
1840
|
-
function writeBlankErr(io) {
|
|
1841
|
-
io.writeErr("\n");
|
|
1842
|
-
}
|
|
1843
|
-
function collectReviewPaths(summary) {
|
|
1844
|
-
if (!summary) return [];
|
|
1845
|
-
const prefer = [
|
|
1846
|
-
"AGENTS.md",
|
|
1847
|
-
".gitignore",
|
|
1848
|
-
"Taskfile.yml",
|
|
1849
|
-
".deft/GENERATION.json"
|
|
1850
|
-
];
|
|
1851
|
-
const migrated = summary.migrated ?? [];
|
|
1852
|
-
const archived = summary.archived ?? [];
|
|
1853
|
-
const all = [...summary.created, ...summary.modified, ...migrated, ...archived];
|
|
1854
|
-
const out = [];
|
|
1855
|
-
for (const p of prefer) {
|
|
1856
|
-
if (all.includes(p)) out.push(p);
|
|
1857
|
-
}
|
|
1858
|
-
for (const p of [...migrated, ...archived]) {
|
|
1859
|
-
if (!out.includes(p)) out.push(p);
|
|
1860
|
-
}
|
|
1861
|
-
for (const p of all) {
|
|
1862
|
-
if (out.includes(p)) continue;
|
|
1863
|
-
if (p === ".claude/settings.json" || p === ".cursor/hooks.json" || p === ".codex/hooks.json" || p === ".grok/hooks/deft.json") {
|
|
1864
|
-
out.push(p);
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
return out.slice(0, 16);
|
|
1868
|
-
}
|
|
1869
|
-
function tryParseSetupSummary(stdout) {
|
|
1870
|
-
const trimmed = stdout.trim();
|
|
1871
|
-
if (!trimmed) return null;
|
|
1872
|
-
try {
|
|
1873
|
-
const last = trimmed.split("\n").filter(Boolean).pop() ?? "";
|
|
1874
|
-
const parsed = JSON.parse(last);
|
|
1875
|
-
if (!parsed || typeof parsed !== "object") return null;
|
|
1876
|
-
const rec = parsed;
|
|
1877
|
-
return {
|
|
1878
|
-
created: Array.isArray(rec.created) ? rec.created : [],
|
|
1879
|
-
modified: Array.isArray(rec.modified) ? rec.modified : [],
|
|
1880
|
-
removed: Array.isArray(rec.removed) ? rec.removed : [],
|
|
1881
|
-
migrated: Array.isArray(rec.migrated) ? rec.migrated : void 0,
|
|
1882
|
-
archived: Array.isArray(rec.archived) ? rec.archived : void 0,
|
|
1883
|
-
conflicts: Array.isArray(rec.conflicts) ? rec.conflicts : void 0,
|
|
1884
|
-
message: typeof rec.message === "string" ? rec.message : void 0
|
|
1885
|
-
};
|
|
1886
|
-
} catch {
|
|
1887
|
-
return null;
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
// packages/cli/src/registry.ts
|
|
1892
|
-
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
1893
|
-
function parseExactVersionParts(version) {
|
|
1894
|
-
const trimmed = version.trim();
|
|
1895
|
-
const plus = trimmed.indexOf("+");
|
|
1896
|
-
const noBuild = plus === -1 ? trimmed : trimmed.slice(0, plus);
|
|
1897
|
-
if (plus !== -1) {
|
|
1898
|
-
const build = trimmed.slice(plus + 1);
|
|
1899
|
-
if (!/^[0-9A-Za-z.-]+$/.test(build)) return null;
|
|
1900
|
-
}
|
|
1901
|
-
if (noBuild.length === 0 || !isExactSemver(noBuild)) return null;
|
|
1902
|
-
const dash = noBuild.indexOf("-");
|
|
1903
|
-
const core = dash === -1 ? noBuild : noBuild.slice(0, dash);
|
|
1904
|
-
const pre = dash === -1 ? null : noBuild.slice(dash + 1).split(".");
|
|
1905
|
-
const [majS, minS, patS] = core.split(".");
|
|
1906
|
-
const major = Number(majS);
|
|
1907
|
-
const minor = Number(minS);
|
|
1908
|
-
const patch = Number(patS);
|
|
1909
|
-
if (![major, minor, patch].every((n) => Number.isInteger(n) && n >= 0)) return null;
|
|
1910
|
-
return { major, minor, patch, pre };
|
|
1911
|
-
}
|
|
1912
|
-
function comparePreParts(a, b) {
|
|
1913
|
-
const len = Math.max(a.length, b.length);
|
|
1914
|
-
for (let i = 0; i < len; i++) {
|
|
1915
|
-
const left = a[i];
|
|
1916
|
-
const right = b[i];
|
|
1917
|
-
if (left === void 0) return -1;
|
|
1918
|
-
if (right === void 0) return 1;
|
|
1919
|
-
const leftNum = /^\d+$/.test(left) ? Number(left) : null;
|
|
1920
|
-
const rightNum = /^\d+$/.test(right) ? Number(right) : null;
|
|
1921
|
-
if (leftNum !== null && rightNum !== null) {
|
|
1922
|
-
if (leftNum !== rightNum) return leftNum < rightNum ? -1 : 1;
|
|
1923
|
-
continue;
|
|
1924
|
-
}
|
|
1925
|
-
if (leftNum !== null) return -1;
|
|
1926
|
-
if (rightNum !== null) return 1;
|
|
1927
|
-
if (left !== right) return left < right ? -1 : 1;
|
|
1928
|
-
}
|
|
1929
|
-
return 0;
|
|
1930
|
-
}
|
|
1931
|
-
function compareExactVersions(a, b) {
|
|
1932
|
-
const left = parseExactVersionParts(a);
|
|
1933
|
-
const right = parseExactVersionParts(b);
|
|
1934
|
-
if (left === null || right === null) {
|
|
1935
|
-
throw new Error(
|
|
1936
|
-
`compareExactVersions: invalid exact SemVer (${JSON.stringify(a)} vs ${JSON.stringify(b)})`
|
|
1937
|
-
);
|
|
1938
|
-
}
|
|
1939
|
-
if (left.major !== right.major) return left.major < right.major ? -1 : 1;
|
|
1940
|
-
if (left.minor !== right.minor) return left.minor < right.minor ? -1 : 1;
|
|
1941
|
-
if (left.patch !== right.patch) return left.patch < right.patch ? -1 : 1;
|
|
1942
|
-
if (left.pre === null && right.pre === null) return 0;
|
|
1943
|
-
if (left.pre === null) return 1;
|
|
1944
|
-
if (right.pre === null) return -1;
|
|
1945
|
-
return comparePreParts(left.pre, right.pre);
|
|
1946
|
-
}
|
|
1947
|
-
function validateTargetVersionSpec(raw) {
|
|
1948
|
-
const trimmed = raw.trim();
|
|
1949
|
-
if (trimmed.length === 0) {
|
|
1950
|
-
return { ok: false, message: "--target-version requires an exact version." };
|
|
1951
|
-
}
|
|
1952
|
-
if (trimmed === "latest" || trimmed === "next" || trimmed === "canary") {
|
|
1953
|
-
return {
|
|
1954
|
-
ok: false,
|
|
1955
|
-
message: `--target-version rejects tags (${trimmed}); pass an exact version.`
|
|
1956
|
-
};
|
|
1957
|
-
}
|
|
1958
|
-
if (!isExactSemver(trimmed)) {
|
|
1959
|
-
return {
|
|
1960
|
-
ok: false,
|
|
1961
|
-
message: `--target-version must be an exact SemVer (got ${trimmed}). Ranges and tags are rejected.`
|
|
1962
|
-
};
|
|
1963
|
-
}
|
|
1964
|
-
return { ok: true, version: trimmed };
|
|
1965
|
-
}
|
|
1966
|
-
function resolveLatestStableVersion(env = process.env, projectRoot) {
|
|
1967
|
-
const result = spawnSync5(
|
|
1968
|
-
"npm",
|
|
1969
|
-
["view", RUNTIME_PACKAGE_NAME, "version", "--json"],
|
|
1970
|
-
{
|
|
1971
|
-
encoding: "utf8",
|
|
1972
|
-
env,
|
|
1973
|
-
cwd: projectRoot,
|
|
1974
|
-
shell: process.platform === "win32"
|
|
1975
|
-
}
|
|
1976
|
-
);
|
|
1977
|
-
if (result.error) {
|
|
1978
|
-
return {
|
|
1979
|
-
ok: false,
|
|
1980
|
-
message: `Failed to query registry for ${RUNTIME_PACKAGE_NAME}: ${result.error.message}`
|
|
1981
|
-
};
|
|
1982
|
-
}
|
|
1983
|
-
if (result.status !== 0) {
|
|
1984
|
-
return {
|
|
1985
|
-
ok: false,
|
|
1986
|
-
message: `npm view ${RUNTIME_PACKAGE_NAME} version failed` + (result.stderr ? `: ${result.stderr.trim()}` : "")
|
|
1987
|
-
};
|
|
1988
|
-
}
|
|
1989
|
-
let version = (result.stdout ?? "").trim();
|
|
1990
|
-
if (version.startsWith('"') && version.endsWith('"')) {
|
|
1991
|
-
try {
|
|
1992
|
-
version = JSON.parse(version);
|
|
1993
|
-
} catch {
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
if (!isExactSemver(version)) {
|
|
1997
|
-
return {
|
|
1998
|
-
ok: false,
|
|
1999
|
-
message: `Registry latest is not an exact SemVer (${version}).`
|
|
2000
|
-
};
|
|
2001
|
-
}
|
|
2002
|
-
if (isPrereleaseVersion(version)) {
|
|
2003
|
-
return {
|
|
2004
|
-
ok: false,
|
|
2005
|
-
message: `Registry latest (${version}) is a prerelease. Pass --target-version <exact> to install it (AC6.1).`
|
|
2006
|
-
};
|
|
2007
|
-
}
|
|
2008
|
-
return { ok: true, version };
|
|
2009
|
-
}
|
|
2010
|
-
function resolvePublishedContract(version, env = process.env, projectRoot) {
|
|
2011
|
-
const result = spawnSync5(
|
|
2012
|
-
"npm",
|
|
2013
|
-
["view", `${RUNTIME_PACKAGE_NAME}@${version}`, "directive", "--json"],
|
|
2014
|
-
{
|
|
2015
|
-
encoding: "utf8",
|
|
2016
|
-
env,
|
|
2017
|
-
cwd: projectRoot,
|
|
2018
|
-
shell: process.platform === "win32"
|
|
2019
|
-
}
|
|
2020
|
-
);
|
|
2021
|
-
if (result.error) {
|
|
2022
|
-
return {
|
|
2023
|
-
ok: false,
|
|
2024
|
-
message: `Failed to query published contract for ${RUNTIME_PACKAGE_NAME}@${version}: ${result.error.message}`
|
|
2025
|
-
};
|
|
2026
|
-
}
|
|
2027
|
-
if (result.status !== 0) {
|
|
2028
|
-
return {
|
|
2029
|
-
ok: false,
|
|
2030
|
-
message: `npm view ${RUNTIME_PACKAGE_NAME}@${version} directive failed` + (result.stderr ? `: ${result.stderr.trim()}` : "")
|
|
2031
|
-
};
|
|
2032
|
-
}
|
|
2033
|
-
let raw = (result.stdout ?? "").trim();
|
|
2034
|
-
if (raw.length === 0) {
|
|
2035
|
-
return {
|
|
2036
|
-
ok: false,
|
|
2037
|
-
message: `Published ${RUNTIME_PACKAGE_NAME}@${version} has no directive.contract metadata.`
|
|
2038
|
-
};
|
|
2039
|
-
}
|
|
2040
|
-
let parsed;
|
|
2041
|
-
try {
|
|
2042
|
-
parsed = JSON.parse(raw);
|
|
2043
|
-
} catch {
|
|
2044
|
-
return {
|
|
2045
|
-
ok: false,
|
|
2046
|
-
message: `Could not parse directive metadata for ${RUNTIME_PACKAGE_NAME}@${version}.`
|
|
2047
|
-
};
|
|
2048
|
-
}
|
|
2049
|
-
let directive = parsed;
|
|
2050
|
-
if (directive !== null && typeof directive === "object" && !Array.isArray(directive) && !("contract" in directive)) {
|
|
2051
|
-
const values = Object.values(directive);
|
|
2052
|
-
if (values.length === 1) directive = values[0];
|
|
2053
|
-
}
|
|
2054
|
-
if (directive === null || typeof directive !== "object" || Array.isArray(directive)) {
|
|
2055
|
-
return {
|
|
2056
|
-
ok: false,
|
|
2057
|
-
message: `Published ${RUNTIME_PACKAGE_NAME}@${version} directive metadata is not an object.`
|
|
2058
|
-
};
|
|
2059
|
-
}
|
|
2060
|
-
const contract = directive.contract;
|
|
2061
|
-
if (typeof contract !== "number" || !Number.isInteger(contract) || contract < 1) {
|
|
2062
|
-
return {
|
|
2063
|
-
ok: false,
|
|
2064
|
-
message: `Published ${RUNTIME_PACKAGE_NAME}@${version} has invalid directive.contract.`
|
|
2065
|
-
};
|
|
2066
|
-
}
|
|
2067
|
-
return { ok: true, contract };
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
// packages/cli/src/uninstall-map.ts
|
|
2071
|
-
import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync3, realpathSync as realpathSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2072
|
-
import { dirname, isAbsolute as isAbsolute2, join as join4, normalize, relative, resolve as resolve2, sep } from "node:path";
|
|
2073
|
-
import { execFileSync } from "node:child_process";
|
|
2074
|
-
var MANAGED_OPEN = "<!-- deft:managed-section";
|
|
2075
|
-
var MANAGED_CLOSE = "<!-- /deft:managed-section -->";
|
|
2076
|
-
var RUNTIME_PREFIX = "node_modules/@deftai/directive-runtime/";
|
|
2077
|
-
var SKILL_HOST_ROOTS = [
|
|
2078
|
-
".agents/skills",
|
|
2079
|
-
".claude/skills",
|
|
2080
|
-
".cursor/skills",
|
|
2081
|
-
".codex/skills",
|
|
2082
|
-
".github/skills"
|
|
2083
|
-
];
|
|
2084
|
-
var SKILL_DIRS = [
|
|
2085
|
-
"deft",
|
|
2086
|
-
"deft-directive-setup",
|
|
2087
|
-
"deft-directive-build",
|
|
2088
|
-
"deft-directive-swarm",
|
|
2089
|
-
"deft-directive-sync"
|
|
2090
|
-
];
|
|
2091
|
-
var GITHOOK_RELS = [
|
|
2092
|
-
".githooks/pre-commit",
|
|
2093
|
-
".githooks/pre-push",
|
|
2094
|
-
".githooks/_directive-run.sh",
|
|
2095
|
-
".githooks/_deft-run.sh"
|
|
2096
|
-
];
|
|
2097
|
-
var HOST_HOOK_RELS = [
|
|
2098
|
-
".claude/settings.json",
|
|
2099
|
-
".cursor/hooks.json",
|
|
2100
|
-
".codex/hooks.json",
|
|
2101
|
-
".grok/hooks/deft.json"
|
|
2102
|
-
];
|
|
2103
|
-
function readText(path) {
|
|
2104
|
-
try {
|
|
2105
|
-
return readFileSync3(path, "utf8");
|
|
2106
|
-
} catch {
|
|
2107
|
-
return null;
|
|
2108
|
-
}
|
|
2109
|
-
}
|
|
2110
|
-
function readJsonObject3(path) {
|
|
2111
|
-
try {
|
|
2112
|
-
const raw = readFileSync3(path, "utf8");
|
|
2113
|
-
const parsed = JSON.parse(raw);
|
|
2114
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
2115
|
-
return parsed;
|
|
2116
|
-
} catch {
|
|
2117
|
-
return null;
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
function pkgHasDep(projectRoot, name) {
|
|
2121
|
-
const pkg = readJsonObject3(join4(projectRoot, "package.json"));
|
|
2122
|
-
if (pkg === null) return false;
|
|
2123
|
-
for (const key of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
2124
|
-
const block = pkg[key];
|
|
2125
|
-
if (block !== null && typeof block === "object" && !Array.isArray(block)) {
|
|
2126
|
-
if (name in block) return true;
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
return false;
|
|
2130
|
-
}
|
|
2131
|
-
function isSafeOwnershipRelPath(rel) {
|
|
2132
|
-
if (typeof rel !== "string" || !rel.trim()) return false;
|
|
2133
|
-
const trimmed = rel.trim();
|
|
2134
|
-
if (isAbsolute2(trimmed)) return false;
|
|
2135
|
-
if (/^[A-Za-z]:[\\/]/.test(trimmed) || trimmed.startsWith("\\\\")) return false;
|
|
2136
|
-
const rawParts = trimmed.replace(/\\/g, "/").split("/");
|
|
2137
|
-
if (rawParts.some((p) => p === "..")) return false;
|
|
2138
|
-
const norm = normalize(trimmed).replace(/\\/g, "/");
|
|
2139
|
-
if (norm === "" || norm === "." || norm === "..") return false;
|
|
2140
|
-
if (norm.startsWith("../") || norm.split("/").includes("..")) return false;
|
|
2141
|
-
if (norm.startsWith("/")) return false;
|
|
2142
|
-
return true;
|
|
2143
|
-
}
|
|
2144
|
-
function isPathInsideProjectRoot(projectRoot, absCandidate) {
|
|
2145
|
-
const rootAbs = resolve2(projectRoot);
|
|
2146
|
-
const candAbs = resolve2(absCandidate);
|
|
2147
|
-
const rel = relative(rootAbs, candAbs);
|
|
2148
|
-
if (rel === "" || rel === "." || rel.startsWith("..") || isAbsolute2(rel)) return false;
|
|
2149
|
-
try {
|
|
2150
|
-
const rootReal = realpathSync2(rootAbs);
|
|
2151
|
-
let candReal;
|
|
2152
|
-
try {
|
|
2153
|
-
candReal = realpathSync2(candAbs);
|
|
2154
|
-
} catch {
|
|
2155
|
-
return true;
|
|
2156
|
-
}
|
|
2157
|
-
const relReal = relative(rootReal, candReal);
|
|
2158
|
-
if (relReal === "" || relReal === "." || relReal.startsWith("..") || isAbsolute2(relReal)) {
|
|
2159
|
-
return false;
|
|
2160
|
-
}
|
|
2161
|
-
const rootPrefix = rootReal.endsWith(sep) ? rootReal : rootReal + sep;
|
|
2162
|
-
if (!candReal.startsWith(rootPrefix)) return false;
|
|
2163
|
-
return true;
|
|
2164
|
-
} catch {
|
|
2165
|
-
return false;
|
|
2166
|
-
}
|
|
2167
|
-
}
|
|
2168
|
-
function safeWriteText(projectRoot, rel, body) {
|
|
2169
|
-
if (!isSafeOwnershipRelPath(rel)) return false;
|
|
2170
|
-
const abs = resolve2(projectRoot, rel);
|
|
2171
|
-
if (!isPathInsideProjectRoot(projectRoot, abs)) return false;
|
|
2172
|
-
try {
|
|
2173
|
-
writeFileSync2(abs, body);
|
|
2174
|
-
return true;
|
|
2175
|
-
} catch {
|
|
2176
|
-
return false;
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
function parseOwnershipArtifacts(projectRoot) {
|
|
2180
|
-
const path = join4(projectRoot, ".deft", "GENERATION.json");
|
|
2181
|
-
if (!existsSync6(path)) return { inspection: "absent", artifacts: [], rejected: [] };
|
|
2182
|
-
let parsed;
|
|
2183
|
-
try {
|
|
2184
|
-
parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
2185
|
-
} catch {
|
|
2186
|
-
return { inspection: "unreadable", artifacts: [], rejected: [] };
|
|
2187
|
-
}
|
|
2188
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2189
|
-
return { inspection: "unreadable", artifacts: [], rejected: [] };
|
|
2190
|
-
}
|
|
2191
|
-
const ownership = parsed.ownership;
|
|
2192
|
-
if (!Array.isArray(ownership)) {
|
|
2193
|
-
return { inspection: "unreadable", artifacts: [], rejected: [] };
|
|
2194
|
-
}
|
|
2195
|
-
const artifacts = [];
|
|
2196
|
-
const rejected = [];
|
|
2197
|
-
for (const entry of ownership) {
|
|
2198
|
-
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
2199
|
-
const rec = entry;
|
|
2200
|
-
if (typeof rec.path !== "string" || !rec.path.trim()) continue;
|
|
2201
|
-
const kind = rec.kind;
|
|
2202
|
-
if (kind !== "whole-file" && kind !== "marked-section" && kind !== "structured-entry" && kind !== "retained") {
|
|
2203
|
-
continue;
|
|
2204
|
-
}
|
|
2205
|
-
const normalized = rec.path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2206
|
-
if (!isSafeOwnershipRelPath(normalized)) {
|
|
2207
|
-
rejected.push(normalized);
|
|
2208
|
-
continue;
|
|
2209
|
-
}
|
|
2210
|
-
const art = {
|
|
2211
|
-
path: normalized,
|
|
2212
|
-
kind,
|
|
2213
|
-
source: "ownership-record",
|
|
2214
|
-
...typeof rec.entryKey === "string" && rec.entryKey.trim() ? { entryKey: rec.entryKey.trim() } : {},
|
|
2215
|
-
...typeof rec.markerId === "string" && rec.markerId.trim() ? { markerId: rec.markerId.trim() } : kind === "marked-section" ? { markerId: "deft:managed-section" } : {}
|
|
2216
|
-
};
|
|
2217
|
-
artifacts.push(art);
|
|
2218
|
-
}
|
|
2219
|
-
return { inspection: "valid", artifacts, rejected };
|
|
2220
|
-
}
|
|
2221
|
-
function skillPointerRels() {
|
|
2222
|
-
const out = [];
|
|
2223
|
-
for (const host of SKILL_HOST_ROOTS) {
|
|
2224
|
-
for (const dir of SKILL_DIRS) {
|
|
2225
|
-
out.push(`${host}/${dir}/SKILL.md`);
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
return out;
|
|
2229
|
-
}
|
|
2230
|
-
function isOurSkillPointer(body) {
|
|
2231
|
-
return body.includes(RUNTIME_PREFIX) || body.includes("npm i -g @deftai/directive") || body.includes(".deft/core/");
|
|
2232
|
-
}
|
|
2233
|
-
function isOurGitHook(rel, body) {
|
|
2234
|
-
const base = rel.split("/").pop() ?? rel;
|
|
2235
|
-
if (base === "_directive-run.sh" || base === "_deft-run.sh") {
|
|
2236
|
-
return body.includes(RUNTIME_PREFIX) || body.includes("run_directive") || body.includes("run_deft") || body.includes(".deft/core") || body.includes("packages/cli/dist/bin.js");
|
|
2237
|
-
}
|
|
2238
|
-
return body.includes("_directive-run.sh") || body.includes("_deft-run.sh") || body.includes(RUNTIME_PREFIX) || /\brun_deft\s+(verify:|preflight)/.test(body);
|
|
2239
|
-
}
|
|
2240
|
-
function listWiringReady(projectRoot) {
|
|
2241
|
-
const dir = join4(projectRoot, ".deft", "directive");
|
|
2242
|
-
if (!existsSync6(dir)) return [];
|
|
2243
|
-
try {
|
|
2244
|
-
return readdirSync(dir).filter((n) => n.startsWith("wiring-ready.") && n.endsWith(".json")).map((n) => `.deft/directive/${n}`);
|
|
2245
|
-
} catch {
|
|
2246
|
-
return [];
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
function discoverKnownArtifacts(projectRoot) {
|
|
2250
|
-
const out = [];
|
|
2251
|
-
const push = (art) => {
|
|
2252
|
-
if (!out.some((a) => a.path === art.path && a.kind === art.kind && a.entryKey === art.entryKey)) {
|
|
2253
|
-
out.push(art);
|
|
2254
|
-
}
|
|
2255
|
-
};
|
|
2256
|
-
if (existsSync6(join4(projectRoot, ".deft", "GENERATION.json"))) {
|
|
2257
|
-
push({ path: ".deft/GENERATION.json", kind: "whole-file", source: "known-map" });
|
|
2258
|
-
}
|
|
2259
|
-
for (const rel of listWiringReady(projectRoot)) {
|
|
2260
|
-
push({ path: rel, kind: "whole-file", source: "known-map" });
|
|
2261
|
-
}
|
|
2262
|
-
if (existsSync6(join4(projectRoot, ".deft", "directive", "data-schema.json"))) {
|
|
2263
|
-
push({
|
|
2264
|
-
path: ".deft/directive/data-schema.json",
|
|
2265
|
-
kind: "whole-file",
|
|
2266
|
-
source: "known-map"
|
|
2267
|
-
});
|
|
2268
|
-
}
|
|
2269
|
-
if (existsSync6(join4(projectRoot, ".deft", "core"))) {
|
|
2270
|
-
push({ path: ".deft/core", kind: "whole-file", source: "known-map" });
|
|
2271
|
-
}
|
|
2272
|
-
for (const rel of GITHOOK_RELS) {
|
|
2273
|
-
const body = readText(join4(projectRoot, rel));
|
|
2274
|
-
if (body !== null && isOurGitHook(rel, body)) {
|
|
2275
|
-
push({ path: rel, kind: "whole-file", source: "known-map" });
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
for (const rel of skillPointerRels()) {
|
|
2279
|
-
const body = readText(join4(projectRoot, rel));
|
|
2280
|
-
if (body !== null && isOurSkillPointer(body)) {
|
|
2281
|
-
push({ path: rel, kind: "whole-file", source: "known-map" });
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
const agents = readText(join4(projectRoot, "AGENTS.md"));
|
|
2285
|
-
if (agents !== null && agents.includes(MANAGED_OPEN)) {
|
|
2286
|
-
push({
|
|
2287
|
-
path: "AGENTS.md",
|
|
2288
|
-
kind: "marked-section",
|
|
2289
|
-
markerId: "deft:managed-section",
|
|
2290
|
-
source: "known-map"
|
|
2291
|
-
});
|
|
2292
|
-
}
|
|
2293
|
-
for (const rel of HOST_HOOK_RELS) {
|
|
2294
|
-
if (existsSync6(join4(projectRoot, rel))) {
|
|
2295
|
-
push({
|
|
2296
|
-
path: rel,
|
|
2297
|
-
kind: "structured-entry",
|
|
2298
|
-
entryKey: "hooks",
|
|
2299
|
-
source: "known-map"
|
|
2300
|
-
});
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
const taskfile = readText(join4(projectRoot, "Taskfile.yml"));
|
|
2304
|
-
if (taskfile !== null && (taskfile.includes(`${RUNTIME_PREFIX}Taskfile.yml`) || taskfile.includes(".deft/core/Taskfile.yml") || /(^|\n)[ \t]*deft:\s*(\n|$)/.test(taskfile))) {
|
|
2305
|
-
push({
|
|
2306
|
-
path: "Taskfile.yml",
|
|
2307
|
-
kind: "structured-entry",
|
|
2308
|
-
entryKey: "includes.deft",
|
|
2309
|
-
source: "known-map"
|
|
2310
|
-
});
|
|
2311
|
-
}
|
|
2312
|
-
if (existsSync6(join4(projectRoot, "xbrief"))) {
|
|
2313
|
-
push({ path: "xbrief", kind: "retained", source: "known-map" });
|
|
2314
|
-
}
|
|
2315
|
-
if (existsSync6(join4(projectRoot, ".deft", "settings.json"))) {
|
|
2316
|
-
push({ path: ".deft/settings.json", kind: "retained", source: "known-map" });
|
|
2317
|
-
}
|
|
2318
|
-
return out;
|
|
2319
|
-
}
|
|
2320
|
-
function mergeArtifacts(fromRecord, fromKnown) {
|
|
2321
|
-
const out = [...fromRecord];
|
|
2322
|
-
for (const art of fromKnown) {
|
|
2323
|
-
const overlap = out.find(
|
|
2324
|
-
(a) => a.path === art.path && (a.kind === art.kind || art.kind === "retained")
|
|
2325
|
-
);
|
|
2326
|
-
if (overlap) continue;
|
|
2327
|
-
const samePath = out.find((a) => a.path === art.path);
|
|
2328
|
-
if (samePath) continue;
|
|
2329
|
-
out.push(art);
|
|
2330
|
-
}
|
|
2331
|
-
return out;
|
|
2332
|
-
}
|
|
2333
|
-
function discoverUninstallFootprint(projectRoot) {
|
|
2334
|
-
const {
|
|
2335
|
-
inspection,
|
|
2336
|
-
artifacts: fromRecord,
|
|
2337
|
-
rejected
|
|
2338
|
-
} = parseOwnershipArtifacts(projectRoot);
|
|
2339
|
-
const fromKnown = discoverKnownArtifacts(projectRoot);
|
|
2340
|
-
const artifacts = mergeArtifacts(fromRecord, fromKnown);
|
|
2341
|
-
const hasRuntimePin = pkgHasDep(projectRoot, RUNTIME_PACKAGE_NAME);
|
|
2342
|
-
const hasLegacyLauncherPin = pkgHasDep(projectRoot, LAUNCHER_PACKAGE_NAME);
|
|
2343
|
-
const hasLegacyCore = existsSync6(join4(projectRoot, ".deft", "core"));
|
|
2344
|
-
const removable = artifacts.some((a) => a.kind !== "retained");
|
|
2345
|
-
const hasFootprint = removable || hasRuntimePin || hasLegacyLauncherPin || hasLegacyCore || existsSync6(join4(projectRoot, ".deft", "GENERATION.json"));
|
|
2346
|
-
return {
|
|
2347
|
-
artifacts,
|
|
2348
|
-
ownershipInspection: inspection,
|
|
2349
|
-
rejectedOwnershipPaths: rejected,
|
|
2350
|
-
hasRuntimePin,
|
|
2351
|
-
hasLegacyLauncherPin,
|
|
2352
|
-
hasLegacyCore,
|
|
2353
|
-
hasFootprint
|
|
2354
|
-
};
|
|
2355
|
-
}
|
|
2356
|
-
function removeManagedSection(body) {
|
|
2357
|
-
const start = body.indexOf(MANAGED_OPEN);
|
|
2358
|
-
if (start < 0) return { next: body, removed: false };
|
|
2359
|
-
const openEnd = body.indexOf("-->", start);
|
|
2360
|
-
if (openEnd < 0) return { next: body, removed: false };
|
|
2361
|
-
const end = body.indexOf(MANAGED_CLOSE, openEnd);
|
|
2362
|
-
if (end < 0) return { next: body, removed: false };
|
|
2363
|
-
const closeEnd = end + MANAGED_CLOSE.length;
|
|
2364
|
-
let next = body.slice(0, start) + body.slice(closeEnd);
|
|
2365
|
-
next = next.replace(/\n{3,}/g, "\n\n");
|
|
2366
|
-
if (next.trim().length === 0) next = "";
|
|
2367
|
-
return { next, removed: true };
|
|
2368
|
-
}
|
|
2369
|
-
function isManagedHookCommand(command) {
|
|
2370
|
-
return command.includes("directive-runtime/dist/hook-bin.js") || command.includes(".deft/core/") || /(^|[\s"`'])deft-hook([\s"`']|$)/.test(command) || command.includes("deft hook:dispatch");
|
|
2371
|
-
}
|
|
2372
|
-
function object(value) {
|
|
2373
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2374
|
-
}
|
|
2375
|
-
function stripHostHooks(cfg) {
|
|
2376
|
-
const hooks = object(cfg.hooks);
|
|
2377
|
-
if (hooks === null) return cfg;
|
|
2378
|
-
const nextHooks = {};
|
|
2379
|
-
for (const [key, value] of Object.entries(hooks)) {
|
|
2380
|
-
if (!Array.isArray(value)) {
|
|
2381
|
-
nextHooks[key] = value;
|
|
2382
|
-
continue;
|
|
2383
|
-
}
|
|
2384
|
-
const filtered = value.filter((entry) => {
|
|
2385
|
-
const e = object(entry);
|
|
2386
|
-
if (e === null) return true;
|
|
2387
|
-
if (typeof e.command === "string" && isManagedHookCommand(e.command)) return false;
|
|
2388
|
-
if (Array.isArray(e.hooks)) {
|
|
2389
|
-
const nestedManaged = e.hooks.some((h) => {
|
|
2390
|
-
const hook = object(h);
|
|
2391
|
-
return typeof hook?.command === "string" && isManagedHookCommand(hook.command);
|
|
2392
|
-
});
|
|
2393
|
-
if (nestedManaged) return false;
|
|
2394
|
-
}
|
|
2395
|
-
return true;
|
|
2396
|
-
});
|
|
2397
|
-
if (filtered.length > 0) nextHooks[key] = filtered;
|
|
2398
|
-
}
|
|
2399
|
-
if (Object.keys(nextHooks).length === 0) {
|
|
2400
|
-
const { hooks: _h, ...rest } = cfg;
|
|
2401
|
-
if ("version" in rest && Object.keys(rest).length === 1) {
|
|
2402
|
-
const { version: _v, ...rest2 } = rest;
|
|
2403
|
-
return rest2;
|
|
2404
|
-
}
|
|
2405
|
-
return rest;
|
|
2406
|
-
}
|
|
2407
|
-
return { ...cfg, hooks: nextHooks };
|
|
2408
|
-
}
|
|
2409
|
-
function stripTaskfileDeftInclude(body) {
|
|
2410
|
-
const blockRe = /(^|\n)([ \t]*)deft:\s*(?:#.*)?\n(?:\2[ \t]+[^\n]*\n)*/;
|
|
2411
|
-
if (blockRe.test(body)) {
|
|
2412
|
-
const next = body.replace(blockRe, "$1");
|
|
2413
|
-
return { next: next.replace(/\n{3,}/g, "\n\n"), removed: next !== body };
|
|
2414
|
-
}
|
|
2415
|
-
return { next: body, removed: false };
|
|
2416
|
-
}
|
|
2417
|
-
function safeUnlink(projectRoot, rel) {
|
|
2418
|
-
if (!isSafeOwnershipRelPath(rel)) return false;
|
|
2419
|
-
const abs = resolve2(projectRoot, rel);
|
|
2420
|
-
if (!isPathInsideProjectRoot(projectRoot, abs)) return false;
|
|
2421
|
-
if (!existsSync6(abs)) return false;
|
|
2422
|
-
try {
|
|
2423
|
-
if (!isPathInsideProjectRoot(projectRoot, abs)) return false;
|
|
2424
|
-
rmSync(abs, { recursive: true, force: true });
|
|
2425
|
-
return true;
|
|
2426
|
-
} catch {
|
|
2427
|
-
return false;
|
|
2428
|
-
}
|
|
2429
|
-
}
|
|
2430
|
-
function maybeRemoveEmptyParents(projectRoot, relFile) {
|
|
2431
|
-
const removed = [];
|
|
2432
|
-
let dir = dirname(relFile);
|
|
2433
|
-
while (dir && dir !== "." && dir !== "/") {
|
|
2434
|
-
const abs = join4(projectRoot, dir);
|
|
2435
|
-
if (!existsSync6(abs)) break;
|
|
2436
|
-
try {
|
|
2437
|
-
const entries = readdirSync(abs);
|
|
2438
|
-
if (entries.length > 0) break;
|
|
2439
|
-
rmSync(abs, { recursive: false, force: true });
|
|
2440
|
-
removed.push(dir);
|
|
2441
|
-
} catch {
|
|
2442
|
-
break;
|
|
2443
|
-
}
|
|
2444
|
-
const parent = dirname(dir);
|
|
2445
|
-
if (parent === dir) break;
|
|
2446
|
-
dir = parent;
|
|
2447
|
-
}
|
|
2448
|
-
return removed;
|
|
2449
|
-
}
|
|
2450
|
-
function applyLauncherCleanup(projectRoot, discovery) {
|
|
2451
|
-
const removed = [];
|
|
2452
|
-
const modified = [];
|
|
2453
|
-
const preserved = [];
|
|
2454
|
-
const leftovers = [];
|
|
2455
|
-
const uncertainty = [];
|
|
2456
|
-
if (discovery.ownershipInspection === "unreadable") {
|
|
2457
|
-
uncertainty.push("GENERATION.json ownership record was unreadable; used known-artifact map");
|
|
2458
|
-
} else if (discovery.ownershipInspection === "absent") {
|
|
2459
|
-
uncertainty.push("GENERATION.json absent; used known-artifact map");
|
|
2460
|
-
}
|
|
2461
|
-
for (const bad of discovery.rejectedOwnershipPaths) {
|
|
2462
|
-
uncertainty.push(
|
|
2463
|
-
`rejected unsafe ownership path (escape/absolute): ${bad}`
|
|
2464
|
-
);
|
|
2465
|
-
}
|
|
2466
|
-
const ordered = [...discovery.artifacts].sort((a, b) => {
|
|
2467
|
-
const rank = (k) => k === "structured-entry" ? 0 : k === "marked-section" ? 1 : k === "whole-file" ? 2 : 3;
|
|
2468
|
-
return rank(a.kind) - rank(b.kind);
|
|
2469
|
-
});
|
|
2470
|
-
for (const art of ordered) {
|
|
2471
|
-
if (art.kind === "retained") {
|
|
2472
|
-
if (existsSync6(join4(projectRoot, art.path))) preserved.push(art.path);
|
|
2473
|
-
continue;
|
|
2474
|
-
}
|
|
2475
|
-
if (art.kind === "marked-section") {
|
|
2476
|
-
const abs = resolve2(projectRoot, art.path);
|
|
2477
|
-
if (!isPathInsideProjectRoot(projectRoot, abs)) {
|
|
2478
|
-
uncertainty.push(`skipped marked-section outside project: ${art.path}`);
|
|
2479
|
-
continue;
|
|
2480
|
-
}
|
|
2481
|
-
const body = readText(abs);
|
|
2482
|
-
if (body === null) continue;
|
|
2483
|
-
const { next, removed: did } = removeManagedSection(body);
|
|
2484
|
-
if (!did) {
|
|
2485
|
-
if (body.includes(MANAGED_OPEN)) {
|
|
2486
|
-
leftovers.push(`${art.path} (managed section markers incomplete)`);
|
|
2487
|
-
uncertainty.push(`could not safely remove marked section from ${art.path}`);
|
|
2488
|
-
}
|
|
2489
|
-
continue;
|
|
2490
|
-
}
|
|
2491
|
-
if (next.trim().length === 0) {
|
|
2492
|
-
if (safeUnlink(projectRoot, art.path)) removed.push(art.path);
|
|
2493
|
-
else leftovers.push(art.path);
|
|
2494
|
-
} else if (safeWriteText(projectRoot, art.path, next.endsWith("\n") ? next : `${next}
|
|
2495
|
-
`)) {
|
|
2496
|
-
modified.push(`${art.path}#marked-section`);
|
|
2497
|
-
} else {
|
|
2498
|
-
leftovers.push(art.path);
|
|
2499
|
-
uncertainty.push(`refused write outside project: ${art.path}`);
|
|
2500
|
-
}
|
|
2501
|
-
continue;
|
|
2502
|
-
}
|
|
2503
|
-
if (art.kind === "structured-entry") {
|
|
2504
|
-
const abs = resolve2(projectRoot, art.path);
|
|
2505
|
-
if (!isPathInsideProjectRoot(projectRoot, abs) || !existsSync6(abs)) {
|
|
2506
|
-
if (existsSync6(abs)) {
|
|
2507
|
-
uncertainty.push(`skipped structured-entry outside project: ${art.path}`);
|
|
2508
|
-
}
|
|
2509
|
-
continue;
|
|
2510
|
-
}
|
|
2511
|
-
if (art.path === "Taskfile.yml" || art.entryKey === "includes.deft") {
|
|
2512
|
-
const body = readText(abs);
|
|
2513
|
-
if (body === null) continue;
|
|
2514
|
-
const { next: next2, removed: did } = stripTaskfileDeftInclude(body);
|
|
2515
|
-
if (did) {
|
|
2516
|
-
if (safeWriteText(projectRoot, art.path, next2.endsWith("\n") ? next2 : `${next2}
|
|
2517
|
-
`)) {
|
|
2518
|
-
modified.push(`${art.path}#${art.entryKey ?? "includes.deft"}`);
|
|
2519
|
-
} else {
|
|
2520
|
-
uncertainty.push(`refused write outside project: ${art.path}`);
|
|
2521
|
-
}
|
|
2522
|
-
}
|
|
2523
|
-
continue;
|
|
2524
|
-
}
|
|
2525
|
-
const cfg = readJsonObject3(abs);
|
|
2526
|
-
if (cfg === null) {
|
|
2527
|
-
uncertainty.push(`${art.path} is not valid JSON; left untouched`);
|
|
2528
|
-
leftovers.push(art.path);
|
|
2529
|
-
continue;
|
|
2530
|
-
}
|
|
2531
|
-
const stripped = stripHostHooks(cfg);
|
|
2532
|
-
const prior = readText(abs) ?? "";
|
|
2533
|
-
const next = `${JSON.stringify(stripped, null, 2)}
|
|
2534
|
-
`;
|
|
2535
|
-
if (next !== prior) {
|
|
2536
|
-
if (safeWriteText(projectRoot, art.path, next)) {
|
|
2537
|
-
modified.push(`${art.path}#${art.entryKey ?? "hooks"}`);
|
|
2538
|
-
} else {
|
|
2539
|
-
uncertainty.push(`refused write outside project: ${art.path}`);
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
continue;
|
|
2543
|
-
}
|
|
2544
|
-
if (art.path === "Taskfile.yml") {
|
|
2545
|
-
const body = readText(join4(projectRoot, art.path));
|
|
2546
|
-
if (body !== null) {
|
|
2547
|
-
const { next, removed: did } = stripTaskfileDeftInclude(body);
|
|
2548
|
-
if (did) {
|
|
2549
|
-
if (safeWriteText(
|
|
2550
|
-
projectRoot,
|
|
2551
|
-
art.path,
|
|
2552
|
-
next.endsWith("\n") ? next : `${next}
|
|
2553
|
-
`
|
|
2554
|
-
)) {
|
|
2555
|
-
modified.push(`${art.path}#includes.deft`);
|
|
2556
|
-
} else {
|
|
2557
|
-
uncertainty.push(`refused write outside project: ${art.path}`);
|
|
2558
|
-
}
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
|
-
continue;
|
|
2562
|
-
}
|
|
2563
|
-
if (HOST_HOOK_RELS.includes(art.path)) {
|
|
2564
|
-
const abs = resolve2(projectRoot, art.path);
|
|
2565
|
-
if (!isPathInsideProjectRoot(projectRoot, abs)) {
|
|
2566
|
-
uncertainty.push(`skipped host-hook rewrite outside project: ${art.path}`);
|
|
2567
|
-
continue;
|
|
2568
|
-
}
|
|
2569
|
-
const cfg = readJsonObject3(abs);
|
|
2570
|
-
if (cfg !== null) {
|
|
2571
|
-
const stripped = stripHostHooks(cfg);
|
|
2572
|
-
const prior = readText(abs) ?? "";
|
|
2573
|
-
const next = `${JSON.stringify(stripped, null, 2)}
|
|
2574
|
-
`;
|
|
2575
|
-
if (next !== prior) {
|
|
2576
|
-
if (safeWriteText(projectRoot, art.path, next)) {
|
|
2577
|
-
modified.push(`${art.path}#hooks`);
|
|
2578
|
-
} else {
|
|
2579
|
-
uncertainty.push(`refused write outside project: ${art.path}`);
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
} else {
|
|
2583
|
-
uncertainty.push(`${art.path} is not valid JSON; left untouched`);
|
|
2584
|
-
leftovers.push(art.path);
|
|
2585
|
-
}
|
|
2586
|
-
continue;
|
|
2587
|
-
}
|
|
2588
|
-
if (safeUnlink(projectRoot, art.path)) {
|
|
2589
|
-
removed.push(art.path);
|
|
2590
|
-
if (art.path.includes("/")) {
|
|
2591
|
-
removed.push(...maybeRemoveEmptyParents(projectRoot, art.path));
|
|
2592
|
-
}
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
for (const rel of [...GITHOOK_RELS, ...skillPointerRels(), ...listWiringReady(projectRoot)]) {
|
|
2596
|
-
if (removed.includes(rel)) continue;
|
|
2597
|
-
const body = rel.includes("SKILL.md") ? readText(join4(projectRoot, rel)) : rel.startsWith(".githooks/") ? readText(join4(projectRoot, rel)) : existsSync6(join4(projectRoot, rel)) ? "yes" : null;
|
|
2598
|
-
if (body === null) continue;
|
|
2599
|
-
if (rel.includes("SKILL.md") && !isOurSkillPointer(body)) continue;
|
|
2600
|
-
if (rel.startsWith(".githooks/") && !isOurGitHook(rel, body)) continue;
|
|
2601
|
-
if (safeUnlink(projectRoot, rel)) {
|
|
2602
|
-
removed.push(rel);
|
|
2603
|
-
removed.push(...maybeRemoveEmptyParents(projectRoot, rel));
|
|
2604
|
-
}
|
|
2605
|
-
}
|
|
2606
|
-
if (existsSync6(join4(projectRoot, ".deft", "core"))) {
|
|
2607
|
-
if (safeUnlink(projectRoot, ".deft/core")) removed.push(".deft/core");
|
|
2608
|
-
else leftovers.push(".deft/core");
|
|
2609
|
-
}
|
|
2610
|
-
if (existsSync6(join4(projectRoot, ".deft", "directive", "data-schema.json"))) {
|
|
2611
|
-
if (safeUnlink(projectRoot, ".deft/directive/data-schema.json")) {
|
|
2612
|
-
removed.push(".deft/directive/data-schema.json");
|
|
2613
|
-
}
|
|
2614
|
-
}
|
|
2615
|
-
const directiveDir = join4(projectRoot, ".deft", "directive");
|
|
2616
|
-
if (existsSync6(directiveDir)) {
|
|
2617
|
-
try {
|
|
2618
|
-
const left = readdirSync(directiveDir);
|
|
2619
|
-
if (left.length === 0) {
|
|
2620
|
-
if (safeUnlink(projectRoot, ".deft/directive")) removed.push(".deft/directive");
|
|
2621
|
-
} else {
|
|
2622
|
-
leftovers.push(
|
|
2623
|
-
...left.map((n) => `.deft/directive/${n}`)
|
|
2624
|
-
);
|
|
2625
|
-
uncertainty.push(
|
|
2626
|
-
`.deft/directive retains unrecognized entries; not removed wholesale`
|
|
2627
|
-
);
|
|
2628
|
-
}
|
|
2629
|
-
} catch {
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
-
if (existsSync6(join4(projectRoot, ".deft", "GENERATION.json"))) {
|
|
2633
|
-
if (safeUnlink(projectRoot, ".deft/GENERATION.json")) removed.push(".deft/GENERATION.json");
|
|
2634
|
-
}
|
|
2635
|
-
if (existsSync6(join4(projectRoot, "xbrief")) && !preserved.includes("xbrief")) {
|
|
2636
|
-
preserved.push("xbrief");
|
|
2637
|
-
}
|
|
2638
|
-
if (existsSync6(join4(projectRoot, ".deft", "settings.json")) && !preserved.includes(".deft/settings.json")) {
|
|
2639
|
-
preserved.push(".deft/settings.json");
|
|
2640
|
-
}
|
|
2641
|
-
return {
|
|
2642
|
-
removed: [...new Set(removed)],
|
|
2643
|
-
modified: [...new Set(modified)],
|
|
2644
|
-
preserved: [...new Set(preserved)],
|
|
2645
|
-
leftovers: [...new Set(leftovers)],
|
|
2646
|
-
uncertainty: [...new Set(uncertainty)],
|
|
2647
|
-
hooksPath: "unchanged"
|
|
2648
|
-
};
|
|
2649
|
-
}
|
|
2650
|
-
function listLinkedWorktreePaths(projectRoot) {
|
|
2651
|
-
try {
|
|
2652
|
-
const out = execFileSync("git", ["-C", projectRoot, "worktree", "list", "--porcelain"], {
|
|
2653
|
-
encoding: "utf8"
|
|
2654
|
-
});
|
|
2655
|
-
const paths = [];
|
|
2656
|
-
for (const line of out.split(/\r?\n/)) {
|
|
2657
|
-
if (line.startsWith("worktree ")) paths.push(line.slice("worktree ".length).trim());
|
|
2658
|
-
}
|
|
2659
|
-
return paths;
|
|
2660
|
-
} catch {
|
|
2661
|
-
return [];
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
function siblingNeedsHooksPath(projectRoot) {
|
|
2665
|
-
const rootResolved = join4(projectRoot);
|
|
2666
|
-
for (const wt of listLinkedWorktreePaths(projectRoot)) {
|
|
2667
|
-
if (join4(wt) === rootResolved) continue;
|
|
2668
|
-
if (existsSync6(join4(wt, ".githooks", "_directive-run.sh"))) return true;
|
|
2669
|
-
if (existsSync6(join4(wt, ".deft", "GENERATION.json"))) return true;
|
|
2670
|
-
}
|
|
2671
|
-
return false;
|
|
2672
|
-
}
|
|
2673
|
-
function cleanupHooksPath(projectRoot) {
|
|
2674
|
-
let current = null;
|
|
2675
|
-
try {
|
|
2676
|
-
current = execFileSync("git", ["-C", projectRoot, "config", "--get", "core.hooksPath"], {
|
|
2677
|
-
encoding: "utf8"
|
|
2678
|
-
}).trim();
|
|
2679
|
-
} catch {
|
|
2680
|
-
return "absent";
|
|
2681
|
-
}
|
|
2682
|
-
if (current === null || current === "") return "absent";
|
|
2683
|
-
if (current !== ".githooks" && current !== ".githooks/") {
|
|
2684
|
-
return "preserved-foreign";
|
|
2685
|
-
}
|
|
2686
|
-
if (siblingNeedsHooksPath(projectRoot)) {
|
|
2687
|
-
return "preserved-sibling";
|
|
2688
|
-
}
|
|
2689
|
-
try {
|
|
2690
|
-
execFileSync("git", ["-C", projectRoot, "config", "--unset", "core.hooksPath"], {
|
|
2691
|
-
encoding: "utf8"
|
|
2692
|
-
});
|
|
2693
|
-
return "unset";
|
|
2694
|
-
} catch {
|
|
2695
|
-
return "unchanged";
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
function verifyUninstallComplete(projectRoot) {
|
|
2699
|
-
const leftovers = [];
|
|
2700
|
-
if (existsSync6(join4(projectRoot, ".deft", "GENERATION.json"))) {
|
|
2701
|
-
leftovers.push(".deft/GENERATION.json");
|
|
2702
|
-
}
|
|
2703
|
-
if (existsSync6(join4(projectRoot, ".deft", "core"))) {
|
|
2704
|
-
leftovers.push(".deft/core");
|
|
2705
|
-
}
|
|
2706
|
-
for (const rel of GITHOOK_RELS) {
|
|
2707
|
-
const body = readText(join4(projectRoot, rel));
|
|
2708
|
-
if (body !== null && isOurGitHook(rel, body)) leftovers.push(rel);
|
|
2709
|
-
}
|
|
2710
|
-
const agents = readText(join4(projectRoot, "AGENTS.md"));
|
|
2711
|
-
if (agents !== null && agents.includes(MANAGED_OPEN)) {
|
|
2712
|
-
leftovers.push("AGENTS.md#marked-section");
|
|
2713
|
-
}
|
|
2714
|
-
if (pkgHasDep(projectRoot, RUNTIME_PACKAGE_NAME)) {
|
|
2715
|
-
leftovers.push(`package.json#${RUNTIME_PACKAGE_NAME}`);
|
|
2716
|
-
}
|
|
2717
|
-
if (pkgHasDep(projectRoot, LAUNCHER_PACKAGE_NAME)) {
|
|
2718
|
-
leftovers.push(`package.json#${LAUNCHER_PACKAGE_NAME}`);
|
|
2719
|
-
}
|
|
2720
|
-
for (const rel of listWiringReady(projectRoot)) leftovers.push(rel);
|
|
2721
|
-
return { complete: leftovers.length === 0, leftovers };
|
|
2722
|
-
}
|
|
2723
|
-
|
|
2724
|
-
// packages/cli/src/legacy-shapes/detect.ts
|
|
2725
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2726
|
-
import { readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
|
|
2727
|
-
import { join as join5 } from "node:path";
|
|
2728
|
-
|
|
2729
|
-
// packages/cli/src/legacy-shapes/registry.ts
|
|
2730
|
-
var DECISION_BY_SHAPE_ID = {
|
|
2731
|
-
"ovpr-runtime-pin": "l0-current",
|
|
2732
|
-
"npm-hybrid-pin-copied-core": "l0-latest-legacy-convert",
|
|
2733
|
-
"vendored-core-go": "convert-via-removals",
|
|
2734
|
-
"go-tarball-deft-root": "convert-via-removals",
|
|
2735
|
-
"submodule-deft": "convert-via-removals",
|
|
2736
|
-
"dual-layout-deft-and-core": "convert-via-removals",
|
|
2737
|
-
"orphan-deft-version": "convert-via-removals"
|
|
2738
|
-
};
|
|
2739
|
-
function decisionForShapeId(shapeId) {
|
|
2740
|
-
return DECISION_BY_SHAPE_ID[shapeId];
|
|
2741
|
-
}
|
|
2742
|
-
|
|
2743
|
-
// packages/cli/src/legacy-shapes/detect.ts
|
|
2744
|
-
function isFile(path) {
|
|
2745
|
-
try {
|
|
2746
|
-
return statSync3(path).isFile();
|
|
2747
|
-
} catch {
|
|
2748
|
-
return false;
|
|
2749
|
-
}
|
|
2750
|
-
}
|
|
2751
|
-
function isDir(path) {
|
|
2752
|
-
try {
|
|
2753
|
-
return statSync3(path).isDirectory();
|
|
2754
|
-
} catch {
|
|
2755
|
-
return false;
|
|
2756
|
-
}
|
|
2757
|
-
}
|
|
2758
|
-
function readText2(path) {
|
|
2759
|
-
try {
|
|
2760
|
-
return readFileSync4(path, "utf8");
|
|
2761
|
-
} catch {
|
|
2762
|
-
return null;
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
var LEGACY_DEFT_DIR = "deft";
|
|
2766
|
-
function isFrameworkDeftDir(projectRoot) {
|
|
2767
|
-
const deftDir = join5(projectRoot, LEGACY_DEFT_DIR);
|
|
2768
|
-
if (!isDir(deftDir)) return false;
|
|
2769
|
-
return isFile(join5(deftDir, "VERSION")) || isFile(join5(deftDir, "main.md")) || isFile(join5(deftDir, "Taskfile.yml")) || isDir(join5(deftDir, "skills"));
|
|
2770
|
-
}
|
|
2771
|
-
function gitmodulesReferencesFramework(text) {
|
|
2772
|
-
const normalized = text.replace(/\r\n/g, "\n").toLowerCase();
|
|
2773
|
-
if (normalized.includes("deftai/directive")) return true;
|
|
2774
|
-
return /(^|\n)\s*path\s*=\s*\.?deft(\/|\s|$)/.test(normalized);
|
|
2775
|
-
}
|
|
2776
|
-
function isUnsafeSubmoduleGitlink(projectRoot) {
|
|
2777
|
-
try {
|
|
2778
|
-
const out = execFileSync2("git", ["-C", projectRoot, "ls-files", "-s", "--", "deft"], {
|
|
2779
|
-
encoding: "utf8",
|
|
2780
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2781
|
-
});
|
|
2782
|
-
for (const line of out.split(/\r?\n/)) {
|
|
2783
|
-
if (/^160000\s/.test(line.trim())) return true;
|
|
2784
|
-
}
|
|
2785
|
-
} catch {
|
|
2786
|
-
}
|
|
2787
|
-
return false;
|
|
2788
|
-
}
|
|
2789
|
-
function resolution(shapeId, evidence, decisionOverride) {
|
|
2790
|
-
return {
|
|
2791
|
-
shapeId,
|
|
2792
|
-
decision: decisionOverride ?? decisionForShapeId(shapeId),
|
|
2793
|
-
evidence
|
|
2794
|
-
};
|
|
2795
|
-
}
|
|
2796
|
-
function resolveInstallShape(projectRoot) {
|
|
2797
|
-
const pin = readPin(projectRoot);
|
|
2798
|
-
if (pin.kind === "exact" && !pin.oldStyle && pin.version !== null) {
|
|
2799
|
-
return resolution("ovpr-runtime-pin", [`package.json#@deftai/directive-runtime@${pin.version}`]);
|
|
2800
|
-
}
|
|
2801
|
-
if (pin.oldStyle) {
|
|
2802
|
-
return resolution("npm-hybrid-pin-copied-core", [
|
|
2803
|
-
"package.json#@deftai/directive (exact)",
|
|
2804
|
-
".deft/core (copied-core evidence)"
|
|
2805
|
-
]);
|
|
2806
|
-
}
|
|
2807
|
-
const hasCore = hasCopiedCoreEvidence(projectRoot);
|
|
2808
|
-
const frameworkDeft = isFrameworkDeftDir(projectRoot);
|
|
2809
|
-
if (hasCore && frameworkDeft) {
|
|
2810
|
-
return resolution("dual-layout-deft-and-core", [".deft/core", "deft/"]);
|
|
2811
|
-
}
|
|
2812
|
-
if (hasCore) {
|
|
2813
|
-
return resolution("vendored-core-go", [".deft/core (copied-core evidence, no exact pin)"]);
|
|
2814
|
-
}
|
|
2815
|
-
if (isFile(join5(projectRoot, ".deft", "VERSION")) && !isDir(join5(projectRoot, ".deft", "core"))) {
|
|
2816
|
-
return resolution("orphan-deft-version", [".deft/VERSION"]);
|
|
2817
|
-
}
|
|
2818
|
-
const deftDir = join5(projectRoot, LEGACY_DEFT_DIR);
|
|
2819
|
-
if (frameworkDeft) {
|
|
2820
|
-
const submoduleGit = isFile(join5(deftDir, ".git")) || isDir(join5(deftDir, ".git"));
|
|
2821
|
-
if (submoduleGit) {
|
|
2822
|
-
if (isUnsafeSubmoduleGitlink(projectRoot)) {
|
|
2823
|
-
return resolution(
|
|
2824
|
-
"submodule-deft",
|
|
2825
|
-
["deft/", "deft/.git", "gitlink mode 160000"],
|
|
2826
|
-
"unsupported-honest"
|
|
2827
|
-
);
|
|
2828
|
-
}
|
|
2829
|
-
return resolution("submodule-deft", ["deft/", "deft/.git"]);
|
|
2830
|
-
}
|
|
2831
|
-
return resolution("go-tarball-deft-root", ["deft/"]);
|
|
2832
|
-
}
|
|
2833
|
-
const gitmodulesPath = join5(projectRoot, ".gitmodules");
|
|
2834
|
-
if (isFile(gitmodulesPath)) {
|
|
2835
|
-
const text = readText2(gitmodulesPath);
|
|
2836
|
-
if (text !== null && gitmodulesReferencesFramework(text)) {
|
|
2837
|
-
if (isUnsafeSubmoduleGitlink(projectRoot)) {
|
|
2838
|
-
return resolution(
|
|
2839
|
-
"submodule-deft",
|
|
2840
|
-
[".gitmodules", "gitlink mode 160000"],
|
|
2841
|
-
"unsupported-honest"
|
|
2842
|
-
);
|
|
2843
|
-
}
|
|
2844
|
-
return resolution("submodule-deft", [".gitmodules"]);
|
|
2845
|
-
}
|
|
2846
|
-
}
|
|
2847
|
-
return null;
|
|
2848
|
-
}
|
|
2849
|
-
|
|
2850
|
-
// packages/cli/src/legacy-shapes/messages.ts
|
|
2851
|
-
function formatShapeRecoveryMessage(shape, command) {
|
|
2852
|
-
const evidence = shape.evidence.length > 0 ? ` Evidence: ${shape.evidence.join(", ")}.` : "";
|
|
2853
|
-
if (shape.decision === "uninstall-reinstall") {
|
|
2854
|
-
return `directive ${command}: detected historical Directive layout '${shape.shapeId}'.${evidence} In-place convert is not supported for this shape. Run \`directive uninstall\` then \`directive install\` to move to the current layout (xbrief/ and user-owned shared content follow uninstall keep-lists).`;
|
|
2855
|
-
}
|
|
2856
|
-
if (shape.decision === "unsupported-honest") {
|
|
2857
|
-
return `directive ${command}: detected unsupported Directive layout '${shape.shapeId}'.${evidence} Automatic recovery is not supported. Inspect the tree, remove orphan markers if safe, then run \`directive install\`, or ask for support.`;
|
|
2858
|
-
}
|
|
2859
|
-
if (shape.decision === "convert-via-removals") {
|
|
2860
|
-
return `directive ${command}: layout '${shape.shapeId}' converts via removal strategy (handled by the upgrade/install convert path).`;
|
|
2861
|
-
}
|
|
2862
|
-
return `directive ${command}: layout '${shape.shapeId}' uses decision '${shape.decision}' (handled by the current L0 path).`;
|
|
2863
|
-
}
|
|
2864
|
-
function isL2RefusalDecision(decision) {
|
|
2865
|
-
return decision === "uninstall-reinstall" || decision === "unsupported-honest";
|
|
2866
|
-
}
|
|
2867
|
-
|
|
2868
|
-
// packages/cli/src/bookends.ts
|
|
2869
|
-
var CANCELLED_NO_CHANGES = "Cancelled. No project files were changed.";
|
|
2870
|
-
function defaultIo() {
|
|
2871
|
-
return {
|
|
2872
|
-
writeOut: (t) => {
|
|
2873
|
-
process.stdout.write(t);
|
|
2874
|
-
},
|
|
2875
|
-
writeErr: (t) => {
|
|
2876
|
-
process.stderr.write(t);
|
|
2877
|
-
}
|
|
2878
|
-
};
|
|
2879
|
-
}
|
|
2880
|
-
function flagValueMissing(next) {
|
|
2881
|
-
return next === void 0 || next.startsWith("-");
|
|
2882
|
-
}
|
|
2883
|
-
function parseFlags(argv) {
|
|
2884
|
-
const { verbosity, rest: afterVerbosity } = parseVerbosityFlags(argv);
|
|
2885
|
-
const rest = [];
|
|
2886
|
-
let dryRun = false;
|
|
2887
|
-
let yes = false;
|
|
2888
|
-
let repair = false;
|
|
2889
|
-
let targetVersion = null;
|
|
2890
|
-
let packageManager = null;
|
|
2891
|
-
let usageError = null;
|
|
2892
|
-
for (let i = 0; i < afterVerbosity.length; i++) {
|
|
2893
|
-
const a = afterVerbosity[i];
|
|
2894
|
-
if (a === "--dry-run") {
|
|
2895
|
-
dryRun = true;
|
|
2896
|
-
continue;
|
|
2897
|
-
}
|
|
2898
|
-
if (a === "--yes" || a === "-y") {
|
|
2899
|
-
yes = true;
|
|
2900
|
-
continue;
|
|
2901
|
-
}
|
|
2902
|
-
if (a === "--repair") {
|
|
2903
|
-
repair = true;
|
|
2904
|
-
continue;
|
|
2905
|
-
}
|
|
2906
|
-
if (a === "--target-version") {
|
|
2907
|
-
const next = afterVerbosity[i + 1];
|
|
2908
|
-
if (flagValueMissing(next)) {
|
|
2909
|
-
usageError = "--target-version requires a value.";
|
|
2910
|
-
continue;
|
|
2911
|
-
}
|
|
2912
|
-
targetVersion = afterVerbosity[++i];
|
|
2913
|
-
continue;
|
|
2914
|
-
}
|
|
2915
|
-
if (a.startsWith("--target-version=")) {
|
|
2916
|
-
targetVersion = a.slice("--target-version=".length);
|
|
2917
|
-
if (targetVersion.length === 0) {
|
|
2918
|
-
usageError = "--target-version requires a value.";
|
|
2919
|
-
}
|
|
2920
|
-
continue;
|
|
2921
|
-
}
|
|
2922
|
-
if (a === "--package-manager") {
|
|
2923
|
-
const next = afterVerbosity[i + 1];
|
|
2924
|
-
if (flagValueMissing(next)) {
|
|
2925
|
-
usageError = "--package-manager requires a value.";
|
|
2926
|
-
continue;
|
|
2927
|
-
}
|
|
2928
|
-
packageManager = afterVerbosity[++i];
|
|
2929
|
-
continue;
|
|
2930
|
-
}
|
|
2931
|
-
if (a.startsWith("--package-manager=")) {
|
|
2932
|
-
packageManager = a.slice("--package-manager=".length);
|
|
2933
|
-
if (packageManager.length === 0) {
|
|
2934
|
-
usageError = "--package-manager requires a value.";
|
|
2935
|
-
}
|
|
2936
|
-
continue;
|
|
2937
|
-
}
|
|
2938
|
-
rest.push(a);
|
|
2939
|
-
}
|
|
2940
|
-
return {
|
|
2941
|
-
flags: { dryRun, yes, repair, targetVersion, packageManager, verbosity },
|
|
2942
|
-
rest,
|
|
2943
|
-
usageError
|
|
2944
|
-
};
|
|
2945
|
-
}
|
|
2946
|
-
function preflightPmAvailable(projectRoot, pm, deps, io) {
|
|
2947
|
-
const declaredVersion = readDeclaredPackageManagerVersion(projectRoot);
|
|
2948
|
-
const check = deps.checkPmAvailable?.(pm) ?? ensurePackageManagerAvailable(pm, deps.env ?? process.env, declaredVersion, projectRoot);
|
|
2949
|
-
if (check.ok) return null;
|
|
2950
|
-
io.writeErr(`${check.message}
|
|
2951
|
-
`);
|
|
2952
|
-
return EXIT_CODES.FAILURE;
|
|
2953
|
-
}
|
|
2954
|
-
function assertPlanStillValid(projectRoot, pm, fingerprint, io) {
|
|
2955
|
-
if (!planFingerprintChanged(projectRoot, pm, fingerprint)) return null;
|
|
2956
|
-
io.writeErr(
|
|
2957
|
-
"Plan invalidated: relevant project files changed after the plan was shown. Re-run the command.\n"
|
|
2958
|
-
);
|
|
2959
|
-
return EXIT_CODES.FAILURE;
|
|
2960
|
-
}
|
|
2961
|
-
function reportPmFailure(io, add) {
|
|
2962
|
-
io.writeErr(`${add.message}
|
|
2963
|
-
`);
|
|
2964
|
-
if (add.stderr) io.writeErr(`${add.stderr}
|
|
2965
|
-
`);
|
|
2966
|
-
if (add.interrupted) {
|
|
2967
|
-
if (!/partial changes may remain/i.test(add.message)) {
|
|
2968
|
-
io.writeErr(`${PARTIAL_CHANGES_MAY_REMAIN}
|
|
2969
|
-
`);
|
|
2970
|
-
}
|
|
2971
|
-
return EXIT_CODES.INTERRUPTED;
|
|
2972
|
-
}
|
|
2973
|
-
return EXIT_CODES.FAILURE;
|
|
2974
|
-
}
|
|
2975
|
-
function parseYesNo(answer) {
|
|
2976
|
-
const trimmed = answer.trim().toLowerCase();
|
|
2977
|
-
return trimmed === "y" || trimmed === "yes";
|
|
2978
|
-
}
|
|
2979
|
-
function resolveLineReader(deps) {
|
|
2980
|
-
return deps.readInteractiveLine ?? readInteractiveLine;
|
|
2981
|
-
}
|
|
2982
|
-
function shouldDefaultToRepair(projectRoot, state) {
|
|
2983
|
-
if (state.installed === null) return true;
|
|
2984
|
-
return !existsSync7(join6(projectRoot, ".deft", "GENERATION.json"));
|
|
2985
|
-
}
|
|
2986
|
-
function printConfiguredMenu(io, opts) {
|
|
2987
|
-
const repairMarker = opts.defaultChoice === "repair" ? ">" : " ";
|
|
2988
|
-
const cancelMarker = opts.defaultChoice === "cancel" ? ">" : " ";
|
|
2989
|
-
io.writeOut("Directive is already configured in this project.\n\n");
|
|
2990
|
-
io.writeOut(`Project: ${opts.projectRoot}
|
|
2991
|
-
`);
|
|
2992
|
-
io.writeOut(`Pinned version: ${opts.pinVersion}
|
|
2993
|
-
|
|
2994
|
-
`);
|
|
2995
|
-
io.writeOut("What would you like to do?\n\n");
|
|
2996
|
-
io.writeOut(`${repairMarker} Repair ${opts.pinVersion}
|
|
2997
|
-
`);
|
|
2998
|
-
io.writeOut(" Restore the current version and rerun project setup.\n\n");
|
|
2999
|
-
io.writeOut(`${cancelMarker} Cancel
|
|
3000
|
-
`);
|
|
3001
|
-
io.writeOut(" Leave the project unchanged.\n\n");
|
|
3002
|
-
}
|
|
3003
|
-
function parseMenuChoice(answer, defaultChoice) {
|
|
3004
|
-
const trimmed = answer.trim().toLowerCase();
|
|
3005
|
-
if (trimmed.length === 0) return defaultChoice;
|
|
3006
|
-
if (trimmed === "1" || trimmed === "r" || trimmed === "repair") return "repair";
|
|
3007
|
-
if (trimmed === "2" || trimmed === "c" || trimmed === "cancel") return "cancel";
|
|
3008
|
-
return "cancel";
|
|
3009
|
-
}
|
|
3010
|
-
function defaultChooseMenu(io, opts, readLine) {
|
|
3011
|
-
printConfiguredMenu(io, opts);
|
|
3012
|
-
const defaultLabel = opts.defaultChoice === "repair" ? "Repair" : "Cancel";
|
|
3013
|
-
io.writeOut(`Choice [Repair/Cancel] (default: ${defaultLabel}): `);
|
|
3014
|
-
const result = readLine();
|
|
3015
|
-
if (result.eof) return "eof";
|
|
3016
|
-
return parseMenuChoice(result.line, opts.defaultChoice);
|
|
3017
|
-
}
|
|
3018
|
-
function requireApproval(flags, deps, io, prompt) {
|
|
3019
|
-
if (flags.dryRun) return null;
|
|
3020
|
-
if (flags.yes) return null;
|
|
3021
|
-
const tty = deps.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
3022
|
-
if (!tty) {
|
|
3023
|
-
io.writeErr(`Non-interactive session requires --yes to approve: ${prompt}
|
|
3024
|
-
`);
|
|
3025
|
-
return EXIT_CODES.USAGE;
|
|
3026
|
-
}
|
|
3027
|
-
if (deps.confirm) {
|
|
3028
|
-
io.writeOut(`${prompt} [y/N]
|
|
3029
|
-
`);
|
|
3030
|
-
if (deps.confirm(prompt)) return null;
|
|
3031
|
-
io.writeOut(`${CANCELLED_NO_CHANGES}
|
|
3032
|
-
`);
|
|
3033
|
-
return EXIT_CODES.SUCCESS;
|
|
3034
|
-
}
|
|
3035
|
-
io.writeOut(`${prompt} [y/N] `);
|
|
3036
|
-
const result = resolveLineReader(deps)();
|
|
3037
|
-
if (result.eof) {
|
|
3038
|
-
io.writeErr(`Non-interactive session requires --yes to approve: ${prompt}
|
|
3039
|
-
`);
|
|
3040
|
-
return EXIT_CODES.USAGE;
|
|
3041
|
-
}
|
|
3042
|
-
if (parseYesNo(result.line)) return null;
|
|
3043
|
-
io.writeOut(`${CANCELLED_NO_CHANGES}
|
|
3044
|
-
`);
|
|
3045
|
-
return EXIT_CODES.SUCCESS;
|
|
3046
|
-
}
|
|
3047
|
-
function runInstall(projectRoot, state, argv, launcherVersion, io = defaultIo(), deps = {}) {
|
|
3048
|
-
const { flags, rest, usageError } = parseFlags(argv);
|
|
3049
|
-
const env = deps.env ?? process.env;
|
|
3050
|
-
if (usageError !== null) {
|
|
3051
|
-
io.writeErr(`${usageError}
|
|
3052
|
-
`);
|
|
3053
|
-
return EXIT_CODES.USAGE;
|
|
3054
|
-
}
|
|
3055
|
-
if (rest.length > 0) {
|
|
3056
|
-
io.writeErr(`Unknown argument(s): ${rest.join(" ")}
|
|
3057
|
-
`);
|
|
3058
|
-
return EXIT_CODES.USAGE;
|
|
3059
|
-
}
|
|
3060
|
-
if (flags.repair && flags.targetVersion !== null) {
|
|
3061
|
-
io.writeErr(
|
|
3062
|
-
"Cannot combine --repair with --target-version. Repair keeps the existing pin; use directive upgrade to change versions.\n"
|
|
3063
|
-
);
|
|
3064
|
-
return EXIT_CODES.USAGE;
|
|
3065
|
-
}
|
|
3066
|
-
const installShape = resolveInstallShape(projectRoot);
|
|
3067
|
-
if (installShape !== null && isL2RefusalDecision(installShape.decision)) {
|
|
3068
|
-
io.writeErr(`${formatShapeRecoveryMessage(installShape, "install")}
|
|
3069
|
-
`);
|
|
3070
|
-
return EXIT_CODES.FAILURE;
|
|
3071
|
-
}
|
|
3072
|
-
if (state.oldStyle || installShape?.decision === "l0-latest-legacy-convert") {
|
|
3073
|
-
io.writeErr("This project uses an older Directive layout. Run `directive upgrade`.\n");
|
|
3074
|
-
return EXIT_CODES.FAILURE;
|
|
3075
|
-
}
|
|
3076
|
-
const convertShapeId = installShape?.decision === "convert-via-removals" ? installShape.shapeId : null;
|
|
3077
|
-
const configuredExact = state.pin.kind === "exact" && state.pin.version !== null;
|
|
3078
|
-
if (configuredExact && !flags.repair) {
|
|
3079
|
-
const pinVersion = state.pin.version;
|
|
3080
|
-
if (flags.dryRun) {
|
|
3081
|
-
const defaultChoice = shouldDefaultToRepair(projectRoot, state) ? "repair" : "cancel";
|
|
3082
|
-
io.writeOut(
|
|
3083
|
-
`Directive is already configured. Pinned version: ${pinVersion}.
|
|
3084
|
-
[dry-run] would present Repair ${pinVersion} / Cancel (default: ${defaultChoice === "repair" ? "Repair" : "Cancel"}; use --repair to select repair).
|
|
3085
|
-
`
|
|
3086
|
-
);
|
|
3087
|
-
return EXIT_CODES.SUCCESS;
|
|
3088
|
-
}
|
|
3089
|
-
if (flags.yes) {
|
|
3090
|
-
presentAmbiguousYes(io, pinVersion);
|
|
3091
|
-
return EXIT_CODES.USAGE;
|
|
3092
|
-
}
|
|
3093
|
-
const tty = deps.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
3094
|
-
if (!tty) {
|
|
3095
|
-
io.writeErr(
|
|
3096
|
-
`Directive is already configured (pin ${pinVersion}). Pass --repair --yes to repair, or run interactively.
|
|
3097
|
-
directive install --repair --yes
|
|
3098
|
-
directive upgrade --yes
|
|
3099
|
-
`
|
|
3100
|
-
);
|
|
3101
|
-
return EXIT_CODES.USAGE;
|
|
3102
|
-
}
|
|
3103
|
-
const menuOpts = {
|
|
3104
|
-
projectRoot,
|
|
3105
|
-
pinVersion,
|
|
3106
|
-
defaultChoice: shouldDefaultToRepair(projectRoot, state) ? "repair" : "cancel"
|
|
3107
|
-
};
|
|
3108
|
-
let choice;
|
|
3109
|
-
if (deps.chooseMenu) {
|
|
3110
|
-
printConfiguredMenu(io, menuOpts);
|
|
3111
|
-
choice = deps.chooseMenu(menuOpts);
|
|
3112
|
-
} else {
|
|
3113
|
-
choice = defaultChooseMenu(io, menuOpts, resolveLineReader(deps));
|
|
3114
|
-
}
|
|
3115
|
-
if (choice === "eof") {
|
|
3116
|
-
io.writeErr(
|
|
3117
|
-
`Non-interactive session requires --yes to approve. Pass --repair --yes to repair, or run interactively.
|
|
3118
|
-
directive install --repair --yes
|
|
3119
|
-
directive upgrade --yes
|
|
3120
|
-
`
|
|
3121
|
-
);
|
|
3122
|
-
return EXIT_CODES.USAGE;
|
|
3123
|
-
}
|
|
3124
|
-
if (choice === "cancel") {
|
|
3125
|
-
io.writeOut(`${CANCELLED_NO_CHANGES}
|
|
3126
|
-
`);
|
|
3127
|
-
return EXIT_CODES.SUCCESS;
|
|
3128
|
-
}
|
|
3129
|
-
return runRepair(projectRoot, state, flags, launcherVersion, io, deps, env);
|
|
3130
|
-
}
|
|
3131
|
-
if (flags.repair) {
|
|
3132
|
-
return runRepair(projectRoot, state, flags, launcherVersion, io, deps, env);
|
|
3133
|
-
}
|
|
3134
|
-
return runFirstInstall(
|
|
3135
|
-
projectRoot,
|
|
3136
|
-
state,
|
|
3137
|
-
flags,
|
|
3138
|
-
launcherVersion,
|
|
3139
|
-
io,
|
|
3140
|
-
deps,
|
|
3141
|
-
env,
|
|
3142
|
-
convertShapeId
|
|
3143
|
-
);
|
|
3144
|
-
}
|
|
3145
|
-
function runFirstInstall(projectRoot, state, flags, launcherVersion, io, deps, env, convertShapeId = null) {
|
|
3146
|
-
if (state.pin.kind === "range" || state.pin.kind === "invalid") {
|
|
3147
|
-
io.writeErr(
|
|
3148
|
-
`Runtime pin is ${state.pin.kind} (${state.pin.raw}). Rectify package.json to an exact @deftai/directive-runtime version, then retry.
|
|
3149
|
-
`
|
|
3150
|
-
);
|
|
3151
|
-
return EXIT_CODES.FAILURE;
|
|
3152
|
-
}
|
|
3153
|
-
const pmDetect = detectPackageManager(projectRoot, flags.packageManager);
|
|
3154
|
-
if (!pmDetect.ok) {
|
|
3155
|
-
io.writeErr(`${pmDetect.message}
|
|
3156
|
-
`);
|
|
3157
|
-
return EXIT_CODES.USAGE;
|
|
3158
|
-
}
|
|
3159
|
-
const pmMissing = preflightPmAvailable(projectRoot, pmDetect.pm, deps, io);
|
|
3160
|
-
if (pmMissing !== null) return pmMissing;
|
|
3161
|
-
let targetVersion;
|
|
3162
|
-
if (flags.targetVersion !== null) {
|
|
3163
|
-
const validated = validateTargetVersionSpec(flags.targetVersion);
|
|
3164
|
-
if (!validated.ok) {
|
|
3165
|
-
io.writeErr(`${validated.message}
|
|
3166
|
-
`);
|
|
3167
|
-
return EXIT_CODES.USAGE;
|
|
3168
|
-
}
|
|
3169
|
-
targetVersion = validated.version;
|
|
3170
|
-
} else {
|
|
3171
|
-
const resolver = deps.resolveLatest ?? (() => resolveLatestStableVersion(env, projectRoot));
|
|
3172
|
-
const resolved = resolver();
|
|
3173
|
-
if (resolved instanceof Promise) {
|
|
3174
|
-
io.writeErr("Internal error: async resolveLatest is not supported in sync install path.\n");
|
|
3175
|
-
return EXIT_CODES.FAILURE;
|
|
3176
|
-
}
|
|
3177
|
-
if (!resolved.ok) {
|
|
3178
|
-
io.writeErr(`${resolved.message}
|
|
3179
|
-
`);
|
|
3180
|
-
return EXIT_CODES.FAILURE;
|
|
3181
|
-
}
|
|
3182
|
-
targetVersion = resolved.version;
|
|
3183
|
-
}
|
|
3184
|
-
const willCreatePkg = !existsSync7(join6(projectRoot, "package.json"));
|
|
3185
|
-
const pmCmd = formatPmExactAddCommand(pmDetect.pm, targetVersion, projectRoot);
|
|
3186
|
-
presentFirstInstallPlan(io, {
|
|
3187
|
-
projectRoot,
|
|
3188
|
-
pm: pmDetect.pm,
|
|
3189
|
-
pmEvidence: pmDetect.evidence,
|
|
3190
|
-
version: targetVersion,
|
|
3191
|
-
pmCommand: pmCmd,
|
|
3192
|
-
willCreatePkg,
|
|
3193
|
-
gitHooksInactive: !existsSync7(join6(projectRoot, ".git")),
|
|
3194
|
-
nodeWarning: checkNodeVersion(deps.nodeVersion).warning ?? null,
|
|
3195
|
-
verbosity: flags.verbosity
|
|
3196
|
-
});
|
|
3197
|
-
const planFp = fingerprintPlanCriticalFiles(projectRoot, pmDetect.pm);
|
|
3198
|
-
if (flags.dryRun) {
|
|
3199
|
-
io.writeOut("[dry-run] no changes made.\n");
|
|
3200
|
-
return EXIT_CODES.SUCCESS;
|
|
3201
|
-
}
|
|
3202
|
-
const approval = requireApproval(
|
|
3203
|
-
flags,
|
|
3204
|
-
deps,
|
|
3205
|
-
io,
|
|
3206
|
-
"Set up Directive in this project?"
|
|
3207
|
-
);
|
|
3208
|
-
if (approval !== null) return approval;
|
|
3209
|
-
const stale = assertPlanStillValid(projectRoot, pmDetect.pm, planFp, io);
|
|
3210
|
-
if (stale !== null) return stale;
|
|
3211
|
-
if (willCreatePkg) {
|
|
3212
|
-
ensurePackageJson(projectRoot);
|
|
3213
|
-
io.writeOut('Created package.json { "private": true }\n');
|
|
3214
|
-
}
|
|
3215
|
-
const add = deps.runPmAdd?.({ projectRoot, pm: pmDetect.pm, version: targetVersion }) ?? runPmExactAdd({ projectRoot, pm: pmDetect.pm, version: targetVersion, env });
|
|
3216
|
-
if (!add.ok) {
|
|
3217
|
-
return reportPmFailure(io, add);
|
|
3218
|
-
}
|
|
3219
|
-
const after = readProjectInstallState(projectRoot);
|
|
3220
|
-
if (after.pin.kind !== "exact" || after.pin.version !== targetVersion) {
|
|
3221
|
-
io.writeErr(
|
|
3222
|
-
`Pin verification failed: expected ${targetVersion}, got ${after.pin.raw ?? "(none)"}.
|
|
3223
|
-
`
|
|
3224
|
-
);
|
|
3225
|
-
return EXIT_CODES.FAILURE;
|
|
3226
|
-
}
|
|
3227
|
-
const installed = readInstalledRuntime(projectRoot);
|
|
3228
|
-
if (installed === null || installed.version !== targetVersion) {
|
|
3229
|
-
io.writeErr(
|
|
3230
|
-
`Installed package verification failed: expected ${RUNTIME_PACKAGE_NAME}@${targetVersion}, found ${installed?.version ?? "nothing"}.
|
|
3231
|
-
`
|
|
3232
|
-
);
|
|
3233
|
-
return EXIT_CODES.FAILURE;
|
|
3234
|
-
}
|
|
3235
|
-
if (installed.contract > 1) {
|
|
3236
|
-
}
|
|
3237
|
-
const verified = readProjectInstallState(projectRoot);
|
|
3238
|
-
if (!verified.contractSupported) {
|
|
3239
|
-
io.writeErr(
|
|
3240
|
-
"This launcher is too old for the installed runtime. `npm i -g @deftai/directive@latest`.\n"
|
|
3241
|
-
);
|
|
3242
|
-
return EXIT_CODES.FAILURE;
|
|
3243
|
-
}
|
|
3244
|
-
return handOffSetup({
|
|
3245
|
-
projectRoot,
|
|
3246
|
-
launcherVersion,
|
|
3247
|
-
runtime: installed,
|
|
3248
|
-
operation: "install",
|
|
3249
|
-
from: convertShapeId ?? "none",
|
|
3250
|
-
io,
|
|
3251
|
-
deps,
|
|
3252
|
-
verbosity: flags.verbosity,
|
|
3253
|
-
pm: pmDetect.pm
|
|
3254
|
-
}).exitCode;
|
|
3255
|
-
}
|
|
3256
|
-
function runRepair(projectRoot, state, flags, launcherVersion, io, deps, env) {
|
|
3257
|
-
if (state.pin.kind !== "exact" || state.pin.version === null) {
|
|
3258
|
-
io.writeErr(
|
|
3259
|
-
`Repair requires an existing unambiguous exact ${RUNTIME_PACKAGE_NAME} pin (found ${state.pin.kind}: ${state.pin.raw ?? "none"}). Rectify package.json; repair does not guess a version.
|
|
3260
|
-
`
|
|
3261
|
-
);
|
|
3262
|
-
return EXIT_CODES.FAILURE;
|
|
3263
|
-
}
|
|
3264
|
-
const pinVersion = state.pin.version;
|
|
3265
|
-
const pmDetect = detectPackageManager(projectRoot, flags.packageManager);
|
|
3266
|
-
if (!pmDetect.ok) {
|
|
3267
|
-
io.writeErr(`${pmDetect.message}
|
|
3268
|
-
`);
|
|
3269
|
-
return EXIT_CODES.USAGE;
|
|
3270
|
-
}
|
|
3271
|
-
const lockOk = lockfileRecordsExactPin(projectRoot, pinVersion, pmDetect.pm);
|
|
3272
|
-
const installedOk = state.installed !== null && state.installed.version === pinVersion && state.contractSupported;
|
|
3273
|
-
const needsPmRestore = !lockOk || !installedOk;
|
|
3274
|
-
if (needsPmRestore) {
|
|
3275
|
-
const pmMissing = preflightPmAvailable(projectRoot, pmDetect.pm, deps, io);
|
|
3276
|
-
if (pmMissing !== null) return pmMissing;
|
|
3277
|
-
}
|
|
3278
|
-
const pmCmd = formatPmExactAddCommand(pmDetect.pm, pinVersion, projectRoot);
|
|
3279
|
-
const restoreReason = lockOk ? "installed package mismatch/missing" : "lockfile missing/unusable/disagree";
|
|
3280
|
-
presentRepairPlan(io, {
|
|
3281
|
-
projectRoot,
|
|
3282
|
-
pm: pmDetect.pm,
|
|
3283
|
-
pmEvidence: pmDetect.evidence,
|
|
3284
|
-
version: pinVersion,
|
|
3285
|
-
needsPmRestore,
|
|
3286
|
-
pmCommand: needsPmRestore ? pmCmd : null,
|
|
3287
|
-
restoreReason: needsPmRestore ? restoreReason : null,
|
|
3288
|
-
gitHooksInactive: !existsSync7(join6(projectRoot, ".git")),
|
|
3289
|
-
nodeWarning: checkNodeVersion(deps.nodeVersion).warning ?? null,
|
|
3290
|
-
verbosity: flags.verbosity
|
|
3291
|
-
});
|
|
3292
|
-
const planFp = fingerprintPlanCriticalFiles(projectRoot, pmDetect.pm);
|
|
3293
|
-
if (flags.dryRun) {
|
|
3294
|
-
io.writeOut("[dry-run] no changes made.\n");
|
|
3295
|
-
return EXIT_CODES.SUCCESS;
|
|
3296
|
-
}
|
|
3297
|
-
const approval = requireApproval(
|
|
3298
|
-
flags,
|
|
3299
|
-
deps,
|
|
3300
|
-
io,
|
|
3301
|
-
`Repair Directive ${pinVersion} in this project?`
|
|
3302
|
-
);
|
|
3303
|
-
if (approval !== null) return approval;
|
|
3304
|
-
const stale = assertPlanStillValid(projectRoot, pmDetect.pm, planFp, io);
|
|
3305
|
-
if (stale !== null) return stale;
|
|
3306
|
-
if (needsPmRestore) {
|
|
3307
|
-
const add = deps.runPmAdd?.({ projectRoot, pm: pmDetect.pm, version: pinVersion }) ?? runPmExactAdd({ projectRoot, pm: pmDetect.pm, version: pinVersion, env });
|
|
3308
|
-
if (!add.ok) {
|
|
3309
|
-
return reportPmFailure(io, add);
|
|
3310
|
-
}
|
|
3311
|
-
}
|
|
3312
|
-
const after = readProjectInstallState(projectRoot);
|
|
3313
|
-
if (after.pin.version !== pinVersion) {
|
|
3314
|
-
io.writeErr(`Repair changed the pin unexpectedly (${after.pin.raw}). Aborting.
|
|
3315
|
-
`);
|
|
3316
|
-
return EXIT_CODES.FAILURE;
|
|
3317
|
-
}
|
|
3318
|
-
const installed = readInstalledRuntime(projectRoot);
|
|
3319
|
-
if (installed === null || installed.version !== pinVersion) {
|
|
3320
|
-
io.writeErr(
|
|
3321
|
-
`Repair verification failed: expected installed ${pinVersion}, found ${installed?.version ?? "nothing"}. ${RECOVERY_GUIDANCE}
|
|
3322
|
-
`
|
|
3323
|
-
);
|
|
3324
|
-
return EXIT_CODES.FAILURE;
|
|
3325
|
-
}
|
|
3326
|
-
if (!after.contractSupported) {
|
|
3327
|
-
io.writeErr(
|
|
3328
|
-
"This launcher is too old for this project. `npm i -g @deftai/directive@latest`.\n"
|
|
3329
|
-
);
|
|
3330
|
-
return EXIT_CODES.FAILURE;
|
|
3331
|
-
}
|
|
3332
|
-
return handOffSetup({
|
|
3333
|
-
projectRoot,
|
|
3334
|
-
launcherVersion,
|
|
3335
|
-
runtime: installed,
|
|
3336
|
-
operation: "repair",
|
|
3337
|
-
from: pinVersion,
|
|
3338
|
-
io,
|
|
3339
|
-
deps,
|
|
3340
|
-
verbosity: flags.verbosity,
|
|
3341
|
-
pm: pmDetect.pm,
|
|
3342
|
-
restoredPackage: needsPmRestore
|
|
3343
|
-
}).exitCode;
|
|
3344
|
-
}
|
|
3345
|
-
function reportInvokeStderr(io, result) {
|
|
3346
|
-
if (result.message) io.writeErr(`${result.message}
|
|
3347
|
-
`);
|
|
3348
|
-
if (result.stderr) {
|
|
3349
|
-
io.writeErr(result.stderr.endsWith("\n") ? result.stderr : `${result.stderr}
|
|
3350
|
-
`);
|
|
3351
|
-
}
|
|
3352
|
-
}
|
|
3353
|
-
function reportJsonSummary(io, label, result, verbosity) {
|
|
3354
|
-
reportInvokeStderr(io, result);
|
|
3355
|
-
if (!result.stdout) return null;
|
|
3356
|
-
const summary = tryParseSetupSummary(result.stdout);
|
|
3357
|
-
if (summary) {
|
|
3358
|
-
if (verbosity >= 1) {
|
|
3359
|
-
presentPathSummary(io, label, summary, verbosity);
|
|
3360
|
-
} else if (summary.message) {
|
|
3361
|
-
io.writeOut(`${summary.message}
|
|
3362
|
-
`);
|
|
3363
|
-
}
|
|
3364
|
-
return summary;
|
|
3365
|
-
}
|
|
3366
|
-
io.writeOut(result.stdout.endsWith("\n") ? result.stdout : `${result.stdout}
|
|
3367
|
-
`);
|
|
3368
|
-
return null;
|
|
3369
|
-
}
|
|
3370
|
-
function handOffSetup(opts) {
|
|
3371
|
-
const argv = setupArgv(opts.operation, opts.from, opts.projectRoot);
|
|
3372
|
-
const result = opts.deps.invokeSetup?.(argv) ?? invokeRuntime({
|
|
3373
|
-
projectRoot: opts.projectRoot,
|
|
3374
|
-
launcherVersion: opts.launcherVersion,
|
|
3375
|
-
runtime: opts.runtime,
|
|
3376
|
-
argv,
|
|
3377
|
-
stdio: "pipe"
|
|
3378
|
-
});
|
|
3379
|
-
const suppressPathDump = result.exitCode === 0 && (opts.operation === "install" || opts.operation === "repair" || opts.operation === "upgrade");
|
|
3380
|
-
let summary = null;
|
|
3381
|
-
if (suppressPathDump) {
|
|
3382
|
-
reportInvokeStderr(opts.io, result);
|
|
3383
|
-
summary = tryParseSetupSummary(result.stdout);
|
|
3384
|
-
} else {
|
|
3385
|
-
summary = reportJsonSummary(opts.io, `setup ${opts.operation}`, result, opts.verbosity);
|
|
3386
|
-
}
|
|
3387
|
-
if (result.exitCode === 0) {
|
|
3388
|
-
if (opts.operation === "install") {
|
|
3389
|
-
presentInstallSuccess(opts.io, {
|
|
3390
|
-
version: opts.runtime.version,
|
|
3391
|
-
projectRoot: opts.projectRoot,
|
|
3392
|
-
pm: opts.pm,
|
|
3393
|
-
summary,
|
|
3394
|
-
verbosity: opts.verbosity
|
|
3395
|
-
});
|
|
3396
|
-
} else if (opts.operation === "repair") {
|
|
3397
|
-
presentRepairSuccess(opts.io, {
|
|
3398
|
-
version: opts.runtime.version,
|
|
3399
|
-
restoredPackage: opts.restoredPackage ?? false,
|
|
3400
|
-
summary,
|
|
3401
|
-
verbosity: opts.verbosity,
|
|
3402
|
-
pm: opts.pm
|
|
3403
|
-
});
|
|
3404
|
-
}
|
|
3405
|
-
} else if (opts.operation === "install" || opts.operation === "upgrade" || opts.operation === "repair") {
|
|
3406
|
-
presentSetupFailedAfterInstall(opts.io, {
|
|
3407
|
-
version: opts.runtime.version,
|
|
3408
|
-
operation: opts.operation,
|
|
3409
|
-
failedPaths: summary?.conflicts,
|
|
3410
|
-
reason: result.message ?? summary?.message ?? null
|
|
3411
|
-
});
|
|
3412
|
-
}
|
|
3413
|
-
return { exitCode: result.exitCode, summary };
|
|
3414
|
-
}
|
|
3415
|
-
function handOffMigrate(opts) {
|
|
3416
|
-
const argv = migrateArgv(opts.from, opts.projectRoot);
|
|
3417
|
-
const result = opts.deps.invokeMigrate?.(argv) ?? invokeRuntime({
|
|
3418
|
-
projectRoot: opts.projectRoot,
|
|
3419
|
-
launcherVersion: opts.launcherVersion,
|
|
3420
|
-
runtime: opts.runtime,
|
|
3421
|
-
argv,
|
|
3422
|
-
stdio: "pipe"
|
|
3423
|
-
});
|
|
3424
|
-
reportInvokeStderr(opts.io, result);
|
|
3425
|
-
const summary = tryParseSetupSummary(result.stdout);
|
|
3426
|
-
if (result.exitCode !== 0 && opts.verbosity >= 1 && summary) {
|
|
3427
|
-
presentPathSummary(opts.io, "migrate", summary, opts.verbosity);
|
|
3428
|
-
} else if (result.exitCode !== 0 && !summary && result.stdout) {
|
|
3429
|
-
opts.io.writeOut(result.stdout.endsWith("\n") ? result.stdout : `${result.stdout}
|
|
3430
|
-
`);
|
|
3431
|
-
}
|
|
3432
|
-
return { exitCode: result.exitCode, summary };
|
|
3433
|
-
}
|
|
3434
|
-
function isWiringCurrentForVersion(projectRoot, version) {
|
|
3435
|
-
try {
|
|
3436
|
-
const genPath = join6(projectRoot, ".deft", "GENERATION.json");
|
|
3437
|
-
if (!existsSync7(genPath)) return false;
|
|
3438
|
-
const genRaw = JSON.parse(readFileSync5(genPath, "utf8"));
|
|
3439
|
-
if (genRaw === null || typeof genRaw !== "object" || Array.isArray(genRaw)) return false;
|
|
3440
|
-
if (genRaw.runtimeVersion !== version) return false;
|
|
3441
|
-
const ready = join6(projectRoot, ".deft", "directive", `wiring-ready.${version}.json`);
|
|
3442
|
-
if (!existsSync7(ready)) return false;
|
|
3443
|
-
const bodyRaw = JSON.parse(readFileSync5(ready, "utf8"));
|
|
3444
|
-
if (bodyRaw === null || typeof bodyRaw !== "object" || Array.isArray(bodyRaw)) return false;
|
|
3445
|
-
const bodyVersion = bodyRaw.runtimeVersion;
|
|
3446
|
-
if (bodyVersion !== void 0 && bodyVersion !== version) return false;
|
|
3447
|
-
return true;
|
|
3448
|
-
} catch {
|
|
3449
|
-
return false;
|
|
3450
|
-
}
|
|
3451
|
-
}
|
|
3452
|
-
function parseDoctorDataStatusResponse(result) {
|
|
3453
|
-
if (result.exitCode !== 0) {
|
|
3454
|
-
return {
|
|
3455
|
-
ok: false,
|
|
3456
|
-
message: result.message ?? `doctor diagnostic failed (exit ${result.exitCode})`
|
|
3457
|
-
};
|
|
3458
|
-
}
|
|
3459
|
-
try {
|
|
3460
|
-
const line = result.stdout.trim().split("\n").filter(Boolean).pop() ?? "";
|
|
3461
|
-
const parsed = JSON.parse(line);
|
|
3462
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3463
|
-
return { ok: false, message: "doctor data-status JSON malformed" };
|
|
3464
|
-
}
|
|
3465
|
-
const obj = parsed;
|
|
3466
|
-
if (obj.contract !== CONTRACT_NUMBER) {
|
|
3467
|
-
return {
|
|
3468
|
-
ok: false,
|
|
3469
|
-
message: `doctor data-status unsupported contract: ${String(obj.contract)}`
|
|
3470
|
-
};
|
|
3471
|
-
}
|
|
3472
|
-
const status = obj.dataStatus;
|
|
3473
|
-
if (status !== "current" && status !== "migration_needed" && status !== "diagnosis_failed") {
|
|
3474
|
-
return { ok: false, message: `doctor data-status unknown: ${String(status)}` };
|
|
3475
|
-
}
|
|
3476
|
-
return { ok: true, dataStatus: status };
|
|
3477
|
-
} catch {
|
|
3478
|
-
return {
|
|
3479
|
-
ok: false,
|
|
3480
|
-
message: result.message ?? "doctor data-status parse failed"
|
|
3481
|
-
};
|
|
3482
|
-
}
|
|
3483
|
-
}
|
|
3484
|
-
function obtainDataStatus(projectRoot, launcherVersion, runtime, deps) {
|
|
3485
|
-
if (deps.invokeDoctorDataStatus) return deps.invokeDoctorDataStatus();
|
|
3486
|
-
const result = invokeRuntime({
|
|
3487
|
-
projectRoot,
|
|
3488
|
-
launcherVersion,
|
|
3489
|
-
runtime,
|
|
3490
|
-
argv: doctorArgv(),
|
|
3491
|
-
stdio: "pipe"
|
|
3492
|
-
});
|
|
3493
|
-
return parseDoctorDataStatusResponse(result);
|
|
3494
|
-
}
|
|
3495
|
-
function runUpgrade(projectRoot, state, argv, launcherVersion, io = defaultIo(), deps = {}) {
|
|
3496
|
-
const { flags, rest, usageError } = parseFlags(argv);
|
|
3497
|
-
const env = deps.env ?? process.env;
|
|
3498
|
-
if (usageError !== null) {
|
|
3499
|
-
io.writeErr(`${usageError}
|
|
3500
|
-
`);
|
|
3501
|
-
return EXIT_CODES.USAGE;
|
|
3502
|
-
}
|
|
3503
|
-
if (rest.length > 0) {
|
|
3504
|
-
io.writeErr(`Unknown argument(s): ${rest.join(" ")}
|
|
3505
|
-
`);
|
|
3506
|
-
return EXIT_CODES.USAGE;
|
|
3507
|
-
}
|
|
3508
|
-
const upgradeShape = resolveInstallShape(projectRoot);
|
|
3509
|
-
if (upgradeShape !== null && isL2RefusalDecision(upgradeShape.decision)) {
|
|
3510
|
-
io.writeErr(`${formatShapeRecoveryMessage(upgradeShape, "upgrade")}
|
|
3511
|
-
`);
|
|
3512
|
-
return EXIT_CODES.USAGE;
|
|
3513
|
-
}
|
|
3514
|
-
const convertViaRemovals = upgradeShape !== null && upgradeShape.decision === "convert-via-removals";
|
|
3515
|
-
const exactPin = state.pin.kind === "exact" && state.pin.version !== null;
|
|
3516
|
-
if (!exactPin && !state.oldStyle && !convertViaRemovals) {
|
|
3517
|
-
io.writeErr(
|
|
3518
|
-
`Upgrade requires an exact ${RUNTIME_PACKAGE_NAME} pin or a supported old-style layout. Run \`directive install\`.
|
|
3519
|
-
`
|
|
3520
|
-
);
|
|
3521
|
-
return EXIT_CODES.USAGE;
|
|
3522
|
-
}
|
|
3523
|
-
const currentPin = state.pin.version ?? "none";
|
|
3524
|
-
const fromLabel = state.oldStyle ? "old-style" : convertViaRemovals ? upgradeShape.shapeId : currentPin;
|
|
3525
|
-
let targetVersion;
|
|
3526
|
-
if (flags.targetVersion !== null) {
|
|
3527
|
-
const validated = validateTargetVersionSpec(flags.targetVersion);
|
|
3528
|
-
if (!validated.ok) {
|
|
3529
|
-
io.writeErr(`${validated.message}
|
|
3530
|
-
`);
|
|
3531
|
-
return EXIT_CODES.USAGE;
|
|
3532
|
-
}
|
|
3533
|
-
targetVersion = validated.version;
|
|
3534
|
-
} else {
|
|
3535
|
-
const resolver = deps.resolveLatest ?? (() => resolveLatestStableVersion(env, projectRoot));
|
|
3536
|
-
const resolved = resolver();
|
|
3537
|
-
if (resolved instanceof Promise) {
|
|
3538
|
-
io.writeErr("Internal error: async resolveLatest is not supported in sync upgrade path.\n");
|
|
3539
|
-
return EXIT_CODES.FAILURE;
|
|
3540
|
-
}
|
|
3541
|
-
if (!resolved.ok) {
|
|
3542
|
-
io.writeErr(`${resolved.message}
|
|
3543
|
-
`);
|
|
3544
|
-
return EXIT_CODES.FAILURE;
|
|
3545
|
-
}
|
|
3546
|
-
targetVersion = resolved.version;
|
|
3547
|
-
}
|
|
3548
|
-
let versionOrd;
|
|
3549
|
-
try {
|
|
3550
|
-
versionOrd = state.oldStyle || convertViaRemovals ? 1 : compareExactVersions(targetVersion, currentPin);
|
|
3551
|
-
} catch (err) {
|
|
3552
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3553
|
-
io.writeErr(`${msg}
|
|
3554
|
-
`);
|
|
3555
|
-
return EXIT_CODES.FAILURE;
|
|
3556
|
-
}
|
|
3557
|
-
if (!state.oldStyle && !convertViaRemovals && versionOrd < 0) {
|
|
3558
|
-
io.writeErr(
|
|
3559
|
-
`Target ${targetVersion} is older than pin ${currentPin}; upgrade rejects downgrades.
|
|
3560
|
-
`
|
|
3561
|
-
);
|
|
3562
|
-
return EXIT_CODES.FAILURE;
|
|
3563
|
-
}
|
|
3564
|
-
const contractResolver = deps.resolvePublishedContract ?? ((version) => resolvePublishedContract(version, env, projectRoot));
|
|
3565
|
-
const published = contractResolver(targetVersion);
|
|
3566
|
-
if (!published.ok) {
|
|
3567
|
-
io.writeErr(`${published.message}
|
|
3568
|
-
`);
|
|
3569
|
-
return EXIT_CODES.FAILURE;
|
|
3570
|
-
}
|
|
3571
|
-
if (!isContractSupported(published.contract)) {
|
|
3572
|
-
io.writeErr(
|
|
3573
|
-
`This launcher is too old for target ${RUNTIME_PACKAGE_NAME}@${targetVersion} (contract ${published.contract} > ${LAUNCHER_MAX_SUPPORTED_CONTRACT}). \`npm i -g @deftai/directive@latest\`.
|
|
3574
|
-
`
|
|
3575
|
-
);
|
|
3576
|
-
return EXIT_CODES.FAILURE;
|
|
3577
|
-
}
|
|
3578
|
-
const versionChange = state.oldStyle || convertViaRemovals || versionOrd !== 0;
|
|
3579
|
-
const pmDetect = detectPackageManager(projectRoot, flags.packageManager);
|
|
3580
|
-
if (!pmDetect.ok) {
|
|
3581
|
-
io.writeErr(`${pmDetect.message}
|
|
3582
|
-
`);
|
|
3583
|
-
return EXIT_CODES.USAGE;
|
|
3584
|
-
}
|
|
3585
|
-
let needSetup = versionChange;
|
|
3586
|
-
let needMigrate = versionChange;
|
|
3587
|
-
let dataStatus = null;
|
|
3588
|
-
if (!versionChange) {
|
|
3589
|
-
if (state.installed === null || !state.pinMatchesInstalled || !state.contractSupported) {
|
|
3590
|
-
io.writeErr(
|
|
3591
|
-
`Equal-version upgrade requires the pinned runtime installed and contract-supported. ${RECOVERY_GUIDANCE}
|
|
3592
|
-
`
|
|
3593
|
-
);
|
|
3594
|
-
return EXIT_CODES.FAILURE;
|
|
3595
|
-
}
|
|
3596
|
-
const status = obtainDataStatus(projectRoot, launcherVersion, state.installed, deps);
|
|
3597
|
-
if (!status.ok) {
|
|
3598
|
-
io.writeErr(`Data-status diagnostic failed: ${status.message}
|
|
3599
|
-
`);
|
|
3600
|
-
return EXIT_CODES.FAILURE;
|
|
3601
|
-
}
|
|
3602
|
-
if (status.dataStatus === "diagnosis_failed") {
|
|
3603
|
-
io.writeErr("Data-status diagnostic reported diagnosis_failed; refusing no-op.\n");
|
|
3604
|
-
return EXIT_CODES.FAILURE;
|
|
3605
|
-
}
|
|
3606
|
-
dataStatus = status.dataStatus;
|
|
3607
|
-
const wiringCurrent = isWiringCurrentForVersion(projectRoot, currentPin);
|
|
3608
|
-
needSetup = !wiringCurrent;
|
|
3609
|
-
needMigrate = dataStatus === "migration_needed" || needSetup;
|
|
3610
|
-
if (!needSetup && !needMigrate) {
|
|
3611
|
-
io.writeOut(
|
|
3612
|
-
`directive upgrade: already current (${RUNTIME_PACKAGE_NAME}@${currentPin}; wiring + data current). No-op.
|
|
3613
|
-
`
|
|
3614
|
-
);
|
|
3615
|
-
return EXIT_CODES.SUCCESS;
|
|
3616
|
-
}
|
|
3617
|
-
}
|
|
3618
|
-
if (versionChange) {
|
|
3619
|
-
const pmMissing = preflightPmAvailable(projectRoot, pmDetect.pm, deps, io);
|
|
3620
|
-
if (pmMissing !== null) return pmMissing;
|
|
3621
|
-
}
|
|
3622
|
-
const pmCmd = versionChange ? formatPmExactAddCommand(pmDetect.pm, targetVersion, projectRoot) : null;
|
|
3623
|
-
presentUpgradePlan(io, {
|
|
3624
|
-
projectRoot,
|
|
3625
|
-
pm: pmDetect.pm,
|
|
3626
|
-
pmEvidence: pmDetect.evidence,
|
|
3627
|
-
version: targetVersion,
|
|
3628
|
-
currentVersion: currentPin,
|
|
3629
|
-
targetVersion,
|
|
3630
|
-
versionChange,
|
|
3631
|
-
pmCommand: pmCmd,
|
|
3632
|
-
needSetup,
|
|
3633
|
-
needMigrate,
|
|
3634
|
-
oldStyle: state.oldStyle,
|
|
3635
|
-
convertShapeId: convertViaRemovals ? upgradeShape.shapeId : null,
|
|
3636
|
-
gitHooksInactive: !existsSync7(join6(projectRoot, ".git")),
|
|
3637
|
-
nodeWarning: checkNodeVersion(deps.nodeVersion).warning ?? null,
|
|
3638
|
-
verbosity: flags.verbosity
|
|
3639
|
-
});
|
|
3640
|
-
const planFp = fingerprintPlanCriticalFiles(projectRoot, pmDetect.pm);
|
|
3641
|
-
if (flags.dryRun) {
|
|
3642
|
-
io.writeOut("[dry-run] no changes made.\n");
|
|
3643
|
-
return EXIT_CODES.SUCCESS;
|
|
3644
|
-
}
|
|
3645
|
-
const approval = requireApproval(
|
|
3646
|
-
flags,
|
|
3647
|
-
deps,
|
|
3648
|
-
io,
|
|
3649
|
-
versionChange ? `Upgrade Directive from ${currentPin} to ${targetVersion}?` : `Finish equal-version upgrade work for ${currentPin}?`
|
|
3650
|
-
);
|
|
3651
|
-
if (approval !== null) return approval;
|
|
3652
|
-
const stale = assertPlanStillValid(projectRoot, pmDetect.pm, planFp, io);
|
|
3653
|
-
if (stale !== null) return stale;
|
|
3654
|
-
if (versionChange) {
|
|
3655
|
-
if (state.oldStyle || convertViaRemovals) {
|
|
3656
|
-
for (const packageName of RETIRED_DIRECTIVE_PACKAGE_NAMES) {
|
|
3657
|
-
const rm = deps.runPmRemove?.({ projectRoot, pm: pmDetect.pm, packageName }) ?? runPmRemove({ projectRoot, pm: pmDetect.pm, packageName, env });
|
|
3658
|
-
if (!rm.ok && rm.interrupted) {
|
|
3659
|
-
return reportPmFailure(io, rm);
|
|
3660
|
-
}
|
|
3661
|
-
}
|
|
3662
|
-
removeLegacyDirectivePackages(projectRoot);
|
|
3663
|
-
const purged = purgeRetiredDirectiveLockfiles(projectRoot);
|
|
3664
|
-
if (purged.length > 0 && flags.verbosity >= 1) {
|
|
3665
|
-
io.writeOut(`Purged lockfile(s) referencing retired Directive packages: ${purged.join(", ")}
|
|
3666
|
-
`);
|
|
3667
|
-
}
|
|
3668
|
-
}
|
|
3669
|
-
if (convertViaRemovals && !existsSync7(join6(projectRoot, "package.json"))) {
|
|
3670
|
-
ensurePackageJson(projectRoot);
|
|
3671
|
-
}
|
|
3672
|
-
const add = deps.runPmAdd?.({ projectRoot, pm: pmDetect.pm, version: targetVersion }) ?? runPmExactAdd({ projectRoot, pm: pmDetect.pm, version: targetVersion, env });
|
|
3673
|
-
if (!add.ok) {
|
|
3674
|
-
return reportPmFailure(io, add);
|
|
3675
|
-
}
|
|
3676
|
-
const after = readProjectInstallState(projectRoot);
|
|
3677
|
-
if (after.pin.kind !== "exact" || after.pin.version !== targetVersion) {
|
|
3678
|
-
io.writeErr(
|
|
3679
|
-
`Pin verification failed: expected ${targetVersion}, got ${after.pin.raw ?? "(none)"}.
|
|
3680
|
-
`
|
|
3681
|
-
);
|
|
3682
|
-
return EXIT_CODES.FAILURE;
|
|
3683
|
-
}
|
|
3684
|
-
const installed = readInstalledRuntime(projectRoot);
|
|
3685
|
-
if (installed === null || installed.version !== targetVersion) {
|
|
3686
|
-
io.writeErr(
|
|
3687
|
-
`Installed package verification failed: expected ${RUNTIME_PACKAGE_NAME}@${targetVersion}, found ${installed?.version ?? "nothing"}.
|
|
3688
|
-
`
|
|
3689
|
-
);
|
|
3690
|
-
return EXIT_CODES.FAILURE;
|
|
3691
|
-
}
|
|
3692
|
-
if (!isContractSupported(installed.contract)) {
|
|
3693
|
-
io.writeErr(
|
|
3694
|
-
"This launcher is too old for the installed runtime. `npm i -g @deftai/directive@latest`.\n"
|
|
3695
|
-
);
|
|
3696
|
-
return EXIT_CODES.FAILURE;
|
|
3697
|
-
}
|
|
3698
|
-
const setupResult = handOffSetup({
|
|
3699
|
-
projectRoot,
|
|
3700
|
-
launcherVersion,
|
|
3701
|
-
runtime: installed,
|
|
3702
|
-
operation: "upgrade",
|
|
3703
|
-
from: fromLabel,
|
|
3704
|
-
io,
|
|
3705
|
-
deps,
|
|
3706
|
-
verbosity: flags.verbosity,
|
|
3707
|
-
pm: pmDetect.pm
|
|
3708
|
-
});
|
|
3709
|
-
if (setupResult.exitCode !== 0) {
|
|
3710
|
-
return setupResult.exitCode;
|
|
3711
|
-
}
|
|
3712
|
-
if (convertViaRemovals) {
|
|
3713
|
-
const pkgPath = join6(projectRoot, "package.json");
|
|
3714
|
-
if (!existsSync7(pkgPath)) {
|
|
3715
|
-
io.writeErr(
|
|
3716
|
-
`Convert upgrade lost package.json after setup (shape ${upgradeShape.shapeId}). Expected exact ${RUNTIME_PACKAGE_NAME}@${targetVersion}. Fix the cause, then run: directive upgrade --yes.
|
|
3717
|
-
`
|
|
3718
|
-
);
|
|
3719
|
-
return EXIT_CODES.FAILURE;
|
|
3720
|
-
}
|
|
3721
|
-
const pinAfterSetup = readProjectInstallState(projectRoot).pin;
|
|
3722
|
-
if (pinAfterSetup.kind !== "exact" || pinAfterSetup.version !== targetVersion) {
|
|
3723
|
-
io.writeErr(
|
|
3724
|
-
`Convert upgrade lost runtime pin after setup (shape ${upgradeShape.shapeId}): expected ${RUNTIME_PACKAGE_NAME}@${targetVersion}, got ${pinAfterSetup.raw ?? "(none)"}. Fix the cause, then run: directive upgrade --yes.
|
|
3725
|
-
`
|
|
3726
|
-
);
|
|
3727
|
-
return EXIT_CODES.FAILURE;
|
|
3728
|
-
}
|
|
3729
|
-
}
|
|
3730
|
-
const migrateResult = handOffMigrate({
|
|
3731
|
-
projectRoot,
|
|
3732
|
-
launcherVersion,
|
|
3733
|
-
runtime: installed,
|
|
3734
|
-
from: fromLabel,
|
|
3735
|
-
io,
|
|
3736
|
-
deps,
|
|
3737
|
-
verbosity: flags.verbosity
|
|
3738
|
-
});
|
|
3739
|
-
if (migrateResult.exitCode !== 0) {
|
|
3740
|
-
presentMigrateFailedAfterUpgrade(io, {
|
|
3741
|
-
version: targetVersion,
|
|
3742
|
-
versionChange: true,
|
|
3743
|
-
summary: migrateResult.summary
|
|
3744
|
-
});
|
|
3745
|
-
return migrateResult.exitCode;
|
|
3746
|
-
}
|
|
3747
|
-
presentUpgradeSuccess(io, {
|
|
3748
|
-
version: targetVersion,
|
|
3749
|
-
versionChange: true,
|
|
3750
|
-
pm: pmDetect.pm,
|
|
3751
|
-
summary: setupResult.summary,
|
|
3752
|
-
migrateSummary: migrateResult.summary,
|
|
3753
|
-
verbosity: flags.verbosity
|
|
3754
|
-
});
|
|
3755
|
-
return EXIT_CODES.SUCCESS;
|
|
3756
|
-
}
|
|
3757
|
-
const runtime = state.installed;
|
|
3758
|
-
let setupSummary = null;
|
|
3759
|
-
if (needSetup) {
|
|
3760
|
-
const setupResult = handOffSetup({
|
|
3761
|
-
projectRoot,
|
|
3762
|
-
launcherVersion,
|
|
3763
|
-
runtime,
|
|
3764
|
-
operation: "upgrade",
|
|
3765
|
-
from: fromLabel,
|
|
3766
|
-
io,
|
|
3767
|
-
deps,
|
|
3768
|
-
verbosity: flags.verbosity,
|
|
3769
|
-
pm: pmDetect.pm
|
|
3770
|
-
});
|
|
3771
|
-
if (setupResult.exitCode !== 0) {
|
|
3772
|
-
return setupResult.exitCode;
|
|
3773
|
-
}
|
|
3774
|
-
setupSummary = setupResult.summary;
|
|
3775
|
-
}
|
|
3776
|
-
let migrateSummary = null;
|
|
3777
|
-
if (needMigrate) {
|
|
3778
|
-
const migrateResult = handOffMigrate({
|
|
3779
|
-
projectRoot,
|
|
3780
|
-
launcherVersion,
|
|
3781
|
-
runtime,
|
|
3782
|
-
from: fromLabel,
|
|
3783
|
-
io,
|
|
3784
|
-
deps,
|
|
3785
|
-
verbosity: flags.verbosity
|
|
3786
|
-
});
|
|
3787
|
-
if (migrateResult.exitCode !== 0) {
|
|
3788
|
-
presentMigrateFailedAfterUpgrade(io, {
|
|
3789
|
-
version: currentPin,
|
|
3790
|
-
versionChange: false,
|
|
3791
|
-
summary: migrateResult.summary
|
|
3792
|
-
});
|
|
3793
|
-
return migrateResult.exitCode;
|
|
3794
|
-
}
|
|
3795
|
-
migrateSummary = migrateResult.summary;
|
|
3796
|
-
}
|
|
3797
|
-
presentUpgradeSuccess(io, {
|
|
3798
|
-
version: currentPin,
|
|
3799
|
-
versionChange: false,
|
|
3800
|
-
pm: pmDetect.pm,
|
|
3801
|
-
summary: setupSummary,
|
|
3802
|
-
migrateSummary,
|
|
3803
|
-
verbosity: flags.verbosity
|
|
3804
|
-
});
|
|
3805
|
-
return EXIT_CODES.SUCCESS;
|
|
3806
|
-
}
|
|
3807
|
-
function runUninstall(projectRoot, state, argv, launcherVersion, io = defaultIo(), deps = {}) {
|
|
3808
|
-
const { flags, usageError } = parseFlags(argv);
|
|
3809
|
-
const env = deps.env ?? process.env;
|
|
3810
|
-
if (usageError !== null) {
|
|
3811
|
-
io.writeErr(`${usageError}
|
|
3812
|
-
`);
|
|
3813
|
-
return EXIT_CODES.USAGE;
|
|
3814
|
-
}
|
|
3815
|
-
const discovery = discoverUninstallFootprint(projectRoot);
|
|
3816
|
-
if (!discovery.hasFootprint) {
|
|
3817
|
-
io.writeOut("No Directive installation footprint found in this project. Nothing to uninstall.\n");
|
|
3818
|
-
return EXIT_CODES.SUCCESS;
|
|
3819
|
-
}
|
|
3820
|
-
const removePaths = discovery.artifacts.filter((a) => a.kind !== "retained").map(
|
|
3821
|
-
(a) => a.kind === "whole-file" ? a.path : a.kind === "marked-section" ? `${a.path} (marked section)` : `${a.path} (${a.entryKey ?? "structured entry"})`
|
|
3822
|
-
);
|
|
3823
|
-
const keepPaths = discovery.artifacts.filter((a) => a.kind === "retained").map((a) => a.path);
|
|
3824
|
-
if (!keepPaths.includes("xbrief") && existsSync7(join6(projectRoot, "xbrief"))) {
|
|
3825
|
-
keepPaths.push("xbrief");
|
|
3826
|
-
}
|
|
3827
|
-
if (!keepPaths.includes(".deft/settings.json") && existsSync7(join6(projectRoot, ".deft", "settings.json"))) {
|
|
3828
|
-
keepPaths.push(".deft/settings.json");
|
|
3829
|
-
}
|
|
3830
|
-
keepPaths.push("user content in AGENTS.md / host configs", "global launcher");
|
|
3831
|
-
const pmDetect = detectPackageManager(projectRoot, flags.packageManager);
|
|
3832
|
-
const pmEvidence = pmDetect.ok ? pmDetect.evidence : "default (detection failed; will skip PM remove)";
|
|
3833
|
-
const packagesToRemove = [];
|
|
3834
|
-
if (discovery.hasRuntimePin) packagesToRemove.push(RUNTIME_PACKAGE_NAME);
|
|
3835
|
-
if (discovery.hasLegacyLauncherPin || state.oldStyle) {
|
|
3836
|
-
packagesToRemove.push(LAUNCHER_PACKAGE_NAME);
|
|
3837
|
-
}
|
|
3838
|
-
const pinRaw = state.pin.version ?? state.pin.raw;
|
|
3839
|
-
const pinVersion = pinRaw != null ? `${pinRaw}${state.oldStyle ? " (old-style)" : ""}` : state.oldStyle ? "(none) (old-style)" : null;
|
|
3840
|
-
const pmCommand = packagesToRemove.length > 0 && pmDetect.ok ? packagesToRemove.map((p) => formatPmRemoveCommand(pmDetect.pm, p, projectRoot)).join("; ") : null;
|
|
3841
|
-
const removal = summarizeUninstallRemovals(removePaths, {
|
|
3842
|
-
willRemovePin: packagesToRemove.length > 0,
|
|
3843
|
-
legacyCore: discovery.hasLegacyCore,
|
|
3844
|
-
preserveLabels: keepPaths
|
|
3845
|
-
});
|
|
3846
|
-
const extraHumanLines = flags.verbosity >= 1 ? [
|
|
3847
|
-
`Ownership record: ${discovery.ownershipInspection}`,
|
|
3848
|
-
`Installed runtime: ${state.installed?.version ?? "(missing/unavailable)"}`,
|
|
3849
|
-
state.installed && state.contractSupported ? "Repository uninstaller: invoke when available, then launcher fallback" : "Repository uninstaller: unavailable \u2014 launcher fallback only (no repair/upgrade required)",
|
|
3850
|
-
"hooksPath: unset .githooks only when safe for sibling worktrees"
|
|
3851
|
-
] : void 0;
|
|
3852
|
-
presentUninstallPlan(io, {
|
|
3853
|
-
projectRoot,
|
|
3854
|
-
pm: pmDetect.ok ? pmDetect.pm : null,
|
|
3855
|
-
pmEvidence: pmDetect.ok ? pmDetect.evidence : pmEvidence,
|
|
3856
|
-
pinVersion,
|
|
3857
|
-
pmCommand,
|
|
3858
|
-
verbosity: flags.verbosity,
|
|
3859
|
-
removal,
|
|
3860
|
-
extraHumanLines
|
|
3861
|
-
});
|
|
3862
|
-
if (flags.dryRun) {
|
|
3863
|
-
io.writeOut("[dry-run] no changes made.\n");
|
|
3864
|
-
return EXIT_CODES.SUCCESS;
|
|
3865
|
-
}
|
|
3866
|
-
const approval = requireApproval(
|
|
3867
|
-
flags,
|
|
3868
|
-
{
|
|
3869
|
-
stdinIsTTY: deps.stdinIsTTY,
|
|
3870
|
-
confirm: deps.confirm,
|
|
3871
|
-
readInteractiveLine: deps.readInteractiveLine
|
|
3872
|
-
},
|
|
3873
|
-
io,
|
|
3874
|
-
"Uninstall Directive from this project?"
|
|
3875
|
-
);
|
|
3876
|
-
if (approval !== null) return approval;
|
|
3877
|
-
let runtimeUninstallAttempted = false;
|
|
3878
|
-
let runtimeUninstallFailed = false;
|
|
3879
|
-
if (state.installed && state.contractSupported) {
|
|
3880
|
-
runtimeUninstallAttempted = true;
|
|
3881
|
-
const result = deps.invokeUninstall?.(uninstallArgv(projectRoot)) ?? invokeRuntime({
|
|
3882
|
-
projectRoot,
|
|
3883
|
-
launcherVersion,
|
|
3884
|
-
runtime: state.installed,
|
|
3885
|
-
argv: uninstallArgv(projectRoot),
|
|
3886
|
-
stdio: "pipe"
|
|
3887
|
-
});
|
|
3888
|
-
reportJsonSummary(io, "repository uninstall", result, flags.verbosity);
|
|
3889
|
-
if (result.exitCode !== 0) {
|
|
3890
|
-
runtimeUninstallFailed = true;
|
|
3891
|
-
io.writeOut(
|
|
3892
|
-
"Repository uninstaller reported failure or partial removal; continuing launcher fallback cleanup.\n"
|
|
3893
|
-
);
|
|
3894
|
-
}
|
|
3895
|
-
} else {
|
|
3896
|
-
io.writeOut(
|
|
3897
|
-
"Runtime unavailable or incompatible; continuing launcher fallback cleanup (R11).\n"
|
|
3898
|
-
);
|
|
3899
|
-
}
|
|
3900
|
-
const cleanup = deps.applyCleanup?.(projectRoot, discovery) ?? applyLauncherCleanup(projectRoot, discovery);
|
|
3901
|
-
if (flags.verbosity >= 1) {
|
|
3902
|
-
io.writeOut(
|
|
3903
|
-
`launcher cleanup: removed=${JSON.stringify(cleanup.removed)} modified=${JSON.stringify(cleanup.modified)} preserved=${JSON.stringify(cleanup.preserved)}
|
|
3904
|
-
`
|
|
3905
|
-
);
|
|
3906
|
-
if (cleanup.leftovers.length > 0) {
|
|
3907
|
-
io.writeOut(`leftovers: ${JSON.stringify(cleanup.leftovers)}
|
|
3908
|
-
`);
|
|
3909
|
-
}
|
|
3910
|
-
if (cleanup.uncertainty.length > 0) {
|
|
3911
|
-
io.writeOut(`uncertainty: ${JSON.stringify(cleanup.uncertainty)}
|
|
3912
|
-
`);
|
|
3913
|
-
}
|
|
3914
|
-
}
|
|
3915
|
-
if (packagesToRemove.length > 0) {
|
|
3916
|
-
if (!pmDetect.ok) {
|
|
3917
|
-
io.writeErr(`${pmDetect.message}
|
|
3918
|
-
`);
|
|
3919
|
-
io.writeErr("Could not remove package dependency via PM; pin may remain.\n");
|
|
3920
|
-
} else {
|
|
3921
|
-
for (const packageName of packagesToRemove) {
|
|
3922
|
-
if (packageName === LAUNCHER_PACKAGE_NAME) {
|
|
3923
|
-
removeLegacyDirectivePin(projectRoot);
|
|
3924
|
-
}
|
|
3925
|
-
const rm = deps.runPmRemove?.({ projectRoot, pm: pmDetect.pm, packageName }) ?? runPmRemove({ projectRoot, pm: pmDetect.pm, packageName, env });
|
|
3926
|
-
if (!rm.ok) {
|
|
3927
|
-
if (rm.interrupted) {
|
|
3928
|
-
return reportPmFailure(io, rm);
|
|
3929
|
-
}
|
|
3930
|
-
io.writeErr(`${rm.message}
|
|
3931
|
-
`);
|
|
3932
|
-
if (rm.stderr) io.writeErr(`${rm.stderr}
|
|
3933
|
-
`);
|
|
3934
|
-
io.writeErr(
|
|
3935
|
-
`PM remove of ${packageName} failed; continuing remaining uninstall steps.
|
|
3936
|
-
`
|
|
3937
|
-
);
|
|
3938
|
-
} else if (flags.verbosity >= 1) {
|
|
3939
|
-
io.writeOut(`Removed ${packageName} via ${pmDetect.pm}.
|
|
3940
|
-
`);
|
|
3941
|
-
}
|
|
3942
|
-
}
|
|
3943
|
-
}
|
|
3944
|
-
}
|
|
3945
|
-
const hooksPathResult = deps.cleanupHooksPath?.(projectRoot) ?? cleanupHooksPath(projectRoot);
|
|
3946
|
-
if (flags.verbosity >= 1) {
|
|
3947
|
-
io.writeOut(`core.hooksPath: ${hooksPathResult}
|
|
3948
|
-
`);
|
|
3949
|
-
}
|
|
3950
|
-
const verification = verifyUninstallComplete(projectRoot);
|
|
3951
|
-
const filteredLeftovers = verification.leftovers.filter((l) => {
|
|
3952
|
-
if (l === `package.json#${LAUNCHER_PACKAGE_NAME}` && !state.oldStyle && !discovery.hasLegacyLauncherPin) {
|
|
3953
|
-
return false;
|
|
3954
|
-
}
|
|
3955
|
-
return true;
|
|
3956
|
-
});
|
|
3957
|
-
const complete = filteredLeftovers.length === 0;
|
|
3958
|
-
presentUninstallSuccess(io, {
|
|
3959
|
-
projectRoot,
|
|
3960
|
-
complete,
|
|
3961
|
-
removal,
|
|
3962
|
-
removedPaths: cleanup.removed,
|
|
3963
|
-
preserved: cleanup.preserved.length > 0 ? cleanup.preserved : keepPaths,
|
|
3964
|
-
leftovers: filteredLeftovers,
|
|
3965
|
-
verbosity: flags.verbosity
|
|
3966
|
-
});
|
|
3967
|
-
if (runtimeUninstallAttempted && runtimeUninstallFailed && complete) {
|
|
3968
|
-
io.writeOut(
|
|
3969
|
-
"Repository uninstaller failed earlier, but launcher fallback verified complete removal.\n"
|
|
3970
|
-
);
|
|
3971
|
-
}
|
|
3972
|
-
if (!complete) {
|
|
3973
|
-
return EXIT_CODES.FAILURE;
|
|
3974
|
-
}
|
|
3975
|
-
return EXIT_CODES.SUCCESS;
|
|
3976
|
-
}
|
|
3977
|
-
function runDoctor(projectRoot, state, argv, launcherVersion, io = defaultIo()) {
|
|
3978
|
-
const { flags } = parseFlags(argv);
|
|
3979
|
-
const node = checkNodeVersion();
|
|
3980
|
-
if (node.warning) io.writeOut(`${node.warning}
|
|
3981
|
-
`);
|
|
3982
|
-
if (projectRoot === null || state === null) {
|
|
3983
|
-
io.writeErr("No Directive project here. Run `directive install`.\n");
|
|
3984
|
-
return EXIT_CODES.FAILURE;
|
|
3985
|
-
}
|
|
3986
|
-
if (state.oldStyle) {
|
|
3987
|
-
io.writeOut(
|
|
3988
|
-
"This project uses an older Directive layout. Run `directive upgrade`.\n"
|
|
3989
|
-
);
|
|
3990
|
-
return EXIT_CODES.FAILURE;
|
|
3991
|
-
}
|
|
3992
|
-
if (state.pin.kind === "none") {
|
|
3993
|
-
io.writeErr("No Directive project here. Run `directive install`.\n");
|
|
3994
|
-
return EXIT_CODES.FAILURE;
|
|
3995
|
-
}
|
|
3996
|
-
const pinLabel = state.pin.version ?? state.pin.raw ?? "(none)";
|
|
3997
|
-
const installedLabel = state.installed?.version ?? "(missing)";
|
|
3998
|
-
if (state.installed === null || !state.pinMatchesInstalled) {
|
|
3999
|
-
if (state.installed === null) {
|
|
4000
|
-
io.writeOut(
|
|
4001
|
-
`directive doctor: project ${projectRoot}
|
|
4002
|
-
launcher: ${launcherVersion}
|
|
4003
|
-
pin: ${pinLabel} (${state.pin.kind})
|
|
4004
|
-
installed: missing
|
|
4005
|
-
Recovery: ${RECOVERY_GUIDANCE}
|
|
4006
|
-
`
|
|
4007
|
-
);
|
|
4008
|
-
} else {
|
|
4009
|
-
io.writeOut(
|
|
4010
|
-
`directive doctor: project ${projectRoot}
|
|
4011
|
-
launcher: ${launcherVersion}
|
|
4012
|
-
pin: ${pinLabel} (${state.pin.kind})
|
|
4013
|
-
installed: ${installedLabel} (\u2260 pin)
|
|
4014
|
-
Recovery: ${RECOVERY_GUIDANCE}
|
|
4015
|
-
`
|
|
4016
|
-
);
|
|
4017
|
-
}
|
|
4018
|
-
return EXIT_CODES.FAILURE;
|
|
4019
|
-
}
|
|
4020
|
-
if (!state.contractSupported) {
|
|
4021
|
-
io.writeErr(
|
|
4022
|
-
"This launcher is too old for this project. `npm i -g @deftai/directive@latest`.\n"
|
|
4023
|
-
);
|
|
4024
|
-
return EXIT_CODES.FAILURE;
|
|
4025
|
-
}
|
|
4026
|
-
if (flags.dryRun) {
|
|
4027
|
-
io.writeOut(
|
|
4028
|
-
`directive doctor: project ${projectRoot}
|
|
4029
|
-
launcher: ${launcherVersion}
|
|
4030
|
-
pin: ${pinLabel}
|
|
4031
|
-
installed: ${installedLabel}
|
|
4032
|
-
(dry-run: runtime diagnosis not invoked)
|
|
4033
|
-
`
|
|
4034
|
-
);
|
|
4035
|
-
return EXIT_CODES.SUCCESS;
|
|
4036
|
-
}
|
|
4037
|
-
const result = invokeRuntime({
|
|
4038
|
-
projectRoot,
|
|
4039
|
-
launcherVersion,
|
|
4040
|
-
runtime: state.installed,
|
|
4041
|
-
argv: doctorDiagnoseArgv(),
|
|
4042
|
-
stdio: "pipe"
|
|
4043
|
-
});
|
|
4044
|
-
if (result.stdout) {
|
|
4045
|
-
io.writeOut(result.stdout.endsWith("\n") ? result.stdout : `${result.stdout}
|
|
4046
|
-
`);
|
|
4047
|
-
}
|
|
4048
|
-
if (result.stderr) {
|
|
4049
|
-
io.writeErr(result.stderr.endsWith("\n") ? result.stderr : `${result.stderr}
|
|
4050
|
-
`);
|
|
4051
|
-
}
|
|
4052
|
-
if (result.message) io.writeErr(`${result.message}
|
|
4053
|
-
`);
|
|
4054
|
-
return result.exitCode;
|
|
4055
|
-
}
|
|
4056
|
-
var runDoctorStub = runDoctor;
|
|
4057
|
-
|
|
4058
|
-
// packages/cli/src/version.ts
|
|
4059
|
-
import { readFileSync as readFileSync6 } from "node:fs";
|
|
4060
|
-
import { dirname as dirname2, join as join7 } from "node:path";
|
|
4061
|
-
import { fileURLToPath } from "node:url";
|
|
4062
|
-
var FALLBACK = "0.0.0";
|
|
4063
|
-
function readLauncherVersion() {
|
|
4064
|
-
try {
|
|
4065
|
-
const pkgPath = join7(dirname2(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
4066
|
-
const parsed = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
4067
|
-
if (parsed !== null && typeof parsed === "object") {
|
|
4068
|
-
const version = parsed.version;
|
|
4069
|
-
if (typeof version === "string" && version.length > 0) return version;
|
|
4070
|
-
}
|
|
4071
|
-
} catch {
|
|
4072
|
-
}
|
|
4073
|
-
return FALLBACK;
|
|
4074
|
-
}
|
|
4075
|
-
|
|
4076
|
-
// packages/cli/src/launcher.ts
|
|
4077
|
-
function defaultIo2() {
|
|
4078
|
-
return {
|
|
4079
|
-
writeOut: (t) => {
|
|
4080
|
-
process.stdout.write(t);
|
|
4081
|
-
},
|
|
4082
|
-
writeErr: (t) => {
|
|
4083
|
-
process.stderr.write(t);
|
|
4084
|
-
}
|
|
4085
|
-
};
|
|
4086
|
-
}
|
|
4087
|
-
function printHelp(version, io) {
|
|
4088
|
-
io.writeOut(
|
|
4089
|
-
`@deftai/directive ${version} \u2014 launcher
|
|
4090
|
-
|
|
4091
|
-
Usage: directive <command> [options]
|
|
4092
|
-
|
|
4093
|
-
Installation management:
|
|
4094
|
-
install [--repair] [--target-version <exact>] [--package-manager npm|pnpm]
|
|
4095
|
-
[--dry-run] [--yes] [--root <path>]
|
|
4096
|
-
upgrade [--target-version <ver>] [--dry-run] [--yes] [--root <path>]
|
|
4097
|
-
uninstall [--dry-run] [--yes] [--root <path>]
|
|
4098
|
-
doctor [--root <path>]
|
|
4099
|
-
|
|
4100
|
-
Aliases: init\u2192install, update\u2192upgrade, remove\u2192uninstall
|
|
4101
|
-
|
|
4102
|
-
Ordinary product commands pass through to the pinned ${RUNTIME_PACKAGE_NAME}.
|
|
4103
|
-
|
|
4104
|
-
First-time: npm i -g @deftai/directive && directive install
|
|
4105
|
-
Repair: ${RECOVERY_GUIDANCE}
|
|
4106
|
-
Upgrade: directive upgrade
|
|
4107
|
-
Uninstall: directive uninstall
|
|
4108
|
-
|
|
4109
|
-
Automation examples:
|
|
4110
|
-
directive install --yes
|
|
4111
|
-
directive install --repair --yes
|
|
4112
|
-
directive upgrade --yes
|
|
4113
|
-
directive uninstall --dry-run
|
|
4114
|
-
|
|
4115
|
-
Options:
|
|
4116
|
-
--version, -V Show launcher / pin status
|
|
4117
|
-
--help, -h Show this help
|
|
4118
|
-
--root <path> Project root (must be Git toplevel when in a repo)
|
|
4119
|
-
--target-version <exact> Exact runtime version (required for prereleases)
|
|
4120
|
-
--package-manager npm|pnpm Force PM when lockfile evidence is absent
|
|
4121
|
-
--repair Restore the existing exact pin + wiring
|
|
4122
|
-
--dry-run Show plan; change nothing
|
|
4123
|
-
--yes, -y Approve a fully resolved bookend plan
|
|
4124
|
-
`
|
|
4125
|
-
);
|
|
4126
|
-
}
|
|
4127
|
-
function printVersion(launcherVersion, projectRoot, state, io) {
|
|
4128
|
-
if (projectRoot === null || state === null) {
|
|
4129
|
-
io.writeOut(`launcher ${launcherVersion} \xB7 no project
|
|
4130
|
-
`);
|
|
4131
|
-
return;
|
|
4132
|
-
}
|
|
4133
|
-
const pin = state.pin.version ?? state.pin.raw ?? "(none)";
|
|
4134
|
-
let status;
|
|
4135
|
-
if (state.pin.kind === "none") {
|
|
4136
|
-
status = "no pin";
|
|
4137
|
-
} else if (state.oldStyle) {
|
|
4138
|
-
status = "older layout \u2014 run directive upgrade";
|
|
4139
|
-
} else if (state.installed === null) {
|
|
4140
|
-
status = "not installed";
|
|
4141
|
-
} else if (!state.pinMatchesInstalled) {
|
|
4142
|
-
status = `installed ${state.installed.version} (does not match pin)`;
|
|
4143
|
-
} else if (!state.contractSupported) {
|
|
4144
|
-
status = `installed ${state.installed.version} (contract too new for this launcher)`;
|
|
4145
|
-
} else {
|
|
4146
|
-
status = "installed (metadata verified)";
|
|
4147
|
-
}
|
|
4148
|
-
io.writeOut(`launcher ${launcherVersion} \xB7 project pins ${pin} (${status})
|
|
4149
|
-
`);
|
|
4150
|
-
}
|
|
4151
|
-
function resolveCommand(raw) {
|
|
4152
|
-
if (raw === void 0) return null;
|
|
4153
|
-
if (INSTALL_ALIASES[raw]) return INSTALL_ALIASES[raw];
|
|
4154
|
-
return raw;
|
|
4155
|
-
}
|
|
4156
|
-
function isInstallManagement(cmd) {
|
|
4157
|
-
return INSTALL_MANAGEMENT_COMMANDS.includes(cmd);
|
|
4158
|
-
}
|
|
4159
|
-
function runLauncher(argv, io = defaultIo2()) {
|
|
4160
|
-
const node = checkNodeVersion();
|
|
4161
|
-
if (!node.ok) {
|
|
4162
|
-
io.writeErr(`${node.message}
|
|
4163
|
-
`);
|
|
4164
|
-
return node.exitCode;
|
|
4165
|
-
}
|
|
4166
|
-
if (node.warning) {
|
|
4167
|
-
io.writeErr(`${node.warning}
|
|
4168
|
-
`);
|
|
4169
|
-
}
|
|
4170
|
-
const launcherVersion = readLauncherVersion();
|
|
4171
|
-
const { root: rootFlag, rest } = takeRootFlag(argv);
|
|
4172
|
-
if (rootFlag === "") {
|
|
4173
|
-
io.writeErr("directive: --root requires a path\n");
|
|
4174
|
-
return EXIT_CODES.USAGE;
|
|
4175
|
-
}
|
|
4176
|
-
const wantsVersion = rest.includes("--version") || rest.includes("-V");
|
|
4177
|
-
const commandIndex = rest.findIndex((a) => a !== "--help" && a !== "-h" && a !== "--version" && a !== "-V" && !a.startsWith("-"));
|
|
4178
|
-
const rawCmd = commandIndex >= 0 ? rest[commandIndex] : void 0;
|
|
4179
|
-
const forwardArgv = commandIndex >= 0 ? rest.filter((a, i) => i === commandIndex || a !== "--version" && a !== "-V") : rest.filter((a) => a !== "--version" && a !== "-V");
|
|
4180
|
-
const cmdArgv = commandIndex >= 0 ? forwardArgv.slice(1) : [];
|
|
4181
|
-
const wantsLauncherHelp = rest.length === 0 || rawCmd === void 0 && (rest.includes("--help") || rest.includes("-h"));
|
|
4182
|
-
const discovered = discoverProject({
|
|
4183
|
-
rootFlag,
|
|
4184
|
-
cwd: process.cwd()
|
|
4185
|
-
});
|
|
4186
|
-
let projectRoot = null;
|
|
4187
|
-
let state = null;
|
|
4188
|
-
if (discovered.ok) {
|
|
4189
|
-
projectRoot = discovered.root;
|
|
4190
|
-
if (discovered.movedUp && rootFlag === null && rawCmd !== void 0) {
|
|
4191
|
-
io.writeErr(`Using project root ${projectRoot} (moved up from ${process.cwd()})
|
|
4192
|
-
`);
|
|
4193
|
-
}
|
|
4194
|
-
state = readProjectInstallState(projectRoot);
|
|
4195
|
-
} else if (rootFlag !== null) {
|
|
4196
|
-
io.writeErr(`${discovered.message}
|
|
4197
|
-
`);
|
|
4198
|
-
return EXIT_CODES.USAGE;
|
|
4199
|
-
}
|
|
4200
|
-
if (wantsVersion && rawCmd === void 0) {
|
|
4201
|
-
printVersion(launcherVersion, projectRoot, state, io);
|
|
4202
|
-
return EXIT_CODES.SUCCESS;
|
|
4203
|
-
}
|
|
4204
|
-
if (wantsLauncherHelp) {
|
|
4205
|
-
printHelp(launcherVersion, io);
|
|
4206
|
-
return EXIT_CODES.SUCCESS;
|
|
4207
|
-
}
|
|
4208
|
-
if (rawCmd !== void 0 && /^(install|upgrade|uninstall|doctor)--/.test(rawCmd)) {
|
|
4209
|
-
const verb = rawCmd.slice(0, rawCmd.indexOf("--"));
|
|
4210
|
-
const restFlag = rawCmd.slice(rawCmd.indexOf("--"));
|
|
4211
|
-
io.writeErr(
|
|
4212
|
-
`Unknown command '${rawCmd}'. Did you mean: ${verb} ${restFlag} \u2026?
|
|
4213
|
-
Known installation commands: install, upgrade, uninstall, doctor.
|
|
4214
|
-
`
|
|
4215
|
-
);
|
|
4216
|
-
return EXIT_CODES.USAGE;
|
|
4217
|
-
}
|
|
4218
|
-
const cmd = resolveCommand(rawCmd);
|
|
4219
|
-
if (cmd === null) {
|
|
4220
|
-
printHelp(launcherVersion, io);
|
|
4221
|
-
return EXIT_CODES.SUCCESS;
|
|
4222
|
-
}
|
|
4223
|
-
if (RETIRED_INSTALL_VERBS.has(cmd) || rawCmd !== void 0 && RETIRED_INSTALL_VERBS.has(rawCmd)) {
|
|
4224
|
-
const suggestion = rawCmd === "migrate" || cmd === "migrate" || rawCmd === "install-upgrade" ? "Did you mean: upgrade?\n" : rawCmd === "sync" || rawCmd === "bootstrap" ? "Did you mean: install?\n" : rawCmd === "install-uninstall" ? "Did you mean: uninstall?\n" : "";
|
|
4225
|
-
io.writeErr(
|
|
4226
|
-
`Unknown command '${rawCmd}'. Known installation commands: install, upgrade, uninstall, doctor.
|
|
4227
|
-
` + suggestion
|
|
4228
|
-
);
|
|
4229
|
-
return EXIT_CODES.USAGE;
|
|
4230
|
-
}
|
|
4231
|
-
if (cmd === "doctor") {
|
|
4232
|
-
const positional = cmdArgv.find((a) => !a.startsWith("-"));
|
|
4233
|
-
if (positional === "fix") {
|
|
4234
|
-
io.writeErr(
|
|
4235
|
-
"Unknown command 'doctor fix'. Doctor is read-only. Known installation commands: install, upgrade, uninstall, doctor.\nDid you mean: install --repair?\n"
|
|
4236
|
-
);
|
|
4237
|
-
return EXIT_CODES.USAGE;
|
|
4238
|
-
}
|
|
4239
|
-
}
|
|
4240
|
-
const cmdWantsHelp = cmdArgv.includes("--help") || cmdArgv.includes("-h");
|
|
4241
|
-
if (projectRoot === null || state === null) {
|
|
4242
|
-
if (cmd === "install") {
|
|
4243
|
-
if (cmdWantsHelp) {
|
|
4244
|
-
printHelp(launcherVersion, io);
|
|
4245
|
-
return EXIT_CODES.SUCCESS;
|
|
4246
|
-
}
|
|
4247
|
-
io.writeErr(
|
|
4248
|
-
`${discovered.ok === false ? discovered.message : "No project root found."}
|
|
4249
|
-
Run from inside a repository, or pass --root <path>.
|
|
4250
|
-
`
|
|
4251
|
-
);
|
|
4252
|
-
return EXIT_CODES.FAILURE;
|
|
4253
|
-
}
|
|
4254
|
-
if (cmd === "doctor") {
|
|
4255
|
-
if (cmdWantsHelp) {
|
|
4256
|
-
printHelp(launcherVersion, io);
|
|
4257
|
-
return EXIT_CODES.SUCCESS;
|
|
4258
|
-
}
|
|
4259
|
-
return runDoctorStub(null, null, cmdArgv, launcherVersion, io);
|
|
4260
|
-
}
|
|
4261
|
-
io.writeErr(
|
|
4262
|
-
`No Directive project here. Run \`directive install\` inside a repository.
|
|
4263
|
-
`
|
|
4264
|
-
);
|
|
4265
|
-
return EXIT_CODES.FAILURE;
|
|
4266
|
-
}
|
|
4267
|
-
if (state.oldStyle) {
|
|
4268
|
-
const oldStyleAllowed = /* @__PURE__ */ new Set(["upgrade", "uninstall"]);
|
|
4269
|
-
if (!oldStyleAllowed.has(cmd)) {
|
|
4270
|
-
io.writeErr(
|
|
4271
|
-
"This project uses an older Directive layout. Run `directive upgrade`.\n"
|
|
4272
|
-
);
|
|
4273
|
-
return EXIT_CODES.FAILURE;
|
|
4274
|
-
}
|
|
4275
|
-
}
|
|
4276
|
-
if (state.installed !== null && !state.contractSupported && cmd !== "uninstall") {
|
|
4277
|
-
io.writeErr(
|
|
4278
|
-
"This launcher is too old for this project. `npm i -g @deftai/directive@latest`.\n"
|
|
4279
|
-
);
|
|
4280
|
-
return EXIT_CODES.FAILURE;
|
|
4281
|
-
}
|
|
4282
|
-
if (isInstallManagement(cmd)) {
|
|
4283
|
-
if (cmdWantsHelp) {
|
|
4284
|
-
printHelp(launcherVersion, io);
|
|
4285
|
-
return EXIT_CODES.SUCCESS;
|
|
4286
|
-
}
|
|
4287
|
-
switch (cmd) {
|
|
4288
|
-
case "install":
|
|
4289
|
-
return runInstall(projectRoot, state, cmdArgv, launcherVersion, io);
|
|
4290
|
-
case "upgrade":
|
|
4291
|
-
return runUpgrade(projectRoot, state, cmdArgv, launcherVersion, io);
|
|
4292
|
-
case "uninstall":
|
|
4293
|
-
return runUninstall(projectRoot, state, cmdArgv, launcherVersion, io);
|
|
4294
|
-
case "doctor":
|
|
4295
|
-
return runDoctorStub(projectRoot, state, cmdArgv, launcherVersion, io);
|
|
4296
|
-
}
|
|
4297
|
-
}
|
|
4298
|
-
if (!state.pinMatchesInstalled || state.installed === null) {
|
|
4299
|
-
io.writeErr(
|
|
4300
|
-
`Pinned ${RUNTIME_PACKAGE_NAME}@${state.pin.version ?? state.pin.raw ?? "?"} is not installed (found ${state.installed?.version ?? "nothing"}).
|
|
4301
|
-
Recovery: ${RECOVERY_GUIDANCE}
|
|
4302
|
-
`
|
|
4303
|
-
);
|
|
4304
|
-
return EXIT_CODES.FAILURE;
|
|
4305
|
-
}
|
|
4306
|
-
if (!state.contractSupported) {
|
|
4307
|
-
io.writeErr(
|
|
4308
|
-
"This launcher is too old for this project. `npm i -g @deftai/directive@latest`.\n"
|
|
4309
|
-
);
|
|
4310
|
-
return EXIT_CODES.FAILURE;
|
|
4311
|
-
}
|
|
4312
|
-
const result = invokeRuntime({
|
|
4313
|
-
projectRoot,
|
|
4314
|
-
launcherVersion,
|
|
4315
|
-
runtime: state.installed,
|
|
4316
|
-
argv: forwardArgv
|
|
4317
|
-
});
|
|
4318
|
-
if (result.message) io.writeErr(`${result.message}
|
|
4319
|
-
`);
|
|
4320
|
-
return result.exitCode;
|
|
4321
|
-
}
|
|
4322
|
-
|
|
4323
|
-
// packages/cli/src/bin.ts
|
|
4324
|
-
function isHookInvocation() {
|
|
4325
|
-
const mode = (process.env.DEFT_LAUNCHER_MODE || "").trim().toLowerCase();
|
|
4326
|
-
if (mode === "hook") return true;
|
|
4327
|
-
if (mode === "launcher" || mode === "directive" || mode === "deft") return false;
|
|
4328
|
-
const argv1 = (process.argv[1] ?? "").replace(/\\/g, "/");
|
|
4329
|
-
const inv = basename(argv1).toLowerCase().replace(/\.(js|mjs|cjs|cmd|ps1|exe)$/i, "");
|
|
4330
|
-
if (inv === "deft-hook" || inv.startsWith("deft-hook.")) return true;
|
|
4331
|
-
if (/(^|\/)deft-hook(\.[^/]+)?$/i.test(argv1)) return true;
|
|
4332
|
-
return false;
|
|
4333
|
-
}
|
|
4334
|
-
var code = isHookInvocation() ? runHookLauncher(process.argv.slice(2)) : runLauncher(process.argv.slice(2));
|
|
2
|
+
import { routeAndDispatch } from "./cli-router/index.js";
|
|
3
|
+
const code = await routeAndDispatch(process.argv.slice(2));
|
|
4335
4
|
process.exit(code);
|
|
5
|
+
//# sourceMappingURL=bin.js.map
|