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 ¶
- func ApplyDiscoveredDefaults(s *InitFlowState, defaults DiscoveredDefaults)
- func CanProceed(s *InitFlowState) bool
- func RefreshObservedStateAfterInitWrites(workDir string)
- func RunInteractiveInit(s *InitFlowState) error
- func ValidatePlatformSelection(primary string, compatible []string) error
- type ConflictMode
- type DiscoveredDefaults
- type DiscoveryConfidence
- type ErrConflict
- type ErrFlowIncomplete
- type ExistingLucyHints
- type InitDiscoveryMode
- type InitFactSource
- type InitFlowResult
- type InitFlowState
- type InitOptimizationGoal
- type InitStep
- type Lock
- type Manifest
- type TakeoverPackageClassification
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.
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 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