This document is for coding agents working on the Lucy codebase.
Lucy is a Minecraft server package manager, like apt or cargo but for server mods/plugins. Written in Go 1.26.4 (github.com/mclucy/lucy). Apache 2.0 licensed.
Users declare desired packages in a manifest. Lucy resolves versions and dependencies, writes a lock file, and installs them into a Minecraft server directory.
- If not otherwise specified, you are allowed to make commits.
- You are always NOT allowed to perform
git pushorgit pull. - Do not create new branches or worktrees if not explicitly asked.
Uses Taskfile (task), not Make.
task build # Build debug binary → dist/lucy-{os}-{arch}-dev
task dev # Same as build
task run -- [args] # go run from repo root with CLI args
task build:dev # Same as build (host dev binary, incremental)
task build:watch # Rebuild on Go file changes (file watcher)
task build:release # Cross-compile all platforms (-tags release -w -s)
task test # go test ./...
task test:race # go test -race ./...
task check # build:dev + test + test:race
task clean # Remove dist/ and release/ directories
task clean:dist # Remove dist/ only
task clean:release # Remove release/ only
task cipher:generate # Generate cipher files from CF_API_KEY env var
task copyright:add # Add Apache 2.0 license headers
task copyright:rm # Remove copyright headers
task ci:test # CI checks (optional cipher:generate, then check)
task ci:build # CI release build + gzip artifactsBuild uses ldflags to inject cipher key+ciphertext via -X github.com/mclucy/lucy/internal/cipher.Key=$KEY. Dotenv loads .env, .cipher_key, .cipher_ciphertext.
To run the built binary against a test server directory:
./dist/lucy-darwin-arm64-dev status
./dist/lucy-darwin-arm64-dev init --yes --game-version 1.21.4main.go (17 lines): defer logger.DumpHistory(); cmd.Execute()
.
├── cmd/ Cobra CLI commands (14+ files)
│ └── init/ Large init state machine sub-package
├── types/ Pure domain types
├── state/ Two-file state model: lucy.yaml + lucy-lock.yaml
├── upstream/ Atomic capability interfaces: Searcher, Informer, etc.
│ └── providers/ Modrinth, CurseForge, GitHub, MCDR, Hangar, Spiget, Fabric, etc.
├── workspace/ Runtime detection — server platform, version, installed mods
│ └── detector/ Declarative runtime node detection
├── install/ RecursivePhase pipeline: Candidate→Downloaded→Verified→Committed
├── input/ Trust boundary for external input, package ref parsing
├── cache/ Three-layer network cache: store, index, policy
├── version/ Version parsing: semver, Minecraft release/snapshot, Maven ranges
├── artifact/ Jar metadata analysis: fabric.mod.json, mods.toml, plugin.yml
├── logger/ Three-tier logging + Fatal, history buffer for DumpHistory()
├── tui/ Terminal UI via bubbletea v2, lipgloss v2, huh v2, glamour
├── github/ Shared GitHub API infra
└── internal/
├── cipher/ ChaCha20Poly1305 encryption for API key embedding
├── fileschema/ Minecraft format definitions: fabric.mod.json, mods.toml, etc.
├── fn/ Generic helpers: Ternary, Memoize, slice utilities
├── fsutil/ Filesystem helpers: CloseReader, path utilities
├── algo/ Graph operations, data structure utilities
├── slugmap/ Remote-to-local slug mapping
└── networkutil/ Network helpers wrapping cache downloads
- types/ has zero dependencies. Never import anything into types. All other packages depend on types, not the reverse.
- Dependency inversion via atomic interfaces. Upstream sources implement atomic capabilities (
Searcher,Informer,ArtifactMapper,VersionSelectorResolver); composite interfaces (PackageResolver,PackageSource) emerge from consumers via type assertion. The install package consumes through interfaces, not concrete types. - State ownership is strict. Only
ProjectStateServicereads/writes state files. Don't bypass it. - Three-tier logger, never
fmt.Printlnfor user output. Use the logger package: file-only, user-display, or both tiers.
lucy.yaml— declared package intent (what the user wants) + optional config overrideslucy-lock.yaml— resolved dependency graph (exact versions, checksums, install paths)- Global config:
os.UserConfigDir()/lucy/config.yaml(user preferences, defaults)
cobra v1.10.2, bubbletea v2, huh v2, lipgloss v2, semver v3, go-toml, glamour, fuzzy, ini.v1, yaml.v3
- If your task is not general, i.e., the ones applicable and universal to almost any program, you should consider doing some research to know about the specific context.
- Always do research on complicated and large features or refactors.
- While researching, you should take reference to other package managers, such as Cargo, npm, pip, apt, brew, etc. This does not mean you should copy their design. Combine your research with our own design principles.
- If the task is highly Minecraft-related, it is very likely that you don't have the most-updated or correct knowledge about it. Either do some research or ask me if you are not sure about something.
- Whenever you are adding new types/enums/structs, you must elaborate and justify your design.
- I am open to adding new packages if you think they will greatly simplify the code. Ask me before doing that.
- You must always justify your design. Elaborate your architecture's shape and why is it.
- Do not add, propose, or even waste time on thinking about tests if not explicitly asked.
- Tests will always be prompted as isolated tasks.
- You are always allowed to use
go testto audit.
You may find test_* directories under the project root. They are sandbox servers for smoke tests. They are .gitignored so you might not be able to find them with provided grep or glob, use ls to discover them instead.
- Don't import into types/. It has zero dependencies by design. If you need a type that depends on something external, it belongs in the consuming package, not types.
- Don't bypass ProjectStateService. All state file reads/writes go through it. Direct file manipulation breaks atomicity guarantees.
- Don't use fmt.Println for user output. The logger has three tiers for a reason. Use them.
- Minecraft knowledge is unreliable. Don't assume you know how mod loaders, plugin systems, or server internals work. Research or ask.
- Upstream providers are routed by Source enum.
hangarandspigetare defined but not wired into the resolver. Don't assume they work. - The cipher system embeds API keys at build time.
task cipher:generaterequiresCF_API_KEYin the environment. Without it, CurseForge integration won't work. - Package identifiers are
[source]:[platform/]name[@version]. Platform and version are optional. Lucy infers platform from the server environment.
- If you suspect there might be helpful packages to add, you should search on the web, or look up on go.dev.
- If you believe the initial demand is fully satisfied and all current context will not be helpful for future tasks, you can remind me to open a new session.