init

package
v0.0.0-...-6cc7f97 Latest Latest
Warning

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

Go to latest
Published: Jun 17, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package init defines the UX contract and state machine for the lucy init command. It covers the interactive multi-step flow, the non-interactive (--yes) fast path, and conflict-resolution semantics for partial lucy.yaml / lucy-lock.yaml state.

Init is takeover-first: its optimization target is adopting an existing server directory safely, not treating the directory as a mostly blank slate. For takeover-class init, Lucy must aggregate current server facts before it proposes desired intent. Existing lucy.yaml files remain informative context, but they must not silently outrank newer observed reality. Persistent intent changes still require explicit operator confirmation at review time.

This file intentionally contains NO huh/bubbletea TUI code. The flow logic is pure and testable without a terminal.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyDiscoveredDefaults

func ApplyDiscoveredDefaults(s *InitFlowState, defaults DiscoveredDefaults)

func CanProceed

func CanProceed(s *InitFlowState) bool

CanProceed reports whether enough information has been collected to write valid state files. The minimum required field is GameVersion.

CanProceed does NOT check Confirmed; callers must also verify that before performing any I/O. This preserves the takeover contract distinction between discovery-led proposal building and explicit user-approved persistence.

func RefreshObservedStateAfterInitWrites

func RefreshObservedStateAfterInitWrites(workDir string)

RefreshObservedStateAfterInitWrites refreshes probe state for the initialized directory so any subsequent takeover/status reads see post-init filesystem reality rather than stale memoized observations.

func RunInteractiveInit

func RunInteractiveInit(s *InitFlowState) error

RunInteractiveInit walks the user through the interactive init flow via huh forms, populating s in-place. Sets s.Aborted=true on cancellation at any step, s.Confirmed=true on final approval. No file I/O occurs here.

func ValidatePlatformSelection

func ValidatePlatformSelection(primary string, compatible []string) error

Types

type ConflictMode

type ConflictMode string

ConflictMode determines how init behaves when it detects that one or more lucy.yaml / lucy-lock.yaml files already exist.

const (
	// PreserveExisting keeps any file that already exists on disk and only
	// scaffolds the missing ones. This is the default and makes init
	// idempotent: running it twice produces no destructive change.
	PreserveExisting ConflictMode = "preserve"

	// AbortOnConflict refuses to write anything if ANY target file already
	// exists. The user must resolve manually or choose a different mode.
	AbortOnConflict ConflictMode = "abort"

	// OverwriteAll writes all files regardless of what currently exists on disk.
	// Existing content is replaced. The user must explicitly opt into this mode.
	OverwriteAll ConflictMode = "overwrite"
)

type DiscoveredDefaults

type DiscoveredDefaults struct {
	GameVersion            string
	Platform               string
	PlatformVersion        string
	DetectedPackages       []string
	PackageClassifications []TakeoverPackageClassification
	Confidence             DiscoveryConfidence
	ExistingLucy           ExistingLucyHints
}

func DiscoverServerDefaults

func DiscoverServerDefaults(workDir string) DiscoveredDefaults

DiscoverServerDefaults is the takeover aggregator used by the current init flow. Contractually, takeover-class init must aggregate current server information before it proposes desired intent:

  • discovery-first is only about sequence: discover before asking
  • discovery-led is about behavior: observed facts become the primary input to the proposal, and stale state files are demoted to advisory hints

workspace.ServerInfoAt(workDir) now provides the primary observed-state layer so takeover candidates come from the richer probe/runtime model first. Local file/archive heuristics remain fallback-only for gaps the probe could not explain. Existing state is recorded separately and only fills gaps when no live observation is available.

type DiscoveryConfidence

type DiscoveryConfidence string
const (
	ConfidenceHigh   DiscoveryConfidence = "high"
	ConfidenceMedium DiscoveryConfidence = "medium"
	ConfidenceLow    DiscoveryConfidence = "low"
	ConfidenceNone   DiscoveryConfidence = "none"
)

type ErrConflict

type ErrConflict struct {
	Mode          ConflictMode
	ConflictFiles []string
}

ErrConflict is returned when ConflictResolution == AbortOnConflict and one or more target files already exist.

func (*ErrConflict) Error

func (e *ErrConflict) Error() string

type ErrFlowIncomplete

type ErrFlowIncomplete struct {
	State *InitFlowState
}

ErrFlowIncomplete is returned when BuildResult is called on an incomplete flow state (CanProceed returns false).

func (*ErrFlowIncomplete) Error

func (e *ErrFlowIncomplete) Error() string

type ExistingLucyHints

type ExistingLucyHints struct {
	GameVersion     string
	Platform        string
	PlatformVersion string
	ConfigPresent   bool
	ManifestPresent bool
	LockPresent     bool
}

ExistingLucyHints captures pre-existing Lucy state as advisory context. Under takeover-first init, these hints may fill observation gaps or explain drift, but they must not silently outrank live observed state.

func (ExistingLucyHints) HasAny

func (h ExistingLucyHints) HasAny() bool

type InitDiscoveryMode

type InitDiscoveryMode string

InitDiscoveryMode distinguishes sequencing from behavior.

const (
	// DiscoveryFirst means discovery happens early in the sequence, but later
	// steps may still ignore or overwrite discovered facts.
	DiscoveryFirst InitDiscoveryMode = "discovery_first"

	// DiscoveryLed means discovery shapes the proposal itself: observed facts are
	// the primary input to takeover intent, existing lucy.yaml files are hints unless
	// re-confirmed, and the review step must surface any divergence before writes.
	DiscoveryLed InitDiscoveryMode = "discovery_led"
)

type InitFactSource

type InitFactSource string

InitFactSource identifies which input layer contributed a proposed init fact.

const (
	// FactSourceObserved is live filesystem/probe truth from the current server.
	FactSourceObserved InitFactSource = "observed"

	// FactSourceUserConfirmed is an explicit operator confirmation or override.
	// It does not remove the need to observe first; it is the confirmation gate
	// before persistent desired state is written.
	FactSourceUserConfirmed InitFactSource = "user_confirmed"

	// FactSourceExistingLucy is inherited context from pre-existing lucy.yaml files.
	// It is informative for takeover, but never silently authoritative.
	FactSourceExistingLucy InitFactSource = "existing_lucy"
)

func TakeoverFactPrecedence

func TakeoverFactPrecedence() []InitFactSource

TakeoverFactPrecedence returns the contract order for takeover-class init proposals. Testable rule: live observed state is primary, explicit operator confirmation is the approval gate for persisting or overriding, and existing lucy.yaml state is the lowest-precedence hint layer.

type InitFlowResult

type InitFlowResult struct {
	// ManifestToWrite is the Manifest that init will marshal to
	// lucy.yaml. Nil means preserve existing.
	ManifestToWrite *Manifest

	// LockToWrite is the empty Lock skeleton that init scaffolds in
	// lucy-lock.yaml. Nil means preserve existing.
	LockToWrite *Lock

	// SkippedFiles lists the state-file paths that were preserved because
	// ConflictResolution == PreserveExisting and they already existed.
	SkippedFiles []string

	// WrittenFiles lists the state-file paths that will be (or were) written.
	WrittenFiles []string
}

InitFlowResult is returned by BuildResult once the user has confirmed. It describes exactly what will be written and what will be preserved.

func BuildResult

func BuildResult(s *InitFlowState) (InitFlowResult, error)

BuildResult constructs an InitFlowResult from the completed flow state. It respects ConflictResolution and returns an error if AbortOnConflict would be violated or if CanProceed returns false.

BuildResult does NOT perform any file I/O. It only produces a plan. The actual writes are performed by the caller.

type InitFlowState

type InitFlowState struct {
	// OptimizationGoal declares the contract this init flow is aiming at.
	OptimizationGoal InitOptimizationGoal

	// DiscoveryMode documents whether init is only ordered discovery-first or is
	// behaviorally discovery-led for takeover.
	DiscoveryMode InitDiscoveryMode

	// CurrentStep is the step the flow is currently on.
	CurrentStep InitStep

	// GameVersion is the Minecraft game version the user entered (e.g. "1.21.4").
	GameVersion string

	// Platform is the chosen server platform identifier.
	// Valid values: "fabric", "neoforge", "forge", "mcdr", "none"
	Platform string

	// PlatformVersion is the chosen loader/platform version.
	// Empty when Platform == "none" or when the user skips the step.
	PlatformVersion string

	// CompatiblePlatforms are extra compatible ecosystems/controller layers that
	// can coexist with the primary runtime. Example: neoforge + fabric + sinytra + mcdr.
	CompatiblePlatforms []string

	// PackageClassifications is the in-session takeover graph classification.
	// It surfaces all discovered packages, marks whether each package is a leaf
	// or a dependency node, and maps operator choices onto the existing manifest
	// roles: required, transitive, or ignored.
	PackageClassifications []TakeoverPackageClassification

	// Confirmed is true only after the user explicitly approves the summary at
	// StepReview. No file I/O or persistent intent mutation must occur before
	// this is true.
	Confirmed bool

	// Aborted is true if the user cancelled the flow before StepReview +
	// Confirmed=true. When true, no files have been written.
	Aborted bool

	// ExistingFiles lists the lucy.yaml / lucy-lock.yaml state files that were already present on
	// disk when NewInitFlowState was called.
	ExistingFiles []string

	// ExistingStateConflicts lists existing state files that could not be safely
	// preserved because they were unreadable or invalid.
	ExistingStateConflicts []string

	// ConflictResolution controls how init handles the ExistingFiles.
	// Default: PreserveExisting.
	ConflictResolution ConflictMode

	// DiscoveredDefaults stores takeover inputs that init will use to propose a
	// starting intent before any file is written. Under the takeover-first
	// contract, these defaults should come from live observation first and only
	// fall back to existing lucy.yaml hints when observation is missing.
	DiscoveredDefaults DiscoveredDefaults
	// contains filtered or unexported fields
}

InitFlowState holds the mutable state accumulated as the user progresses through the init flow. It is passed by pointer through every step so that both the interactive TUI and the non-interactive fast path share one model.

func NewInitFlowState

func NewInitFlowState(workDir string) *InitFlowState

NewInitFlowState constructs an InitFlowState for the given working directory. It probes for pre-existing lucy.yaml / lucy-lock.yaml files and populates takeover defaults from discovery.

type InitOptimizationGoal

type InitOptimizationGoal string

InitOptimizationGoal states what init is trying to optimize for.

const (
	// OptimizationGoalTakeoverExistingServer makes existing-server adoption the
	// primary target. Init should prefer reconstructing the current environment
	// over inventing a fresh one.
	OptimizationGoalTakeoverExistingServer InitOptimizationGoal = "takeover_existing_server"
)

type InitStep

type InitStep string

InitStep names a discrete stage in the init flow.

const (
	// StepWelcome is the opening screen. It explains what lucy init does and
	// what files it will create. No input is collected here.
	StepWelcome InitStep = "welcome"

	// StepGameVersion asks the user for the Minecraft game version (e.g.
	// "1.21.4"). This is the only mandatory question for a minimal init.
	StepGameVersion InitStep = "game_version"

	// StepPlatform asks which primary server runtime to use.
	// Valid values: "fabric", "neoforge", "forge", "mcdr", "none"
	// "none" means vanilla or an as-yet-unknown platform.
	StepPlatform InitStep = "platform"

	// StepPlatformVersion asks for the platform loader version. This step is
	// skipped when Platform == "none".
	StepPlatformVersion InitStep = "platform_version"

	// StepSources lets the user configure source priority (modrinth, curseforge,
	// github, mcdr). This step is optional and may be skipped in minimal flows.
	StepSources InitStep = "sources"

	// StepPackageClassification lets the user review the detected package graph,
	// distinguish leaf packages from graph-only dependencies, and classify them
	// into the existing manifest roles without inventing a new persistent role.
	StepPackageClassification InitStep = "package_classification"

	// StepReview shows the user a complete summary of what will be written
	// before any file I/O occurs. Confirmation here sets Confirmed = true.
	StepReview InitStep = "review"

	// StepDone is the terminal step displayed after files are successfully
	// written. No further state changes occur after this step.
	StepDone InitStep = "done"
)

func NextStep

func NextStep(s *InitFlowState) InitStep

NextStep returns the step that should follow the current state, applying any conditional skips. It does not mutate s; the caller is responsible for updating s.CurrentStep.

Rules:

  • StepPlatformVersion is skipped when Platform == "" or Platform == "none".
  • StepDone has no successor; returning StepDone from StepDone is a no-op sentinel.

type Lock

type Lock = state.Lock

type Manifest

type Manifest = state.Manifest

type TakeoverPackageClassification

type TakeoverPackageClassification struct {
	ID         string
	Version    string
	Source     string
	Role       state.ManifestRole
	Side       state.ManifestSide
	Optional   bool
	Pinned     bool
	Leaf       bool
	Requires   []string
	RequiredBy []string
}

func BuildTakeoverPackageClassifications

func BuildTakeoverPackageClassifications(packages []types.Package) []TakeoverPackageClassification

Jump to

Keyboard shortcuts

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