migrate

package
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2026 License: MIT Imports: 30 Imported by: 0

Documentation

Overview

Package migrate plans and applies declarative source migrations.

Index

Constants

View Source
const MigrationFormatV1 = "https://sneat.dev/workbench/formats/migration/v1"

MigrationFormatV1 is the format contract for HCL migration specifications. It is deliberately a stable public URL so it can become human-readable documentation without teaching the runner a second, implicit versioning system.

Variables

This section is empty.

Functions

func Apply

func Apply(plan Plan) error

Apply writes all planned changes, refusing to overwrite a source file that changed after planning. Each individual file write is atomic.

func CleanupCampaignWorktrees

func CleanupCampaignWorktrees(githubDir, migrationID string) ([]string, error)

CleanupCampaignWorktrees removes clean, dedicated worktrees for one migration. It never removes canonical clones, branches, reports, or a worktree with uncommitted changes.

func WriteCampaignReports

func WriteCampaignReports(dir string, report CampaignReport) error

WriteCampaignReports writes both report formats to dir.

func WriteReports

func WriteReports(dir string, report Report) error

WriteReports writes the Markdown and YAML representations to dir. It is opt-in so a dry-run never creates files unless the caller asks for a report.

Types

type CampaignModuleReport

type CampaignModuleReport struct {
	Path                string                 `yaml:"path"`
	WorktreeDir         string                 `yaml:"worktree_dir,omitempty"`
	MigrationEnabled    bool                   `yaml:"migration_enabled"`
	PlanState           string                 `yaml:"plan_state"`
	ChangedFiles        *int                   `yaml:"changed_files,omitempty"`
	ManifestChanged     bool                   `yaml:"manifest_changed"`
	PublishableManifest bool                   `yaml:"publishable_manifest,omitempty"`
	DependencyDecisions []GoDependencyDecision `yaml:"dependency_decisions,omitempty"`
	ReviewItems         int                    `yaml:"review_items"`
	MigrationReportPath string                 `yaml:"migration_report_path,omitempty"`
	Verifications       []VerificationResult   `yaml:"verifications,omitempty"`
	Status              string                 `yaml:"status"`
}

CampaignModuleReport identifies the submodule work done inside a repository.

type CampaignOptions

type CampaignOptions struct {
	GitHubDir  string
	Ref        string
	ModuleRefs map[string]string
	Apply      bool
	Resume     bool
	Verify     Verification
	Commit     bool
	Push       bool
	PR         bool
	Merge      bool
	Parallel   int
	ReportDir  string

	// CloneURL overrides the canonical GitHub clone URL for a repository. It is
	// primarily useful for hermetic integration tests; normal campaigns use
	// https://<repository>.git.
	CloneURL func(repository string) string
}

CampaignOptions controls a hierarchical migration. Canonical clones are never checked out or altered; PR and merge phases are strictly opt-in.

type CampaignReport

type CampaignReport struct {
	SchemaVersion int                        `yaml:"schema_version"`
	Migration     ReportMigration            `yaml:"migration"`
	Status        string                     `yaml:"status"`
	SourceRoot    string                     `yaml:"source_root"`
	GitHubDir     string                     `yaml:"github_dir"`
	BaseRef       string                     `yaml:"base_ref"`
	Verification  Verification               `yaml:"verification"`
	Parallel      int                        `yaml:"parallel"`
	Repositories  []CampaignRepositoryReport `yaml:"repositories"`
}

CampaignReport is the Markdown/YAML index for a migration campaign.

func RunCampaign

func RunCampaign(spec Spec, sourceRoot string, options CampaignOptions) (CampaignReport, error)

RunCampaign plans or applies a hierarchy-aware Go migration. On apply it clones missing repositories into <github-dir>/<org>/<repo>, creates one dedicated worktree per repository, and modifies only those worktrees.

func (CampaignReport) Markdown

func (r CampaignReport) Markdown() string

Markdown renders the campaign index for human review and AI agents.

func (CampaignReport) YAML

func (r CampaignReport) YAML() ([]byte, error)

YAML renders the deterministic campaign manifest.

type CampaignRepositoryReport

type CampaignRepositoryReport struct {
	Repository     string                 `yaml:"repository"`
	CanonicalDir   string                 `yaml:"canonical_dir"`
	WorktreeDir    string                 `yaml:"worktree_dir"`
	Branch         string                 `yaml:"branch"`
	Ref            string                 `yaml:"ref"`
	Actions        []string               `yaml:"actions"`
	ChangedFiles   *[]string              `yaml:"changed_files,omitempty"`
	Modules        []CampaignModuleReport `yaml:"modules"`
	Commit         string                 `yaml:"commit,omitempty"`
	Pushed         bool                   `yaml:"pushed,omitempty"`
	PR             string                 `yaml:"pr,omitempty"`
	RequiredChecks []RemoteCheck          `yaml:"required_checks,omitempty"`
	Merged         bool                   `yaml:"merged,omitempty"`
}

CampaignRepositoryReport records the isolated worktree used for one GitHub repository. Modules can share a repository/worktree when a repo contains more than one go.mod.

type FileChange

type FileChange struct {
	Path           string   `json:"path"`
	Language       string   `json:"language"`
	OriginalSHA256 string   `json:"original_sha256"`
	Updated        []byte   `json:"-"`
	Steps          []string `json:"steps"`
}

FileChange describes one planned, atomic file replacement.

type Finding

type Finding struct {
	Path     string `json:"path"`
	Language string `json:"language"`
	RuleID   string `json:"rule_id"`
	Message  string `json:"message"`
	Lines    []int  `json:"lines"`
}

Finding is a semantic-review item discovered while building a plan.

type GoDependencyDecision added in v0.6.2

type GoDependencyDecision struct {
	Phase              string `yaml:"phase"`
	Path               string `yaml:"path"`
	RequiredAtCheck    bool   `yaml:"required_at_check"`
	VersionAtCheck     string `yaml:"version_at_check"`
	TargetVersion      string `yaml:"target_version,omitempty"`
	RequiredAfter      bool   `yaml:"required_after"`
	VersionAfter       string `yaml:"version_after"`
	VersionAction      string `yaml:"version_action"`
	ReplacementAtCheck string `yaml:"replacement_at_check,omitempty"`
	ReplacementAfter   string `yaml:"replacement_after,omitempty"`
	ReplacementAction  string `yaml:"replacement_action"`
	Reason             string `yaml:"reason"`
}

GoDependencyDecision records why one go.mod dependency did or did not change. VersionAtCheck captures the selected requirement before WB invokes Go tooling, while VersionAfter records the resulting requirement.

type GoModuleRelease

type GoModuleRelease struct {
	Path    string
	Version string
}

GoModuleRelease declares the published version that replaces a temporary campaign worktree replacement before a branch may become a pull request. It is intentionally separate from GoModuleRequire: an existing dependency may need an upgrade without being newly introduced by source edits.

type GoModuleRequire

type GoModuleRequire struct {
	Path    string
	Version string
}

GoModuleRequire declares a module made newly necessary by the migration. It is acted on only by a hierarchical Go campaign; ordinary source-only migration deliberately leaves package manifests alone.

type Plan

type Plan struct {
	MigrationID string       `json:"migration_id"`
	Changes     []FileChange `json:"changes"`
	Findings    []Finding    `json:"findings"`
}

Plan is an immutable preview of the edits a migration would make.

func BuildPlan

func BuildPlan(spec Spec, roots ...string) (Plan, error)

BuildPlan evaluates spec against every selected source file below roots. It never writes to those roots.

type RemoteCheck

type RemoteCheck struct {
	Name   string `yaml:"name"`
	Bucket string `yaml:"bucket"`
}

RemoteCheck is the required-check state reported by GitHub before --merge performs any merge. WB records it so a campaign report explains a block.

type Report

type Report struct {
	SchemaVersion int             `yaml:"schema_version"`
	Migration     ReportMigration `yaml:"migration"`
	Status        string          `yaml:"status"`
	Files         []ReportFile    `yaml:"files"`
	ReviewItems   []ReportFinding `yaml:"review_items,omitempty"`
}

Report is the portable index of a migration plan. It intentionally records files and operations, rather than embedding a large diff: reviewers and AI agents can ask Git for the exact diff at the repository and path named here.

func NewReport

func NewReport(spec Spec, plan Plan, roots []string, status string) Report

NewReport converts a plan into a sorted, deterministic report. Status is normally "planned" or "applied".

func (Report) Markdown

func (r Report) Markdown() string

Markdown renders a readable report with local-file links and commands that show each file's detailed diff after the migration is applied.

func (Report) YAML

func (r Report) YAML() ([]byte, error)

YAML renders the same report as stable machine-readable YAML.

type ReportFile

type ReportFile struct {
	Root           string   `yaml:"root"`
	Path           string   `yaml:"path"`
	AbsolutePath   string   `yaml:"absolute_path"`
	Language       string   `yaml:"language"`
	Operations     []string `yaml:"operations"`
	OriginalSHA256 string   `yaml:"original_sha256"`
	GitDiffCommand string   `yaml:"git_diff_command"`
}

ReportFile identifies one changed file and the Git command that reveals its detailed patch after an apply operation.

type ReportFinding

type ReportFinding struct {
	Root         string `yaml:"root"`
	Path         string `yaml:"path"`
	AbsolutePath string `yaml:"absolute_path"`
	Language     string `yaml:"language"`
	RuleID       string `yaml:"rule_id"`
	Message      string `yaml:"message"`
	Lines        []int  `yaml:"lines"`
}

ReportFinding is a semantic-review item linked to a local source file.

type ReportMigration

type ReportMigration struct {
	ID     string `yaml:"id"`
	Title  string `yaml:"title,omitempty"`
	Format string `yaml:"format"`
}

ReportMigration identifies the migration that produced a report.

type ReviewRule

type ReviewRule struct {
	ID             string
	Language       string
	Pattern        string
	ExcludePattern string
	Message        string
}

ReviewRule identifies a source pattern that needs semantic review after the mechanical plan. It is reported with affected files and line numbers, but never changes source code itself.

func (ReviewRule) Validate

func (r ReviewRule) Validate() error

type Scope

type Scope struct {
	Languages []string
	Include   []string
	Exclude   []string
}

Scope limits the files to which a migration applies. Empty Languages means every language known to the runner.

type Spec

type Spec struct {
	Format           string
	ID               string
	Title            string
	Scope            Scope
	Steps            []Step
	Review           []ReviewRule
	GoModuleRequires []GoModuleRequire
	GoModuleReleases []GoModuleRelease
}

Spec is a format-versioned, language-neutral migration definition.

The core owns discovery, planning, application, and reporting. Individual adapters own structural edits for their language.

func Load

func Load(path string) (Spec, error)

Load reads and validates an HCL migration specification with HashiCorp's official hclsimple decoder. WB defines the document schema below but never parses HCL itself.

func (Spec) Validate

func (s Spec) Validate() error

Validate rejects unsupported or ambiguous migration definitions before any source tree is inspected or changed.

type Step

type Step struct {
	Kind     string
	Language string

	From string
	To   string

	Import      string
	AddImport   string
	AddImportAs string
	Rewrites    map[string]string
}

Step is one declarative edit. Kinds are intentionally small:

  • text.replace applies an exact replacement to a selected file;
  • import.replace delegates an import-path rewrite to a language adapter;
  • selector.rewrite delegates a qualified-symbol rewrite to an adapter.
  • selector.rename renames one qualified symbol without changing imports.
  • composite_field.rename renames a keyed field in typed composite literals.

More language capabilities can be added without changing the runner's plan and apply protocol.

func (Step) Validate

func (s Step) Validate() error

type StructuralAdapter

type StructuralAdapter interface {
	Transform(source []byte, filename string, step Step) (updated []byte, changed bool, err error)
}

StructuralAdapter performs syntax-aware migration operations for one language. The engine deliberately does not attempt to normalize Go, Python, and TypeScript ASTs into one representation; each adapter owns correctness for its syntax, imports, and name binding.

type Verification

type Verification string

Verification selects the built-in, non-arbitrary checks a hierarchical Go campaign runs after applying changes. Full is the default for apply runs.

const (
	VerifyNone    Verification = "none"
	VerifyCompile Verification = "compile"
	VerifyTest    Verification = "test"
	VerifyFull    Verification = "full"
)

type VerificationResult

type VerificationResult struct {
	Command string `yaml:"command"`
	Passed  bool   `yaml:"passed"`
	Detail  string `yaml:"detail,omitempty"`
}

VerificationResult is deliberately compact: command output remains in the process terminal while this index gives reviewers and agents the result.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL