Monorepo for PackTools - tooling for Minecraft Modpacks!
packtools apps packlsp docs guide.md
16 kB
Markdown

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-<version>.vsix or packlsp-win32-x64-<version>.vsix.
  2. Install it:
    codium --install-extension packlsp-linux-x64-<version>.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 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-<version>/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.<namespace>.<type>; 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 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, <mc_version>-<loader>[-<tail>], 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 <dir>
    • $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.

// .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
  • 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.

{
  // 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 <pack> 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 #

packlsp check <file-or-folder>          # path:line:column: severity code: message
packlsp check <file-or-folder> --json   # one JSON object, Packwand's issue shape
packlsp check <path> --mod-jars <dir>   # also read mod jars from <dir> (repeatable)
packlsp check <path> --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.

packlsp fmt <file-or-folder>...          # format KubeJS scripts and ZenScript files in place
packlsp fmt --check <file-or-folder>...  # report files that would change; exit 1 if any

fmt is PackFMT, the Reverie formatter. 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.

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.