Skip to main content

Setup development environment

Pre-requisites​

The whole Lasers-Enigma family is set up as a single workspace: you clone one master repo (le-workspace) and a bootstrap script clones the six sibling repos side by side, enables the git hooks, and syncs the shared AI tooling. Before running it, make sure you have:

  • Git — download and install. On Windows, this also installs Git Bash, the POSIX shell required to run bootstrap.sh (PowerShell and cmd cannot run it; WSL works too).

  • A GitLab SSH key (required) — bootstrap.sh clones over SSH ([email protected]:â€Ļ), so an SSH key is mandatory, not just convenient. Generate one and add the public key to GitLab → Preferences → SSH Keys. On the first clone, accept the gitlab.com host key when prompted.

  • The toolchain(s) for the repos you'll actually work on — install only what you need:

    RepoToolchain
    lasersenigma (the plugin)IntelliJ + Java Temurin 21 (IntelliJ install guide)
    le-play-server-utils (LEPSU)Java Temurin 21 + Maven
    le-room-organizerGo â‰Ĩ 1.23 + Node â‰Ĩ 20
    le-agent, le-mcpPython + uv

    Each child repo's AGENTS.md / README documents its own toolchain in full. Node â‰Ĩ 20 is also used by the GitLab MCP server (launched via npx).

Clone the workspace​

The whole family is bootstrapped from the le-workspace master repo. From a parent directory of your choice:

git clone [email protected]:lasersenigma/le-workspace.git
cd le-workspace
./bootstrap.sh # Windows: run from Git Bash, not PowerShell

bootstrap.sh clones the six sibling repos side by side under le-workspace/, enables the versioned commit-hygiene hooks in each (core.hooksPath .githooks + the per-repo hooks.requireIssueRef policy — false for le-agent / le-mcp, true elsewhere), and runs the initial shared-tooling sync. It is safe to re-run: an already-cloned repo is left untouched (only hooks + sync are re-applied).

DirectoryRoleStack
lasersenigma/The pluginJava / Gradle / Paper
wiki/Developer & player documentation — the source of truth under Develop/Markdown
le-play-server-utils/LEPSU: play-server Paper plugin (portals, puzzle rooms, reviews, hints, lobby)Java / Maven / Paper
le-room-organizer/Standalone tool to reorganize rooms across portalsGo + React
le-mcp/MCP server exposing the wiki docs as LLM toolsPython / uv
le-agent/LangGraph agent (Gemini + le-mcp) for the LEPSU /puzzle doc commandPython / uv

The six repos are gitignored inside le-workspace/ (each is a standalone clone with its own remote — not a git submodule). Commit and push from inside the relevant directory.

bootstrap.sh assumes you have access to all six repos. With partial access, clone the ones you can individually (same SSH URLs) and run ./sync-tooling.sh <child> for each.

After cloning, each repo is on its default main branch (lasersenigma targets Minecraft 1.19.4 → 1.21.x — the only development branch).

Open the plugin in IntelliJ​

Open the lasersenigma/ directory (under le-workspace/) as the IntelliJ project. The sections below (setupDevEnvironment, the run/ servers, the run configurations) all operate inside it.

Commit-hygiene git hooks (already enabled by bootstrap)​

bootstrap.sh has already enabled the versioned commit-msg hook in every clone — it strips any AI-credit trailer, validates the Conventional Commits subject and its (#<issue>) reference, and blocks GitLab auto-close keywords. You only need to act if you cloned a repo outside bootstrap.sh; then enable it manually, once per clone:

git config core.hooksPath .githooks
## le-agent / le-mcp only require the issue reference when the work tracks an issue:
git config hooks.requireIssueRef false

This works from any shell or IDE — PowerShell 7, cmd, Git-Bash, IntelliJ, VS Code — because git runs the hook through its own bundled sh, never your shell. IntelliJ's Darkyen Time Tracker keeps working: a prepare-commit-msg shim chains to its local hook. In a genuine emergency you can skip the checks with git commit --no-verify.

Editing the shared AI tooling​

The .claude/ commands and skills are synced (tracked) from le-workspace into every child and carry an AUTO-SYNCED from le-workspace header — never hand-edit a synced file inside a child. Edit the canonical copy in le-workspace/.claude/ and re-run ./sync-tooling.sh <child> (or --all). sync-manifest.txt lists what is synced, by type (command / skill / hook). Fragments (.claude/fragments/) are a build input: edited once canonically and inlined into the commands at sync time so each shipped command is self-contained (the runtime doesn't transclude links) — they are not shipped as standalone files. The hook type distributes the canonical commit-msg git hook verbatim into every child except the wiki (its own lighter variant; le-workspace's pre-commit cross-repo guard is repo-local, not synced). The le-workspace .githooks/pre-commit guard whitelists .claude/{commands,fragments,skills}/. Run ./sync-tooling.sh --check --all before pushing a tooling change to catch any drift. See AI Prompts and Skills for the full tooling reference.

Check that the project compiles​

Run Gradle (the wrapper is included in the project, no need to install Gradle):

./gradlew clean build

You can also do it from IntelliJ's Gradle tool window.

The ./gradlew examples on this page assume a POSIX shell (Git Bash on Windows). In Windows PowerShell or cmd, use the batch wrapper instead: .\gradlew.bat clean build.

Prepare local game servers​

The project uses local Paper game servers to test the plugin. The standard setup creates 4 servers under run/:

DirectoryPurposeMC version
run/le-play/Mirror of the play server (untouched)Latest (from play server)
run/latest/Latest MC version testingLatest stable
run/1.20.4/MC 1.20.4 testing1.20.4
run/1.19.4/Oldest supported version testing1.19.4

This method requires access to the Skytale Pterodactyl panel. If you don't have access, ask it on discord or see Manual setup instead.

1. Configure API tokens​

Some Gradle tasks require API tokens. These are stored in your personal Gradle properties file, outside the project directory, so they are never committed to git.

OSFile path
WindowsC:\Users\<your-username>\.gradle\gradle.properties
macOS/Users/<your-username>/.gradle/gradle.properties
Linux/home/<your-username>/.gradle/gradle.properties

Create the file if it doesn't exist, then add:

## Pterodactyl panel configuration (required for automated server setup)
## Generate API key from: Account → API Credentials in the Pterodactyl panel
## Create at: https://panel.skytale.fr/account/api
pterodactylPanelUrl=https://panel.skytale.fr/
pterodactylApiKey=ptlc_xxxxxxxxxxxxxxxxxxxx
pterodactylServerId=5bcf33bc

## Remote debug host (optional, for remote debug run config generation)
## Find (in address field in the sidebar) at: https://panel.skytale.fr/server/5bcf33bc
remoteDebugHost=<server-ip-address>
## remoteDebugPort=25566 (optional, defaults to 25566)

Fill the 2 fields pterodactylApiKey and remoteDebugHost with the appropriate values (instructions are in comments above).

âš ī¸ Never put tokens in the project's gradle.properties — it would be committed to the repository. These properties are shared across all Gradle projects on your machine (e.g. Lasers-Enigma, LEPSU).

If you don't have Pterodactyl access yet, contact us to get an account and the appropriate permissions. Then go to Account → API Credentials and create a new API key.

GitLab personal access token (for the GitLab MCP server)​

GitLab operations performed by the AI tooling (read issues and comments, create/edit issues, fetch Merge Request review comments) go through the GitLab MCP server (@zereight/mcp-gitlab). The server is already declared in the repo — .mcp.json for Claude Code — so you only need to provide a token through the GITLAB_PERSONAL_ACCESS_TOKEN environment variable (this is independent of Pterodactyl access).

  1. Create a token with scope api at https://gitlab.com/-/user_settings/personal_access_tokens (read_api is enough for read-only use, but api is required to create or edit issues).

  2. Expose it as an environment variable:

    OSHow
    Windowssetx GITLAB_PERSONAL_ACCESS_TOKEN "glpat-xxxxxxxxxxxxxxxxxxxx" (new terminals only), or add it via System Properties → Environment Variables
    macOS / Linuxadd export GITLAB_PERSONAL_ACCESS_TOKEN="glpat-xxxxxxxxxxxxxxxxxxxx" to your shell profile (~/.zshrc, ~/.bashrc, â€Ļ)
  3. Restart your IDE / terminal so the MCP server picks up the variable.

âš ī¸ The token lives in your environment, never in the repo. .mcp.json only references the variable name, it doesn't contain the secret.

â„šī¸ GITLAB_TOOLSETS requirement — the committed .mcp.json sets the GITLAB_TOOLSETS env var on the MCP server (merge_requests,issues,repositories,branches,projects,labels,groups,users,ci,pipelines,variables). The variables and pipelines toolsets are required by the check-server-status skill and the release workflow (reading CI/CD variables, triggering/monitoring pipelines). An explicit GITLAB_TOOLSETS list replaces the server's defaults, and toolsets activated dynamically via discover_tools are not visible to an already-running agent session — so keep the toolsets in the static config; if you maintain a custom MCP setup, replicate this variable.

2. Configure plugin deployment​

Copy core/dev-example.properties to core/dev.properties and set:

## Automatically copy the built JAR to servers plugins on build
SKIP_COPY_JAR_TO_SERVER=false
## Comma-separated list of plugins directories (absolute or relative to project root)
SERVER_PLUGINS_DIRECTORIES=run/le-play/plugins,run/latest/plugins,run/1.20.4/plugins,run/1.19.4/plugins

This tells Gradle which servers to target for all development tasks.

âš ī¸ Keep defaults settings for setupDevEnvironment to work correctly. The run/ directories are automatically created and managed by the setup task, so you don't need to create them manually.

3. Run the setup​

A single command sets up everything:

./gradlew setupDevEnvironment

Or use the ⚙ Setup Dev Environment run configuration in IntelliJ.

This takes about 20 minutes (mostly downloading and server starts). It runs these tasks in order:

StepTaskDescription
1downloadServerArchiveDownloads a full server archive from the Pterodactyl panel
2cleanRunServersDeletes any existing run/ directories
3extractServerArchivesExtracts the archive into server directories (run/1.19.4/ is created empty)
4updatePaperJarsDownloads the latest Paper JAR for each server's MC version (except le-play)
5bootstrapEmptyServersLaunches empty servers once to generate config files (run/1.19.4/)
6cleanupServerRemoves unnecessary files from Pterodactyl-extracted servers (schematics, logs, temp files)
7patchServerConfigsPatches spigot.yml and config/paper-global.yml for local development. See details below
8configureLog4jCreates log4j2.xml with trace-level logging for the plugin
9updateLepsuDeploys le-play-server-utils: legacy version to 1.20.4, latest version to latest
10updateNoteBlockApiDeploys the NoteBlockAPI plugin to all servers (except le-play)
11updateSparkDownloads and deploys the latest spark profiler to all servers (except le-play). Can be skipped with -PskipUpdateSpark
12buildAndDeployBranchesBuilds LasersEnigma once and deploys the JAR to all servers (latest, 1.20.4, 1.19.4). le-play is left untouched
13verifyServersLaunches each server to verify it starts without errors

4. Generate remote debug config (optional)​

If you have set remoteDebugHost in ~/.gradle/gradle.properties:

./gradlew generateRemoteDebugConfig

This generates the 🔌 remote debug LE Play run configuration (git-ignored since it contains the server IP).

5. You're done!​

You can now use the IntelliJ run configurations to build and start any server. See Run configurations.

Manual setup (without Pterodactyl access)​

If you don't have access to the Pterodactyl panel (e.g. external contributors), you can set up servers from scratch. You don't need all four — pick the ones relevant to your work.

1. Create server directories​

## Latest Minecraft version
mkdir -p run/latest

## Older Minecraft versions (optional, to test 1.19.4-1.20.4 compatibility)
mkdir -p run/1.20.4
mkdir -p run/1.19.4

On Windows (PowerShell): New-Item -ItemType Directory -Path run/latest -Force

2. Configure plugin deployment​

Copy core/dev-example.properties to core/dev.properties and set:

SKIP_COPY_JAR_TO_SERVER=false
SERVER_PLUGINS_DIRECTORIES=run/latest/plugins

Adjust SERVER_PLUGINS_DIRECTORIES to match the servers you created (comma-separated for multiple).

3. Download Paper​

./gradlew updatePaperJars

This downloads the latest Paper build matching each server's MC version. For new empty directories, it detects the version from the directory name (e.g. 1.19.4) or defaults to the latest stable MC version.

Alternatively, download Paper manually from papermc.io and place the JAR as server.jar in your server directory.

4. Accept the EULA and first launch​

Each server needs a first launch to generate its configuration files:

  1. Create an eula.txt file in each server directory with the content eula=true
  2. Start each server once:
cd run/latest
java -jar server.jar nogui

Wait until you see Done! in the logs, then type stop in the console to shut it down. Repeat for each server directory.

5. Apply debug-friendly configuration​

./gradlew patchServerConfigs
./gradlew configureLog4j
  • patchServerConfigs sets max-tick-time to -1 in spigot.yml to prevent crashes when paused on a debugger breakpoint and sets proxy-protocol to false in config/paper-global.yml so the local server accepts direct connections without a proxy.
  • configureLog4j creates a log4j2.xml with trace-level logging for eu.lasersenigma and NonEuclideanSpaceBuilder.

6. You're done!​

You can now use the â–ļ latest run configuration in IntelliJ to build and start the server. See Run configurations.

Run configurations​

The project includes shared IntelliJ run configurations in the .run/ directory, automatically detected by IntelliJ:

NameTypeDescription
⚙ Setup Dev EnvironmentGradleRuns the full setupDevEnvironment task
â–ļ le-playJAR ApplicationBuilds the plugin, then starts run/le-play/server.jar
â–ļ latestJAR ApplicationBuilds the plugin, then starts run/latest/server.jar
â–ļ 1.20.4JAR ApplicationBuilds the plugin, then starts run/1.20.4/server.jar
â–ļ 1.19.4JAR ApplicationBuilds the plugin, then starts run/1.19.4/server.jar
🔌 remote debug LE PlayRemote DebugConnects to the play server for remote debugging (generated, git-ignored)

All server run configurations include:

  • A Before launch Gradle build task (builds and deploys the plugin automatically)
  • -Dlog4j.configurationFile=log4j2.xml for trace-level plugin logging
  • -Xms3G -Xmx3G memory allocation
  • nogui mode

Note: By default, all servers use the same port (25565). To run multiple servers simultaneously, change the server-port value in each server's server.properties file to a different port.

To create a custom run configuration manually, see the Debug documentation.

Advanced​

Re-setup a single server​

To re-run the full setup for a specific server only (without touching the others), use the -PserverFilter parameter:

./gradlew setupDevEnvironment "-PserverFilter=le-play"

Only the targeted server is deleted and re-extracted — other servers are left untouched. The filter supports comma-separated values (e.g. -PserverFilter=latest,1.20.4).

The filter also works with individual tasks:

./gradlew verifyServers "-PserverFilter=latest"
./gradlew updatePaperJars "-PserverFilter=1.19.4,1.20.4"

On Windows PowerShell, -P must be quoted: "-PserverFilter=1.20.4"

Run individual setup tasks​

Each task in the setupDevEnvironment chain can be run independently. This is useful when you need to re-apply a specific step without running the full setup:

./gradlew updatePaperJars # Re-download Paper JARs
./gradlew cleanupServer # Remove unnecessary files from extracted servers
./gradlew patchServerConfigs # Re-patch spigot.yml + paper-global.yml
./gradlew patchSpigotYml # Re-patch spigot.yml only
./gradlew patchProxyProtocol # Re-patch proxy-protocol in paper-global.yml (target value: -PproxyProtocol, default false)
./gradlew configureLog4j # Re-generate log4j2.xml
./gradlew updateLepsu # Re-deploy LEPSU
./gradlew updateNoteBlockApi # Re-deploy NoteBlockAPI
./gradlew updateSpark # Re-deploy spark profiler
./gradlew buildAndDeployBranches # Re-build and deploy LasersEnigma from branches
./gradlew verifyServers # Re-verify all servers start correctly

Server pack generation​

Additional tasks are available for generating a distributable server pack from the le-play server:

./gradlew cleanupServer "-PserverFilter=le-play" # Remove unnecessary files
./gradlew sanitizeServer "-PserverFilter=le-play" # Remove sensitive data (API keys, SSH keys, webhooks)
./gradlew createServerPackArchive # Create a .tar.gz archive of le-play
./gradlew generateServerPack # Full pipeline: setupDevEnvironment + sanitize + archive

generateServerPack is the single entry point that chains everything: it runs setupDevEnvironment (filtered to le-play, with spigot.yml and log4j patches skipped), then sanitizeServer, then createServerPackArchive.

Optional flags​

The following flags can be passed to setupDevEnvironment (or to individual tasks where relevant):

PropertyEffect
-PskipUpdateSparkSkips the spark profiler download and deployment
-PskipPatchSpigotYmlSkips the patchSpigotYml step inside patchServerConfigs (only patchProxyProtocol runs)
-PskipConfigureLog4jSkips the configureLog4j task entirely
-PproxyProtocol=true|falseTarget value for proxy-protocol in config/paper-global.yml (used by patchProxyProtocol, default false)

Example:

./gradlew setupDevEnvironment "-PskipUpdateSpark"

On Windows PowerShell, -P must be quoted: "-PskipUpdateSpark"

Plugin deployment configuration​

The core/dev.properties file controls which servers are targeted by build deployment and development tasks:

## Automatically copy the built JAR to server plugins on build
SKIP_COPY_JAR_TO_SERVER=false

## Comma-separated list of plugins directories (absolute or relative to project root)
SERVER_PLUGINS_DIRECTORIES=run/le-play/plugins,run/latest/plugins,run/1.20.4/plugins,run/1.19.4/plugins

You can also point to external servers:

SERVER_PLUGINS_DIRECTORIES=D:\\DEV\\Paper-1.21\\plugins

Note: setupDevEnvironment only operates on servers inside the project's run/ directory. External servers are used by other tasks (copyToServer, updatePaperJars, etc.) but are excluded from the automated setup.

Note: The legacy property SERVER_PLUGINS_DIRECTORY (singular) is still supported for backward compatibility and is merged with the list above.

How buildAndDeployBranches works​

buildAndDeployBranches builds LasersEnigma once (from the current working tree) and deploys the JAR to every configured server except the le-play production mirror. Since the plugin now supports every targeted Minecraft version from a single branch, there is no per-branch checkout anymore.

Verify the GitLab MCP server​

The GITLAB_PERSONAL_ACCESS_TOKEN environment variable is consumed by the GitLab MCP server, used by the AI tooling (the fix-mr-comments workflow, the create-issue / edit-issue / fetch-gitlab-issue / fetch-mr-comments skills) to talk to GitLab.

To verify it works, ask your assistant to run the MCP server's health_check and whoami tools — a healthy response reports authenticated: true and your GitLab username. If authentication fails, check that the variable is set (and that you restarted the IDE / terminal after setting it) and that the token has the api scope.

Pitfall: connection times out on Windows (npx startup)​

On Windows — especially with nvm4w and Node â‰Ĩ 24 — the server can fail to start with MCP server 'gitlab' connection timed out after 30000ms, even though the token is valid. The committed config launches the server on demand via npx -y @zereight/mcp-gitlab; in this setup the per-invocation resolution of npx (npm-managed .cmd/extensionless shims, stricter Node 24 process spawning) does not complete within the client's 30 s startup window. Warming the npx cache is not enough.

Fix: install the server globally so npx resolves it instantly instead of resolving it on every launch, then restart Claude Code / your IDE:

npm i -g @zereight/mcp-gitlab

An elevated (admin) shell is not required. This keeps the committed .mcp.json unchanged and cross-platform — it is a local-machine fix only.

AI Assistant Configuration​

The project's canonical rule source is AGENTS.md at the project root. It defines architecture, conventions, layer structure, translation workflow, commit naming, and every other non-obvious rule.

The assistant supported today is Claude Code, whose thin entry-point CLAUDE.md at the project root points to AGENTS.md. To wire another provider, see Re-adding another AI provider.

Commands and skills​

In addition to the custom instructions, the project provides:

  • Commands under .claude/commands/ for multi-step workflows (dev-issue, review, fix-mr-comments, release, design, deliver, finalize-series, epic) — canonical source, synced (tracked) from the le-workspace root. The fragments (reusable primitives the commands are built from) are a build input, inlined into the commands at sync time so each shipped command is self-contained — they are not shipped as standalone files (see Editing the shared AI tooling).
  • Claude Code skills under .claude/skills/ for atomic auto-triggered operations (translation staging/flush, version bump, branch+commit validation, CHANGELOG entry, log analysis, etc.).

See AI Prompts and Skills for the full reference and usage instructions.