workspace migrate command

Preview and apply bounded workspace adoption with per-project loss checks and recoverable writes

workspace migrate

morphir workspace migrate adopts eligible projects within one selected directory. It uses the reusable inventory, aggregate planner/verifier, and transaction APIs. The same operation is available to embedded hosts without invoking this command.

Synopsis

morphir workspace migrate [<directory>]
    [--include <pattern>]... [--exclude <pattern>]...
    [--dry-run] [--json] [--discovery-boundary <directory>]

<directory> defaults to the current directory. It must name an existing directory. That directory bounds inventory, discovery, and all proposed destinations, including when it contains no workspace declaration. Migration does not search ancestors for projects to adopt. Select the intended repository root explicitly when invoking from a member or source directory.

Workspace migration never reads machine or personal configuration, so MORPHIR_ISOLATED and --isolated do not apply to it. See containment from the environment.

Arguments and options

ControlMeaning
<directory>Bounded root; omitted means current directory
--include <pattern>Include candidate project directories matching a root-relative pattern; repeatable
--exclude <pattern>Exclude candidate project directories; repeatable
--dry-runReturn the complete verified plan and write nothing
--jsonWrite one redacted JSON result to stdout; diagnostics remain on stderr
--discovery-boundary <directory>Outer limit the directory must lie within. Default: MORPHIR_DISCOVERY_BOUNDARY, else the filesystem root. A directory outside it fails with exit code 2 and a discovery.boundary diagnostic, and nothing is written
--helpShow usage without discovery or migration

Patterns are relative to the selected root. * and ? match within a path segment; ** matches across directory segments. Absolute patterns and .. segments are invalid. Exclusions prune matching subtrees. Shell-quote wildcards so the shell does not expand them before the CLI receives them. Prefix a literal leading-dash pattern with ./, for example --include './--experimental', so it cannot be mistaken for a missing value followed by an option. Existing workspace.exclude settings remain effective alongside the supplied exclusions. Explicit filters do not override nested workspace, link, version-control, dependency, or build-output boundaries. Invalid controls block before application.

morphir workspace migrate . --dry-run
morphir workspace migrate ./repo --dry-run --json
morphir workspace migrate . --include 'packages/*' --include 'services/*' \
  --exclude 'packages/experimental-*' --exclude 'services/retired' --dry-run --json

Inventory and membership

Inventory reports project candidates, excluded paths, skipped links, nested declared workspace boundaries, and already migrated projects in deterministic path order. It remains bounded even without an existing workspace. It skips generated output, version-control, and dependency directories and does not follow discovered links.

The plan uses explicit root-relative paths for new members. Existing membership, exclusions, settings, and valid workspace.default_member remain in place. Already migrated members are retained without rewriting their primaries. A root project keeps its identity. A root without a project becomes a virtual workspace. Duplicate project names produce diagnostics while their distinct paths remain separate.

For example, a new virtual workspace containing packages/orders and packages/payments receives:

[workspace]
members = ["packages/orders", "packages/payments"]

Each classic project receives its own supported TOML primary. Hidden primary layouts keep the owning project directory as the source-path anchor.

Conversion and verification

Every selected classic project uses the single-project migration contract, including the complete raw loss ledger, unsupported-field blockers, conflict checks, and materialized legacy defaults. A blocked member blocks the aggregate before any destination replacement.

Migration compares each classic source and its TOML conversion inside the same proposed base workspace context. It excludes converted classic sources from the candidate and ambient personal, machine, environment, explicit CLI, and activated profile overlays from both sides. Declared profile controls are still checked. The comparison preserves typed values and source-path meaning and checks the resulting workspace discovery, membership, and selection.

Adoption effects appear separately in the plan: ownership, membership, default selection, and shared output roots. Standalone projects may acquire a shared <workspace>/.morphir/out output context. That effect cannot excuse unrelated setting or source-path changes. Migration moves no generated artifacts and keeps every classic source byte-identical.

Output

Human output names the outcome, inventory entries, member paths, project edits, workspace edits, adoption effects, and verification. Diagnostics go to stderr.

JSON follows the workspace migration result schema. Its fields include:

FieldContent
operation, outcome, exitCodeOperation identity, result state, and exit code
rootPath, rootIsProjectBounded root and root versus virtual identity
changed, dryRun, verifiedPlanned changes, preview mode, and aggregate verification
inventoryPath, relative path, kind, reason, and project name
projectsPer-project redacted ledger, edits, conflicts, and independent verification
members, workspaceTarget, workspaceEditsReviewed membership and workspace primary edits
adoptionEffectsIntentional ownership, membership, default-selection, and output-root effects
diagnosticsStructured warnings and blockers also sent to stderr
recoveryArtifacts, restoredPaths, unresolvedPathsRetained owned evidence and recovery state
uncertainDirectories, preservedDirectoriesDirectory paths whose ownership is uncertain, or that explicit recovery was instructed to preserve
valueTypesTyped encoding metadata for nonfinite and temporal values in workspace edits

Each nested project report has its own valueTypes array. Its JSON pointers are relative to that project report. Sensitive settings and opaque secret references are redacted throughout the reports; reference targets are never resolved.

morphir workspace migrate . --dry-run --json > workspace-preview.json
morphir workspace migrate . --json > workspace-result.json
morphir workspace migrate . --json # unchanged after successful adoption

Exit codes

CodeMeaning
0Applicable preview, successful application, or valid existing-workspace no-op
2Invalid input, blocked conversion, failed verification, stale evidence, failed application, or recovery required

An undeclared directory with no eligible projects reports no adoption plan and exits 2. A valid existing workspace requiring no conversions succeeds unchanged. Equivalent repeats preserve target bytes and timestamps.

Application and recovery

Application validates and stages the entire aggregate before any destination is replaced. It coordinates overlapping cooperating writers, rechecks source, target, listing, and topology preconditions, and records durable recovery evidence. A later replacement failure triggers rollback attempts. The result identifies restored and unresolved paths.

Per-file atomic replacement does not make the batch crash-atomic. Incomplete rollback or interrupted state retains journals, stages, and backups and produces recovery-required. A read-only preview also reports pending recovery with exit 2 and retains the journal metadata. Overlapping configuration editing and project/workspace migration writes remain blocked until explicit recovery resolves that state. Successful application and proven complete rollback remove only owned transaction artifacts. User changes and unresolved evidence are preserved. If interruption occurs after directory creation but before its ownership receipt returns, uncertainDirectories names those paths explicitly. Recovery does not claim or delete them. The shared recovery API can explicitly preserve reviewed uncertain directories and reports them in preservedDirectories.

Use the shared inspection and recovery APIs. There is no automatic cleanup of uncertain recovery state. Do not delete a journal or backup to force a repeat migration through. Classic sources are never removed by migration or recovery.

Troubleshooting and automation

For a blocker, inspect projects[*].ledger, projects[*].conflicts, and diagnostics before changing source or target settings. A fresh preview is needed after source, target, relevant listing, or topology changes. A shared-output-root adoption effect does not permit a different source directory.

CI should inspect the exit code and retain a failed JSON result as an artifact:

morphir workspace migrate "$REPOSITORY" --dry-run --json > workspace-plan.json

A preview still requires independent review before choosing to apply it. No release, deployment, source deletion, or generated-output publication is part of this operation. Migration is owned by project and workspace; there is no config migrate alias.

See the walkthrough, workspace overview, and troubleshooting.