Implement Prepare Environment Script
This skill produces a single executable script that performs the one-time per-build setup that precedes a conformance-test run, following a consistent, language-agnostic pattern.
The reference implementation is assets/prepare_environment_java.sh and assets/prepare_environment_python.sh. Read it first — every script you produce must be a faithful translation of that pattern into the target language's tooling and the user's shell environment.
What a prepare-environment script is for
prepare_environment_<lang>
is an
optional sibling of
run_conformance_tests_<lang>
. It runs
immediately before the conformance script and exists to:
- Stage the build into a working folder (the same one the conformance script will use).
- Pre-warm the dependency cache / build artifacts so the conformance script can start cold without re-downloading or re-compiling anything.
When this script exists for a project, the corresponding conformance script's own dependency-install phase degrades to "activate only" (see
implement-conformance-testing-script/SKILL.md
→ "Skipping setup when
exists"). When it doesn't exist, the conformance script does the setup inline.
Why this script exists at all (the structural reason)
The conformance test runner is invoked
once per functional spec by the renderer — not once per render. Each functional spec in a module has its own
conformance_tests/<module>/<spec>/
folder, and after the renderer finishes generating code for a new spec, it runs the conformance tests of
every previous spec in the same module to detect regressions. For a module with N functional specs, the conformance script is invoked roughly N times on every render.
Without a prepare script, every one of those N invocations does the full dependency install (Python venv +
, full Maven dependency tree,
,
, ...) from scratch. That cost — paid N times per render — dominates the wall-clock time of rendering a non-trivial project.
prepare_environment_<lang>
exists to
amortize that cost to one install per render:
- Prepare runs once, installs everything, populates the project-local isolation location inside (, , , , , ).
- The conformance script then runs N times, each invocation in its activate-only variant, attaching to the already-populated working folder and skipping the install step entirely.
- Net effect: install cost goes from to
1 × install-cost + N × cheap-attach-cost
.
This is the whole reason the prepare-then-conformance split exists. If a project has so few functional specs that the install overhead is negligible, generating a prepare script is wasted effort — the install-inline variant of the conformance script is fine. If a project has many specs (or expensive dependencies, GPU builds, browser binaries, etc.), prepare is mandatory in practice. The user decides per project; this skill is the tool to execute that decision.
is conformance-only — NOT for unit tests
Common and costly mistake: assuming
prepare_environment_<lang>
is a generic "warm up the environment for all the testing scripts" step that the unit-test runner also depends on. It is not.
prepare_environment_<lang>
exists
solely to set up the working folder that
run_conformance_tests_<lang>
then attaches to (via the activate-only variant of the conformance script). It has
no relationship to
:
- The unit-test runner is fully self-contained. It does its own staging into its own working folder, and it installs its own dependencies inline (
pip install -r requirements.txt
, , , , ...) every run.
- The unit-test runner never reads from the working folder populates. The two scripts use independent working folders even when they happen to share a naming convention — each script wipes and rebuilds its own copy.
- The unit-test runner does not require to have run first. Users and CI systems routinely run unit tests as a smoke check without ever invoking conformance, and that must keep working.
- There is no activate-only variant of the unit-test runner.
implement-unit-testing-script
emits a self-contained script every time — the two-variant pattern is exclusive to the conformance runner.
When authoring
prepare_environment_<lang>
, scope it strictly to what the
conformance script needs. Do not bake in dependency installs the unit-test runner needs but conformance doesn't; do not stage files the unit-test runner reads; do not assume the unit-test runner will be the one consuming what you produce. If you find yourself reaching for those,
stop — the right answer is to leave
alone and let the unit-test runner handle its own dependencies inline.
How prepare-environment scripts differ from the others
This script shares most of its structure with its two siblings (
and
run_conformance_tests_<lang>
) but with these differences:
- One positional argument: . No conformance tests folder — that's the conformance script's input, not this one's.
- No test execution step. This script only stages and installs/builds. It never runs unit tests or conformance tests.
- No "no tests discovered" guard. Same reason — no tests are run here.
- Side effects must be visible to the conformance script. Anything this script does (working-folder name, dependency isolation location, installed artifacts) must match exactly what the conformance script expects to find. See Coordination contract.
Everything else — toolchain check, build staging, dependency isolation, exit codes — is the same.
Pick the Shell First
Before writing anything, decide which shell flavor the script must target — it depends on the host machine running this skill, not on the language. Detect the host OS proactively; do not default to Bash.
| Host OS | Emit | How to detect |
|---|
| macOS | | returns |
| Linux (incl. WSL, CI runners) | | returns |
| Native Windows (PowerShell, cmd) | | env var contains , or returns / / (Git Bash / MSYS2 / Cygwin shells on Windows) |
Run
uname -s 2>/dev/null || echo "$OS"
if unsure — don't ask the user before checking.
If the project will be used on
both macOS/Linux and Windows by different team members or CI runners, generate
both and
versions of this script — they're mechanical translations of each other, share exit codes and isolation paths, and the orchestrator (or the user's CI) picks the right one at runtime. The corresponding
run_conformance_tests_<lang>
script must be produced in matching pairs too.
If you genuinely can't tell (e.g. running in a sandbox with no shell access), ask the user — but only after the detection above failed.
The same pattern applies to both shell flavors. Only the syntax changes.
The Pattern
Every prepare-environment script must implement these steps in this order:
- Toolchain check. Verify that the required language runtime / build tool (and the required version, if any) is installed. If not, print an error and exit with code .
- Argument validation. Require one positional argument: . If missing, print usage and exit with code .
- Working directory setup. Define a working folder at — identical to the path the conformance script will use. Wipe it ( /
Remove-Item -Recurse -Force
) and recreate it. This folder — and only this folder — is where every subsequent write must land.
- Copy the build. Recursively copy everything from () into the working folder. After this step the source folder () is treated as read-only for the rest of the script.
- Enter the working directory. / into . If that fails, exit with code . All remaining steps run from inside the working folder; they must never write back to .
- Install dependencies / pre-build artifacts into an isolated environment inside . Set up a per-working-folder dependency location (a Python venv at , a local , a project-scoped Maven repo at , etc.) and install/resolve all dependencies into it. Where the language requires building before tests can run (Java, Rust, Go), also produce the build artifact and place it where the conformance script can find it — inside the working folder, never inside and never in the user's home directory. Never install into the source build folder (), the user's global cache (, system-wide , , , ...), or anywhere outside . If any sub-step fails, exit with code (do not propagate Maven/pip/npm exit codes — a half-prepared environment is itself an unrecoverable error). See Dependency isolation for per-language specifics.
That's it. There is no step 7. Once dependencies are installed and (where applicable) the build artifact is in the local repo, this script's job is done.
The build folder is read-only — hard rule
The source build folder passed in as
is
input only. Prepare reads from it once in step 4 to populate the working folder, and after that the script must never:
- install dependencies into it (no inside , no inside , no writing into , no Cargo build artifacts ending up under ),
- write a virtualenv / / / / / directory inside it,
- pre-build into it (every compile output — , , , native binaries, generated sources — lives inside ),
- create logs, caches, or temp files inside it.
The build folder is shared with the renderer (
by default) and with the conformance script, which staging-checks it via
if [ ! -d ".tmp/<lang>_$1" ]
and expects
itself to look the same as it did right after rendering. Writing into
corrupts the renderer's view of "what was generated", churns git status if the project commits
, and (if the conformance script ever does an
during its own setup) silently destroys work prepare did.
The whole point of staging via
is so the source build folder stays a clean, reproducible artifact of the render. Every dependency, every compiled class, every binary, every cache must land inside the working folder — because that is exactly what the conformance script's activate-only variant attaches to.
If you find yourself about to issue any command whose
is
, or whose target path starts with
,
stop. Either move the operation into
, or you're doing something the script must not do.
Coordination contract
This is the most important part of the skill. A prepare-environment script that doesn't agree with its conformance sibling on where it puts things is worse than no prepare script at all — it costs time and creates the appearance of a warm environment without actually warming the right one.
The two scripts must agree on:
| What | Why it matters |
|---|
| Working folder path () | The conformance script s into this folder. If prepare uses a different name, the conformance script sees an empty / freshly-staged folder and re-does all the work. |
| Dependency isolation location (, , , , , …) — relative to the working folder | If prepare populates and conformance reads from , the warm cache is invisible. Always use the project-local path inside the working folder, in both scripts. |
| Build artifact location (Java/Rust/Go) | The conformance test project depends on the build's artifact. It must be findable at the exact coordinates the conformance script expects (e.g. installed into the same project-local for Java). |
| Toolchain version | If prepare runs under Java 21 and conformance runs under Java 17, classfile incompatibilities will surface at test time, not prepare time. Both scripts should perform the same toolchain check. |
When in doubt, read the conformance script first and mirror its assumptions exactly.
Conventions
Shared across both shell flavors:
- Exit codes:
- — unrecoverable: missing argument, missing toolchain, can't enter working folder, dependency install / build failed. Treat all failures as unrecoverable here — there is no "soft" failure mode for prepare.
- — success.
- Working folder naming: where is a short identifier for the language (, , , , , ...). Use the first (and only) argument in the path. All dependency installs, build outputs, caches, and pre-built artifacts live inside this folder. Nothing the script does should touch after step 4.
- Logging: print short progress lines (
"Preparing <lang> build subfolder: ..."
, , "Installing <lang> dependencies into ..."
, "Pre-build completed in X.XX seconds"
) so failures are easy to triage. Wrap noisy "preparing" lines in a check if matching the Java reference.
- Time the dependency setup with (Bash) / (PowerShell) and print a duration line at the end. This is the slowest phase and the whole reason this script exists; the duration tells you whether it's actually saving time.
Dependency isolation
The dependency environment must live
inside so the conformance script can find it and so concurrent runs of other languages / projects can't interfere. Pick the most idiomatic isolation mechanism for the language — and make sure it matches what the conformance script reads from:
| Language | Isolation mechanism | Prepare command (run inside ) |
|---|
| Python | at | python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt
|
| Node.js | local (default) | (preferred) or |
| Java | project-scoped Maven repo at | mvn -Dmaven.repo.local="$(pwd)/.m2" install -DskipTests
(builds and installs the project's own jar into the repo so dependent test projects can resolve it) |
| Go | module cache at | GOMODCACHE="$PWD/.gocache" go mod download && GOMODCACHE="$PWD/.gocache" go build ./...
|
| Rust | cargo home at | CARGO_HOME="$PWD/.cargo" cargo build --tests
(compiles deps + tests in one shot) |
| Flutter | pub cache at | PUB_CACHE="$PWD/.pub-cache" flutter pub get && flutter precache
|
Notes:
- Every path in the install / pre-build command is relative to . That's why the script s into the working folder in step 5 — from that point on, , , , , , , and any compile output (, , , native binaries) all resolve under , never under and never under the user's home directory.
- Java/Rust/Go must compile, not just download. The conformance script will time-out / re-compile from scratch if you only resolve metadata. Use , , (not just / / ).
- Python and Node.js only need to install — they're interpreted/JIT-compiled at test time, so / is sufficient.
- Always pass the isolation flag/env var. without , without , etc., write to the user's home directory instead of . The conformance script will look in the wrong place and the warming was wasted — and the user's home dir gets polluted.
- Treat install failures as . Unlike the conformance script (which propagates the test command's exit code), prepare has no notion of "the user's tests legitimately failed" — every failure here means the environment isn't usable, period.
Bash specifics
- Shebang: .
- File naming:
prepare_environment_<lang>.sh
, placed in .
- Argument: .
- Make it executable:
chmod +x assets/prepare_environment_<lang>.sh
.
- failure check: use the + pattern from the reference. Put the success log line after the failure check, not before — otherwise it lies on failure.
PowerShell specifics
- No shebang. Use a
param([Parameter(Mandatory=$true)][string]$BuildFolder)
block at the top instead.
- File naming:
prepare_environment_<lang>.ps1
, placed in .
- Exit codes: use etc. (PowerShell honors them just like Bash).
- Toolchain check: prefer
Get-Command <tool> -ErrorAction SilentlyContinue
and, where a specific version is needed, parse the tool's output.
- Filesystem: use ,
Remove-Item -Recurse -Force
, New-Item -ItemType Directory
, , . Quote paths to handle spaces.
- No step needed. If execution policy is likely to block the script, mention
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
to the user — don't bake it into the script.
Workflow
- Detect the host OS to pick the script flavor. Run
uname -s 2>/dev/null || echo "$OS"
and apply the rules in Pick the Shell First: / → , /// → . If the project targets both macOS/Linux and Windows (multi-OS team or CI), plan to produce both and files — repeat steps 3–7 for each.
- Confirm the target language, dependency manifest (, / , , , , ...), and — critically — read the corresponding
run_conformance_tests_<lang>
script first if one already exists, so you know what isolation paths and toolchain versions to mirror. Ask if any is unclear.
- Read assets/prepare_environment_java.sh to refresh the exact structure. Note: that reference still has divergences from the contract above (see Anti-Patterns) — follow the contract, not the bugs.
- Translate each of the six pattern steps into the equivalent commands for the target language and shell. The toolchain check and dependency install/build are the language-specific parts; the rest is mechanical translation between Bash and PowerShell syntax.
- Pick the dependency-isolation mechanism from the Dependency isolation table. Verify it matches the path used by the corresponding
run_conformance_tests_<lang>
script.
- Save the new script to the appropriate location (e.g.
test_scripts/prepare_environment_<lang>.sh
/ ). For Bash, it.
- Reconcile the conformance script (see next section). This is mandatory whenever a matching
run_conformance_tests_<lang>
already exists in the project.
- After both scripts are in place, do a paired re-read: open prepare and conformance side by side and confirm they agree on working folder name, isolation path, and toolchain version.
Reconcile the existing conformance script
Adding a
prepare_environment_<lang>
script changes the contract for the corresponding
run_conformance_tests_<lang>
script — anything prepare now handles must be
removed from conformance, otherwise prepare's work is wiped (by re-staging) or duplicated (by re-installing). Run this reconciliation
every time this skill is used.
Step-by-step
- Look for an existing conformance script in the project. Check the conventional locations (
test_scripts/run_conformance_tests_<lang>.sh
/ , or wherever the project's points its conformance-tests-script:
key).
- If it doesn't exist → stop. Nothing to reconcile. The conformance script will be generated later by
implement-conformance-testing-script
, which already knows about the activate-only variant.
- If it does exist → patch it. Identify and remove the steps that prepare now owns:
| Step in conformance | If prepare exists, you must... |
|---|
| Staging block ( + + ) | Remove entirely. Replace with a guard: if [ ! -d ".tmp/<lang>_$1" ]; then echo "Error: build folder missing — run prepare_environment_<lang>.sh first."; exit 69; fi
|
| Dependency install / pre-build (, , , , etc.) | Remove entirely. Replace with a guard that the isolation location exists ( for Python, for Java, for Node, etc.) and exit if missing. |
Activation step (Python source .venv/bin/activate
, Java -Dmaven.repo.local=$(pwd)/.m2
) | Keep. Without it the test command can't see the prepared deps. |
| Test execution + "no tests discovered" guard + exit-code propagation | Keep unchanged. |
- Verify the conformance script's exit codes still follow
implement-conformance-testing-script
— the new "missing prepared environment" guard should exit (unrecoverable invocation error), the no-tests guard should still exit , and the test command's exit code should still propagate.
- Run a smoke check:
prepare_environment_<lang>.sh <build_folder> && run_conformance_tests_<lang>.sh <build_folder> <conformance_tests_folder>
should succeed end-to-end. If conformance fails with "missing prepared environment" right after a successful prepare, the two scripts disagree on either the working-folder path or the isolation location — go back to Coordination contract.
When to skip this reconciliation
- The conformance script doesn't exist yet. Nothing to reconcile.
- The conformance script already shows no signs of inline staging or install. It was previously generated as the activate-only variant — leave it alone.
- The user explicitly asks to keep prepare and conformance independent (e.g. so conformance can run standalone without prepare). Document this clearly in a comment at the top of both scripts and skip the reconciliation. Note that this loses all the speedup prepare was meant to provide.
Anti-Patterns
- (Hard mistake) Don't pre-warm the unit-test runner from this script.
prepare_environment_<lang>
is for the conformance script only. The unit-test runner (implement-unit-testing-script
) is always fully self-contained — it stages, installs, and runs in one shot, every invocation, regardless of whether a prepare script exists. Do not add a unit-test dependency-install step to "to save time"; the unit-test runner will not read what you produce, and the coupling breaks the activate-only contract between prepare and conformance. See is conformance-only — NOT for unit tests above.
- (Hard mistake) Don't install into, build into, or otherwise write to the source build folder (). The build folder passed as is read-only input after step 4. Every install, cache, build artifact (, , , native binaries, generated sources), log, and temp file must land in . This includes never running , , , , or with as their or target; never letting a venv / / / / / directory appear inside ; and never producing a pre-built artifact at any path under . The whole point of staging into is so the build folder remains a clean artifact of the render — writing to it corrupts the renderer's view, churns git status, and can be silently destroyed if the conformance script ever re-stages on its own.
- Don't write to the user's global dependency cache (, system-wide , , , etc.). The conformance script reads from the project-local cache; a global write is invisible to it and pollutes the user's home dir.
- Don't use a different working folder name than the conformance script. They must match exactly. If you change one, change the other.
- Don't run tests — not unit tests, not conformance tests, not smoke tests. Prepare's contract is "set up the environment"; running tests belongs to its siblings.
- Don't propagate non- exit codes from / / . A failed install means the environment isn't usable. Treat every failure as so the orchestrator can tell "prepare failed" apart from "tests failed".
- Don't skip the toolchain check, even when "everyone has it installed" — exit code is what the calling system relies on to detect a missing runtime, and prepare is usually the first script to run, so it's the cheapest place to surface a missing JDK / Python / Node.
- Don't print the "moved to ..." line before the success check. The reference script does this and it lies on failure. Put the log after the guard, or print "attempting to enter ..." instead.
- Don't reuse the source folder in place. Always copy into first; the conformance script relies on this isolation.
- Don't change the exit-code contract between Bash and PowerShell variants. The and for the same language must use identical exit codes for identical failure modes.
- Don't write a cross-shell hybrid (e.g. a that detects PowerShell, or vice versa). Ship one script per shell, named with the appropriate extension.
- Don't forget to time the install. Without the duration log, there's no way to tell whether prepare is actually saving wall-clock time vs. doing the same work the conformance script would have done inline.
- Don't leave an existing
run_conformance_tests_<lang>
script untouched after generating prepare. If the conformance script still does its own staging and install, prepare's work is wiped (by the ) or duplicated (by re-running install) on every run — defeating the purpose of this skill entirely. Always run the Reconcile the existing conformance script step.
- Don't default to Bash without checking the host OS. A script on native Windows (outside Git Bash / WSL) won't even run, and a script on macOS/Linux is equally useless. Always run the detection in Pick the Shell First before deciding the file extension. If the project supports both, produce both files — and remember to reconcile the matching conformance script in both flavors too.