# PackLSP user guide PackLSP checks a Minecraft modpack while you write it. It runs in VSCodium as a language server, on the command line as `packlsp check`, and for agents as an MCP server. It is the third Reverie tool, after Packwand (manage and build the pack) and Packeater (optimize the output). ## Install 1. Download the `.vsix` for your platform from the release page: `packlsp-linux-x64-.vsix` or `packlsp-win32-x64-.vsix`. 2. Install it: ```sh codium --install-extension packlsp-linux-x64-.vsix ``` Or use **Extensions → … → Install from VSIX…** in VSCodium. 3. Open a pack folder. The status bar shows the detected Minecraft version and loader. Hover it for details, or run **PackLSP: Show Pack Info**. The server binary ships inside the `.vsix`, so nothing else needs to be installed. Packwand is optional: with it on `PATH`, saving pack files also runs `packwand lock check` (see below). ### IntelliJ IDEA and other JetBrains IDEs PackLSP runs in any JetBrains IDE through Red Hat's [LSP4IJ](https://plugins.jetbrains.com/plugin/23257-lsp4ij) plugin, from a template in this repository: 1. Install LSP4IJ (**Settings > Plugins**). 2. Add a new language server in LSP4IJ (the **+** in **Settings > Languages & Frameworks > Language Servers**, or in the **Language Servers** tool window). In its **Template** list, import a custom template from the `editors/jetbrains/lsp4ij-template` folder. 3. Set the command if `packlsp` isn't on your PATH. You can use the binary inside the VSCodium extension (`~/.vscode-oss/extensions/reverie-projects.packlsp-/server/linux-x64/packlsp`) or a `nix build .#packlsp-static` from the monorepo root. Diagnostics, quick fixes, hover, completion, navigation (definition, references, rename, workspace symbols), signature help, inlay hints, semantic highlighting, folding and the load-order code lens work as in VSCodium. **Reformat Code** uses PackFMT for KubeJS scripts and ZenScript files only; other JavaScript keeps the IDE's formatter. The settings are in the server's **Configuration** tab. LSP4IJ maps files by type, not by folder, so PackLSP also opens for JSON, TOML and `.properties` files outside packs. There it only reports syntax errors, which the IDE already shows. ## What it checks | Files | Checks | |---|---| | Packwand `pack.json`, `*.bun.json`, workspace `manifest.json` | Structure, provider coordinates, `chosen`/`update`, dependency paths, lockable hashes. A manifest that declares `pack_format` is a pwf:31 project and gets its own schema (`manifest-pwf31.json`): variants, providers, targets, content, build outputs, the changelog and scripts, with leftover pwf:30 members flagged as deprecated. Leftover packwiz `pack.toml`/`*.pw.toml` files are flagged. | | `pack.mcmeta`, `*.png.mcmeta` | Pack format fields against the pack's Minecraft version, overlays, animation metadata, Sodium's `ignored_shaders`. | | Resource and data pack JSON | Every vanilla file kind (models, blockstates, recipes, loot tables, advancements, tags, worldgen, …), typed per version from vanilla-mcdoc. Unknown ids get a "did you mean". Folder names are checked per version (`recipes/` vs `recipe/`, 26.3's `worldgen/feature`). Blockstate properties are checked against the block, `sounds.json` against vanilla sounds, and lang strings against the formats the game can apply. | | Mod configs (`config/`, `defaultconfigs/`) | Forge/NeoForge TOML `#Range:` and `#Allowed Values:` comments, JSON/JSON5 syntax, configs no installed mod seems to own. | | `server.properties` | Keys the server reads in this version, removed keys (with the game rule that replaced them), types and ranges. | | OptiFine-format CTM and CIT (`optifine/ctm`, `optifine/cit`) | Methods and types, tile counts, tile/texture/model files, `matchBlocks` and `items` ids. Read by OptiFine, Continuity and CIT Resewn. | | Core shaders (`assets/*/shaders/**`) | `#version`, imports and includes per version, uniforms and uniform blocks, stage interfaces, program and post-effect JSON, Sodium's bypassed shaders. | | KubeJS (`kubejs/*_scripts/**/*.js`) | Events per script folder and KubeJS version, removed APIs, the class filter, Rhino syntax limits on KubeJS 6, ids in strings. Across a script folder: names declared twice in the shared scope, names used before the script declaring them loads, tag operations that undo earlier ones, and ids in the wrong tag registry. A code lens on line 1 shows where the script loads. Strings are read the way KubeJS reads them for the type they are passed as (ingredients, item stacks, recipe arguments), and recipe functions are checked against their recipe schema. Hover and completion know the Java API behind a handler's event object and the global bindings (`event.get('…').add(…)`, `Item.of(…)` and the like), also through variables, destructuring and callbacks (`const r = event.smelting(…)`, `list.forEach(x => x.…)`), and through `event.recipes..`; string completion offers ids of the registry the parameter takes. All of it comes from the classes of the KubeJS release each version ships, pinned per branch. VSCodium's own JavaScript support still handles syntax and ProbeJS types. | | ZenScript (`scripts/**/*.zs`) | Syntax, preprocessors, bracket handlers, imports and members against CraftTweaker's documentation. | Every diagnostic has a stable code (`PL2010`, `PL7020`, …). [diagnostics.md](diagnostics.md) lists them all. In the editor, checks use what you see, saved or not: a KubeJS script is checked against the other open scripts as they are in the editor. Editing a script re-checks the other open scripts that read it once you pause typing. A file added, removed or changed on disk (a texture, a script, `pack.json`, `pw.lock`, a mod jar) re-checks the open files that use it. ### Minecraft versions Vanilla ids, block states and resource formats are checked for **every release from 1.20 to 26.3** (and the 26.2 and 26.3 patch releases). Releases that share registries and pack formats share one dataset, so 26.1 and 26.1.1 are checked with 26.1.2's data. A release newer than 26.3 (or a snapshot of one) is checked with 26.3's data and `pack.json` says so (PL3010). Older packs get syntax and structure checks only, with no version-specific errors. The hand-written checks are kept to the releases Reverie ships: core shaders (GLSL and program JSON) for 1.20.1, 1.21.1 and 26.1.x, plus 26.2 and 26.3. Shaders on other releases are not checked. KubeJS is checked for 6 (1.20.1), 7 (1.21 and 1.21.1) and 8 (26.1 to 26.1.2). CraftTweaker is checked for 14 (1.20.1) and 21 (1.21.1). ### Where the version comes from PackLSP finds the pack root (the nearest folder with `pack.json`, or a pwf:31 variant folder, or `pack.mcmeta` for a standalone resource or data pack). A pwf:31 variant has no `pack.json`: it is a folder whose name is a variant key, `-[-]`, of the project `manifest.json` one folder up. PackLSP then takes the version and loader from the first of these that has them: 1. `.packlsp.json` in the pack root 2. `pack.json` `versions` (`minecraft` plus a loader key), or a pwf:31 variant's entry in the project manifest (`mc_version`, `loader`) 3. the workspace `manifest.json` one folder up (`mc_version`, `loader`) 4. the `pack_format` in `pack.mcmeta` Installed mods come from the pack's `*.bun.json` sidecars and `pw.lock`. They turn on mod-specific checks such as Sodium's shader rules, KubeJS and CraftTweaker. ### Mod ids: the pack's mod jars Ids outside `minecraft:` are checked when PackLSP can read the pack's mod jars. Without jars, they are not checked. For each jar `pw.lock` pins, PackLSP looks: 1. in Packwand's store, by the lock's sha512 (`~/.local/share/packwand/store`, or under `$XDG_DATA_HOME`) 2. in the pack's own `mods/` folder 3. in the folders you configure, by file name: - `.packlsp.json` `"modJars"` - the `packlsp.modJarDirs` editor setting - `packlsp check --mod-jars ` - `$PACKLSP_MOD_JARS` (separated like `PATH`) A launcher instance folder works: its `minecraft/mods` folder is used. A jar whose size differs from the lock's is not used. From each jar, and the jars nested in it, PackLSP reads: - its `assets/` and `data/` file names - its mod ids (`mods.toml`, `fabric.mod.json`, …) - `sounds.json` - the `en_us.json` translation keys, which also name content a mod registers in code The MCP `pack_info` tool reports how many jars were found. Among the jars, KubeJS addons (jars with a `kubejs.plugins.txt`, such as LootJS, MoreJS or KubeJS Curios) are read like KubeJS itself: their event groups, events, bindings, recipe schemas and the KubeJS classes their mixins change, so scripts using them are checked, hovered and completed too. An addon whose code PackLSP cannot fully follow is left unchecked rather than guessed at. `pack_info` lists what each addon added. If a game that ran the pack has a KubeJS export (`/kubejs export` writes `local/kubejs/export`: in the pack folder, an instance it is linked to with `packwand link`, or a configured jar folder), its registry ids and mods count as existing, so content mods register in code is not flagged. The export may be older than the pack, so nothing is reported from it alone; the status bar shows its age. In the editor, the jars are read in the background, after the pack's own files: the first diagnostics appear at once, and mod-id checks (PL2012, PL2013) follow when the jars are read. The editor shows the progress ("Reading mod jars 12/179"). Jars added, removed or replaced are read again; for folders outside the workspace (`modJarDirs`, `.packlsp.json` `"modJars"`), PackLSP notices within 30 seconds. ```jsonc // .packlsp.json { "modJars": ["~/.local/share/PrismLauncher/instances/MyPack"] } ``` On CI and on other people's machines the store is usually empty, so nothing is found until a folder is configured. Once Packwand keeps the jars it downloads in its store, the lookup by hash will work everywhere without setup. ## Quick fixes Press `Ctrl+.` on a diagnostic, or use the lightbulb. Fixes include: - unknown id → the suggested id - quote or unquote a value; old `{"item": …}` ingredients → `"id"` - move a file to the folder this version reads - clamp a config value to its range; pick the closest allowed value - remove a trailing comma or byte order mark - fix `#version`, add 26.3's `#extension` line, swap `#moj_import`/`#include`, rename a renamed core shader - add a shader to `sodium.ignored_shaders` in `pack.mcmeta` - `%d` → `%s` in lang strings; drop `.ogg` from sound names - KubeJS `ForgeEvents` → `NativeEvents`, `java(…)` → `Java.loadClass(…)` - fix a misspelled `server.properties` key ## Navigation and editor help - **Go to definition** on an id opens the pack file that defines it: the model, texture, recipe or tag file, the sound's key in `sounds.json`, or the `event.create('…')` line in a KubeJS startup script. Ids that only come from mod jars have no file to open. - **Find references** lists every place in the pack that names the id: JSON id slots, tag values, and KubeJS strings. After a pack opens, PackLSP reads its files in the background ("Indexing references" in the status bar); until that finishes, the list may be short. - **Rename** works on ids the pack defines in its own files, outside `minecraft:`. It rewrites every use, and moves the defining file when the id comes from its path (`assets/example/models/item/base.json` for `example:item/base`), along with its `.mcmeta`. Editors that cannot rename files through a language server get the edits only and a message naming the file to move. Rename waits until the references are indexed. - **Workspace symbols** (`Ctrl+T`) finds ids the pack defines and the top-level declarations of its KubeJS scripts. - In KubeJS scripts: - **signature help** shows the Java overloads of a method, or a recipe type's argument lists, while you type the arguments; - **inlay hints** show inferred Java types after `const x = …` and the recipe key each argument fills (`result:`, `pattern:`); - **semantic highlighting** marks event groups, events, global bindings, Java methods and properties. With an editor that pulls diagnostics and can be told to pull again (VSCodium does), PackLSP answers pulls instead of publishing, so every diagnostic shows once. Packwand lock findings for files that are not open are still published. ## Settings | Setting | What it does | |---|---| | `packlsp.diagnostics.severity` | Per-code severity: `{"PL2003": "off", "PL5004": "hint"}`. Values are `error`, `warning`, `info`, `hint` and `off`. | | `packlsp.packwandPath` | Packwand binary for the lock check on save. Default: `packwand` on `PATH`. | | `packlsp.server.path` | Use another `packlsp` binary instead of the bundled one. | | `packlsp.trace.server` | Log LSP traffic to the PackLSP output channel. | | `packlsp.format.enable` | Format KubeJS scripts and ZenScript files with PackFMT. Default: on. | | `packlsp.format.styleDiagnostics` | Show PackFMT's style checks (`PF0101`, `PF1001`, …). Default: on. | ### Per-pack settings: `.packlsp.json` A `.packlsp.json` in the pack root overrides the detected version and sets severities for everyone working on the pack. Comments are allowed. ```jsonc { // Same shape as pack.json "versions". "versions": { "minecraft": "1.21.1", "neoforge": "21.1.77" }, // The editor setting wins over these when both name a code. "severity": { "PL5004": "off" }, // PackFMT style overrides; see packfmt.md. "format": { "ignore": ["kubejs/server_scripts/vendored/**"] } } ``` ## Packwand lock check When you save `pack.json`, a sidecar or `pw.lock`, PackLSP runs `packwand -C lock check --json` if Packwand is available. Hash mismatches and drift then show as `PL3011` on the affected files. PackLSP never edits `pw.lock`; Packwand owns it. ## Command line ```sh packlsp check # path:line:column: severity code: message packlsp check --json # one JSON object, Packwand's issue shape packlsp check --mod-jars # also read mod jars from (repeatable) packlsp check --style # also report PackFMT's style checks (PF0101, PF1001, …) ``` `check` exits with status 1 when there are errors, so it can gate CI. Lines are 1-based and columns are 1-based UTF-8 bytes. ```sh packlsp fmt ... # format KubeJS scripts and ZenScript files in place packlsp fmt --check ... # report files that would change; exit 1 if any ``` `fmt` is PackFMT, the Reverie formatter. [packfmt.md](packfmt.md) describes the style, the settings, the comments that skip code, and the JSON report that `packwand fmt` reads. ## Agents (MCP) `packlsp mcp` exposes the same checks, fixes, hover, completion and vanilla lookups to agents. The extension registers it with VSCodium automatically. For Claude Code, run **PackLSP: Copy MCP Setup Command** and paste the command into a terminal. It points at a stable copy of the server that the extension refreshes on update. See [mcp.md](mcp.md). ## Commands - **PackLSP: Show Pack Info**: detected root, version, loader, and where they came from. - **PackLSP: Restart Server** - **PackLSP: Copy MCP Setup Command** ## Troubleshooting - **Status bar says "(syntax only)".** The pack's version is not one of the five above, or PackLSP could not find one. Add a `.packlsp.json` with `versions`. - **Duplicate JSON diagnostics.** VSCodium's built-in JSON validation also reports syntax errors. PackLSP's are the ones with `PL` codes. - **An id from a mod is flagged.** `minecraft:` ids are checked against vanilla, the pack's own files and its mod jars. Mod ids are only checked when PackLSP found the jars (see above). PL2013 can still flag content a mod registers in code without any file or translation key; turn it `off` in `.packlsp.json` if a mod does that a lot. - **Mod ids are never checked.** PackLSP found no jars: check `modJars` in `pack_info`, then point `modJars` at an instance or mods folder.