plain-forge 1.0.16 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/forge/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +14 -3
  2. package/forge/rules/integration-embedded-testing.md +9 -7
  3. package/forge/rules/integration-embedded.md +4 -4
  4. package/forge/skills/implement-conformance-testing-script/SKILL.md +56 -43
  5. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +34 -21
  6. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +16 -5
  7. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +22 -11
  8. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +13 -2
  9. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +16 -6
  10. package/forge/skills/implement-prepare-environment-script/SKILL.md +23 -18
  11. package/forge/skills/implement-prepare-environment-script/assets/prepare_environment_java.sh +8 -4
  12. package/forge/skills/implement-prepare-environment-script/assets/prepare_environment_python.sh +8 -4
  13. package/forge/skills/implement-unit-testing-script/SKILL.md +14 -11
  14. package/forge/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +6 -1
  15. package/forge/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +6 -1
  16. package/forge/skills/implement-unit-testing-script/assets/run_unittests_java.sh +6 -3
  17. package/forge/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +6 -1
  18. package/forge/skills/implement-unit-testing-script/assets/run_unittests_python.sh +8 -4
  19. package/forge/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +6 -2
  20. package/forge/skills/init-config-file/SKILL.md +5 -5
  21. package/forge/skills/load-plain-reference/SKILL.md +97 -42
  22. package/forge/skills/plain-healthcheck/SKILL.md +1 -1
  23. package/forge/skills/run-codeplain/SKILL.md +12 -3
  24. package/package.json +1 -1
@@ -19,18 +19,31 @@ fi
19
19
  current_dir=$(pwd)
20
20
  printf "Current directory: $current_dir\n"
21
21
 
22
- tree $2
22
+ # Resolve the conformance tests folder to an absolute path so it can be used
23
+ # after changing into the working folder ($2 may be relative to the invocation
24
+ # directory on older renderers; newer renderers pass it as an absolute path).
25
+ case "$2" in
26
+ /*) CONFORMANCE_TESTS_SOURCE="$2" ;;
27
+ *) CONFORMANCE_TESTS_SOURCE="$current_dir/$2" ;;
28
+ esac
23
29
 
24
- JAVA_BUILD_SUBFOLDER=,tmp/$1
30
+ tree "$CONFORMANCE_TESTS_SOURCE"
31
+
32
+ # Working folders live in the system temp directory. $1 and $2 may be absolute
33
+ # paths, so only their basenames are used to build the working folder names.
34
+ JAVA_BUILD_SUBFOLDER="/tmp/java_$(basename "$1")"
35
+ CONFORMANCE_TESTS_FOLDER="/tmp/java_conformance_$(basename "$2")"
36
+
37
+ trap 'rm -rf "$JAVA_BUILD_SUBFOLDER" "$CONFORMANCE_TESTS_FOLDER"' EXIT
25
38
 
26
39
  if [ "${VERBOSE:-}" -eq 1 ] 2>/dev/null; then
27
40
  printf "Preparing Java build subfolder: $JAVA_BUILD_SUBFOLDER\n"
28
41
  fi
29
42
 
30
- rm -rf $JAVA_BUILD_SUBFOLDER
31
- mkdir -p $JAVA_BUILD_SUBFOLDER
43
+ rm -rf "$JAVA_BUILD_SUBFOLDER"
44
+ mkdir -p "$JAVA_BUILD_SUBFOLDER"
32
45
 
33
- cp -R $1/* $JAVA_BUILD_SUBFOLDER
46
+ cp -R "$1"/* "$JAVA_BUILD_SUBFOLDER"
34
47
  printf "Copied from $1 to $JAVA_BUILD_SUBFOLDER...\n"
35
48
 
36
49
  # Move to the subfolder
@@ -54,17 +67,15 @@ if [ $exit_code -ne 0 ]; then
54
67
  exit $exit_code
55
68
  fi
56
69
 
57
- CONFORMANCE_TESTS_FOLDER=.tmp/java_conformance
58
-
59
70
  cd "$current_dir" 2>/dev/null
60
71
  printf "Moved to $current_dir...\n"
61
72
  printf "Preparing Java conformance tests subfolder: $CONFORMANCE_TESTS_FOLDER\n"
62
73
 
63
- rm -rf $CONFORMANCE_TESTS_FOLDER
64
- mkdir -p $CONFORMANCE_TESTS_FOLDER
74
+ rm -rf "$CONFORMANCE_TESTS_FOLDER"
75
+ mkdir -p "$CONFORMANCE_TESTS_FOLDER"
65
76
 
66
- cp -R $2/* $CONFORMANCE_TESTS_FOLDER
67
- printf "Copied from $2 to $CONFORMANCE_TESTS_FOLDER...\n"
77
+ cp -R "$CONFORMANCE_TESTS_SOURCE"/* "$CONFORMANCE_TESTS_FOLDER"
78
+ printf "Copied from $CONFORMANCE_TESTS_SOURCE to $CONFORMANCE_TESTS_FOLDER...\n"
68
79
 
69
80
  # Move to the subfolder
70
81
  cd "$CONFORMANCE_TESTS_FOLDER" 2>/dev/null
@@ -33,7 +33,15 @@ if (Get-Command python3 -ErrorAction SilentlyContinue) {
33
33
 
34
34
  $current_dir = Get-Location
35
35
 
36
- $PYTHON_BUILD_SUBFOLDER = "python_$BuildFolder"
36
+ # Resolve conformance tests folder to an absolute path so it can be used
37
+ # from the build subfolder (where we'll Push-Location to next).
38
+ if (-not [System.IO.Path]::IsPathRooted($ConformanceTestsFolder)) {
39
+ $ConformanceTestsFolder = Join-Path $current_dir $ConformanceTestsFolder
40
+ }
41
+
42
+ # Working folder lives in the system temp directory. $BuildFolder may be an
43
+ # absolute path, so only its leaf name is used to build the working folder name.
44
+ $PYTHON_BUILD_SUBFOLDER = Join-Path ([System.IO.Path]::GetTempPath()) "python_$(Split-Path $BuildFolder -Leaf)"
37
45
 
38
46
  if ($env:VERBOSE -eq "1") {
39
47
  Write-Host "Preparing Python build subfolder: $PYTHON_BUILD_SUBFOLDER"
@@ -72,7 +80,7 @@ try {
72
80
  # Temporarily allow stderr output without throwing (Python unittest writes progress to stderr)
73
81
  # ForEach-Object converts ErrorRecord objects (from stderr) to plain strings to avoid verbose error formatting
74
82
  $ErrorActionPreference = 'Continue'
75
- $output = & $PYTHON_CMD -m unittest discover -b -s "$current_dir/$ConformanceTestsFolder" 2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { $_.Exception.Message } else { $_ } } | Out-String
83
+ $output = & $PYTHON_CMD -m unittest discover -b -s $ConformanceTestsFolder 2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { $_.Exception.Message } else { $_ } } | Out-String
76
84
  $exit_code = $LASTEXITCODE
77
85
  $ErrorActionPreference = 'Stop'
78
86
 
@@ -89,4 +97,7 @@ try {
89
97
  exit $exit_code
90
98
  } finally {
91
99
  Pop-Location
100
+ if (Test-Path $PYTHON_BUILD_SUBFOLDER) {
101
+ Remove-Item -Path $PYTHON_BUILD_SUBFOLDER -Recurse -Force -ErrorAction SilentlyContinue
102
+ }
92
103
  }
@@ -26,18 +26,28 @@ else
26
26
  exit $UNRECOVERABLE_ERROR_EXIT_CODE
27
27
  fi
28
28
 
29
- current_dir=$(pwd)
29
+ # Resolve the conformance tests folder to an absolute path so it can be used
30
+ # after changing into the working folder ($2 may be relative to the invocation
31
+ # directory on older renderers; newer renderers pass it as an absolute path).
32
+ case "$2" in
33
+ /*) CONFORMANCE_TESTS_FOLDER="$2" ;;
34
+ *) CONFORMANCE_TESTS_FOLDER="$(pwd)/$2" ;;
35
+ esac
30
36
 
31
- PYTHON_BUILD_SUBFOLDER=".tmp/$1"
37
+ # Working folder lives in the system temp directory. $1 may be an absolute
38
+ # path, so only its basename is used to build the working folder name.
39
+ PYTHON_BUILD_SUBFOLDER="/tmp/python_$(basename "$1")"
40
+
41
+ trap 'rm -rf "$PYTHON_BUILD_SUBFOLDER"' EXIT
32
42
 
33
43
  if [ "${VERBOSE:-}" -eq 1 ] 2>/dev/null; then
34
44
  printf "Preparing Python build subfolder: $PYTHON_BUILD_SUBFOLDER\n"
35
45
  fi
36
46
 
37
- rm -rf $PYTHON_BUILD_SUBFOLDER
38
- mkdir -p $PYTHON_BUILD_SUBFOLDER
47
+ rm -rf "$PYTHON_BUILD_SUBFOLDER"
48
+ mkdir -p "$PYTHON_BUILD_SUBFOLDER"
39
49
 
40
- cp -R $1/* $PYTHON_BUILD_SUBFOLDER
50
+ cp -R "$1"/* "$PYTHON_BUILD_SUBFOLDER"
41
51
 
42
52
  # Move to the subfolder
43
53
  cd "$PYTHON_BUILD_SUBFOLDER" 2>/dev/null
@@ -84,7 +94,7 @@ printf "Requirements setup completed in %.2f seconds\n\n" "$duration"
84
94
  # Execute all Python conformance tests in the build folder
85
95
  printf "Running Python conformance tests...\n\n"
86
96
 
87
- output=$($PYTHON_CMD -m unittest discover -b -s "$current_dir/$2" 2>&1)
97
+ output=$($PYTHON_CMD -m unittest discover -b -s "$CONFORMANCE_TESTS_FOLDER" 2>&1)
88
98
  exit_code=$?
89
99
 
90
100
  # Echo the original output
@@ -33,7 +33,7 @@ Without a prepare script, every one of those N invocations does the full depende
33
33
 
34
34
  `prepare_environment_<lang>` exists to **amortize that cost to one** install per render:
35
35
 
36
- - Prepare runs **once**, installs everything, populates the project-local isolation location inside `.tmp/<lang>_<arg>/` (`./.venv`, `./node_modules`, `./.m2`, `./.gocache`, `./.cargo`, `./.pub-cache`).
36
+ - Prepare runs **once**, installs everything, populates the project-local isolation location inside the working folder `<temp>/<lang>_<basename>/` (`./.venv`, `./node_modules`, `./.m2`, `./.gocache`, `./.cargo`, `./.pub-cache`). Throughout this skill, `<temp>/<lang>_<basename>` means: the system temp directory (`/tmp` in Bash, `[System.IO.Path]::GetTempPath()` in PowerShell), a short language identifier, and the **basename** of the build-folder argument (`$(basename "$1")` / `Split-Path -Leaf`) — never the raw argument, which arrives as an absolute path from current renderers.
37
37
  - The conformance script then runs **N times**, each invocation in its [activate-only variant](../implement-conformance-testing-script/SKILL.md#variant-decision-install-inline-vs-activate-only), attaching to the already-populated working folder and skipping the install step entirely.
38
38
  - Net effect: install cost goes from `N × install-cost` to `1 × install-cost + N × cheap-attach-cost`.
39
39
 
@@ -45,8 +45,8 @@ This is the **whole reason** the prepare-then-conformance split exists. If a pro
45
45
 
46
46
  `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 [`run_unittests_<lang>`](../implement-unit-testing-script/SKILL.md):
47
47
 
48
- - The unit-test runner is **fully self-contained**. It does its own staging into its **own** `.tmp/<lang>_<arg>/` working folder, and it installs its own dependencies inline (`pip install -r requirements.txt`, `npm ci`, `mvn`, `cargo fetch`, ...) every run.
49
- - The unit-test runner **never reads from** the working folder `prepare_environment` populates. The two scripts use independent working folders even when they happen to share a `.tmp/<lang>_<arg>/` naming convention — each script wipes and rebuilds its own copy.
48
+ - The unit-test runner is **fully self-contained**. It does its own staging into its **own** `<temp>/<lang>_<basename>/` working folder, and it installs its own dependencies inline (`pip install -r requirements.txt`, `npm ci`, `mvn`, `cargo fetch`, ...) every run — and it removes that folder when it exits.
49
+ - The unit-test runner **never reads from** the working folder `prepare_environment` populates. The two scripts use independent working folders even when they happen to share a `<temp>/<lang>_<basename>/` naming convention — each script wipes and rebuilds its own copy.
50
50
  - The unit-test runner **does not require** `prepare_environment` 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.
51
51
  - There is **no activate-only variant** of the unit-test runner. [`implement-unit-testing-script`](../implement-unit-testing-script/SKILL.md) emits a self-contained script every time — the two-variant pattern is exclusive to the conformance runner.
52
52
 
@@ -86,13 +86,13 @@ The same pattern applies to both shell flavors. Only the syntax changes.
86
86
  Every prepare-environment script must implement these steps **in this order**:
87
87
 
88
88
  1. **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 `69`.
89
- 2. **Argument validation.** Require **one** positional argument: `<build_folder>`. If missing, print usage and exit with code `69`.
90
- 3. **Working directory setup.** Define a working folder at `.tmp/<lang>_<arg>` — **identical** to the path the conformance script will use. Wipe it (`rm -rf` / `Remove-Item -Recurse -Force`) and recreate it. This folder — and **only** this folder — is where every subsequent write must land.
89
+ 2. **Argument validation.** Require **one** positional argument: `<build_folder>`. If missing, print usage and exit with code `69`. The renderer passes this argument as an **absolute path** (older renderer versions passed a relative one) — the script must work with both, which is why the working-folder name in step 3 is built from the argument's *basename*, never from the raw argument.
90
+ 3. **Working directory setup.** Define a working folder at `<temp>/<lang>_<basename>` — **identical** to the path the conformance script will use (`/tmp/<lang>_$(basename "$1")` in Bash, `Join-Path ([System.IO.Path]::GetTempPath()) "<lang>_$(Split-Path $BuildFolder -Leaf)"` in PowerShell). Wipe it (`rm -rf` / `Remove-Item -Recurse -Force`) and recreate it. This folder — and **only** this folder — is where every subsequent write must land. **Do not register a cleanup trap / `finally` deletion** — unlike its two siblings, prepare deliberately leaves the working folder populated on exit; that folder *is* the deliverable the conformance script attaches to.
91
91
  4. **Copy the build.** Recursively copy everything from `<build_folder>` (`$1`) into the working folder. After this step the source folder (`$1`) is treated as **read-only** for the rest of the script.
92
- 5. **Enter the working directory.** `cd` / `Set-Location` into `.tmp/<lang>_<arg>`. If that fails, exit with code `69`. All remaining steps run from inside the working folder; they must never write back to `$1`.
93
- 6. **Install dependencies / pre-build artifacts into an isolated environment inside `.tmp/<lang>_<arg>`.** Set up a per-working-folder dependency location (a Python venv at `./.venv`, a local `./node_modules`, a project-scoped Maven repo at `./.m2`, 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 `$1` and never in the user's home directory. **Never** install into the source build folder (`$1`), the user's global cache (`~/.m2`, system-wide `pip`, `~/.cargo`, `~/.npm`, ...), or anywhere outside `.tmp/<lang>_<arg>`. If any sub-step fails, exit with code `69` (do **not** propagate Maven/pip/npm exit codes — a half-prepared environment is itself an unrecoverable error). See [Dependency isolation](#dependency-isolation) for per-language specifics.
92
+ 5. **Enter the working directory.** `cd` / `Set-Location` into `<temp>/<lang>_<basename>`. If that fails, exit with code `69`. All remaining steps run from inside the working folder; they must never write back to `$1`.
93
+ 6. **Install dependencies / pre-build artifacts into an isolated environment inside `<temp>/<lang>_<basename>`.** Set up a per-working-folder dependency location (a Python venv at `./.venv`, a local `./node_modules`, a project-scoped Maven repo at `./.m2`, 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 `$1` and never in the user's home directory. **Never** install into the source build folder (`$1`), the user's global cache (`~/.m2`, system-wide `pip`, `~/.cargo`, `~/.npm`, ...), or anywhere outside `<temp>/<lang>_<basename>`. If any sub-step fails, exit with code `69` (do **not** propagate Maven/pip/npm exit codes — a half-prepared environment is itself an unrecoverable error). See [Dependency isolation](#dependency-isolation) for per-language specifics.
94
94
 
95
- 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.
95
+ 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 — and the populated working folder is intentionally left in place for the conformance runner.
96
96
 
97
97
  ### The build folder is read-only — hard rule
98
98
 
@@ -100,14 +100,14 @@ The source build folder passed in as `$1` is **input only**. Prepare reads from
100
100
 
101
101
  - install dependencies into it (no `pip install` inside `$1`, no `npm install` inside `$1`, no `mvn install` writing into `$1`, no Cargo build artifacts ending up under `$1`),
102
102
  - write a virtualenv / `node_modules` / `.m2` / `.gocache` / `.cargo` / `.pub-cache` directory inside it,
103
- - pre-build into it (every compile output — `target/`, `build/`, `dist/`, native binaries, generated sources — lives inside `.tmp/<lang>_<arg>`),
103
+ - pre-build into it (every compile output — `target/`, `build/`, `dist/`, native binaries, generated sources — lives inside `<temp>/<lang>_<basename>`),
104
104
  - create logs, caches, or temp files inside it.
105
105
 
106
- The build folder is shared with the renderer (`plain_modules/...` by default) and with the conformance script, which staging-checks it via `if [ ! -d ".tmp/<lang>_$1" ]` and expects `$1` itself to look the same as it did right after rendering. Writing into `$1` corrupts the renderer's view of "what was generated", churns git status if the project commits `$1`, and (if the conformance script ever does an `rm -rf $1` during its own setup) silently destroys work prepare did.
106
+ The build folder is shared with the renderer (`plain_modules/...` by default) and with the conformance script, which staging-checks the working folder via `if [ ! -d "/tmp/<lang>_$(basename "$1")" ]` and expects `$1` itself to look the same as it did right after rendering. Writing into `$1` corrupts the renderer's view of "what was generated", churns git status if the project commits `$1`, and (if the conformance script ever does an `rm -rf $1` during its own setup) silently destroys work prepare did.
107
107
 
108
- The whole point of staging via `.tmp/<lang>_<arg>` 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.
108
+ The whole point of staging into `<temp>/<lang>_<basename>` is so the source build folder stays a clean, reproducible artifact of the render and no build debris lands inside the user's project. 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.
109
109
 
110
- If you find yourself about to issue any command whose `cwd` is `$1`, or whose target path starts with `$1/`, **stop**. Either move the operation into `.tmp/<lang>_<arg>`, or you're doing something the script must not do.
110
+ If you find yourself about to issue any command whose `cwd` is `$1`, or whose target path starts with `$1/`, **stop**. Either move the operation into `<temp>/<lang>_<basename>`, or you're doing something the script must not do.
111
111
 
112
112
  ## Coordination contract
113
113
 
@@ -117,7 +117,8 @@ The two scripts must agree on:
117
117
 
118
118
  | What | Why it matters |
119
119
  |---|---|
120
- | **Working folder path** (`.tmp/<lang>_$1`) | The conformance script `cd`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. |
120
+ | **Working folder path** (`<temp>/<lang>_<basename>`) | The conformance script `cd`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. Both scripts must derive it the same way: system temp dir + `<lang>_` + basename of `$1`. |
121
+ | **Working folder lifecycle** | Prepare wipes-and-recreates the folder at the start and **leaves it populated on exit**. The activate-only conformance script attaches to it and **never deletes it** — not on success, not on failure. Only prepare (on its next run) recreates it. A cleanup trap in either script destroys the amortization this pair exists for. |
121
122
  | **Dependency isolation location** (`./.venv`, `./.m2`, `./node_modules`, `./.gocache`, `./.cargo`, …) — relative to the working folder | If prepare populates `~/.m2` and conformance reads from `./.m2`, the warm cache is invisible. Always use the **project-local** path inside the working folder, in both scripts. |
122
123
  | **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 `./.m2` for Java). |
123
124
  | **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. |
@@ -131,7 +132,8 @@ Shared across both shell flavors:
131
132
  - **Exit codes:**
132
133
  - `69` — 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.
133
134
  - `0` — success.
134
- - **Working folder naming:** `.tmp/<lang>_<arg>` where `<lang>` is a short identifier for the language (`java`, `python`, `node`, `go`, `rust`, ...). 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 `$1` after step 4.
135
+ - **Working folder naming:** `<temp>/<lang>_<basename>` — the system temp directory (`/tmp` in Bash, `[System.IO.Path]::GetTempPath()` in PowerShell) plus a short identifier for the language (`java`, `python`, `node`, `go`, `rust`, ...) and the **basename** of the first (and only) argument (`$(basename "$1")` / `Split-Path -Leaf`). Never embed the raw argument in the path — it may be absolute, and concatenating it produces broken doubled paths. All dependency installs, build outputs, caches, and pre-built artifacts live inside this folder. Nothing the script does should touch `$1` after step 4.
136
+ - **No cleanup on exit.** Prepare leaves the working folder populated — that folder is its deliverable. Do not add a `trap … EXIT` / `finally` deletion; the folder is reclaimed the next time prepare wipes-and-recreates it (and the OS eventually reclaims the temp dir).
135
137
  - **Logging — be as verbose as possible.** The script is the only thing the operator sees between a `codeplain` render and a green/red conformance result; when prepare fails, the only forensic evidence anyone has is its stdout/stderr. Treat the script like a production runbook, not a quiet helper. Concretely:
136
138
  - **Announce every step before doing it**, with the exact value of every variable involved — the resolved source folder, the working folder, the language, the toolchain version detected, the isolation root, the dependency-manifest path, the install / pre-build command about to run. "Installing dependencies" alone is useless; "Installing dependencies from `./requirements.txt` into venv `./.venv` (Python 3.11.6 at `/usr/local/bin/python3`)" is triage-ready.
137
139
  - **Echo every non-trivial command before executing it.** In Bash, use `set -x` for the body of the script (or `echo "+ <cmd>"` immediately before each call); in PowerShell, set `$VerbosePreference = 'Continue'` and use `Write-Verbose` / `Write-Host` to print each command line with its arguments.
@@ -159,7 +161,7 @@ The dependency environment must live **inside** `$WORKING_FOLDER` so the conform
159
161
 
160
162
  Notes:
161
163
 
162
- - **Every path in the install / pre-build command is relative to `.tmp/<lang>_<arg>`.** That's why the script `cd`s into the working folder in step 5 — from that point on, `./.venv`, `./node_modules`, `./.m2`, `./.gocache`, `./.cargo`, `./.pub-cache`, and any compile output (`target/`, `build/`, `dist/`, native binaries) all resolve under `.tmp/<lang>_<arg>`, never under `$1` and never under the user's home directory.
164
+ - **Every path in the install / pre-build command is relative to `<temp>/<lang>_<basename>`.** That's why the script `cd`s into the working folder in step 5 — from that point on, `./.venv`, `./node_modules`, `./.m2`, `./.gocache`, `./.cargo`, `./.pub-cache`, and any compile output (`target/`, `build/`, `dist/`, native binaries) all resolve under `<temp>/<lang>_<basename>`, never under `$1` and never under the user's home directory.
163
165
  - **Java/Rust/Go must compile, not just download.** The conformance script will time-out / re-compile from scratch if you only resolve metadata. Use `mvn install`, `cargo build --tests`, `go build ./...` (not just `dependency:resolve` / `cargo fetch` / `go mod download`).
164
166
  - **Python and Node.js only need to install** — they're interpreted/JIT-compiled at test time, so `pip install` / `npm ci` is sufficient.
165
167
  - **Always pass the isolation flag/env var.** `mvn` without `-Dmaven.repo.local`, `cargo` without `CARGO_HOME`, etc., write to the user's home directory instead of `$WORKING_FOLDER`. The conformance script will look in the wrong place and the warming was wasted — and the user's home dir gets polluted.
@@ -207,9 +209,10 @@ Adding a `prepare_environment_<lang>` script changes the contract for the corres
207
209
 
208
210
  | Step in conformance | If prepare exists, you must... |
209
211
  |---|---|
210
- | Staging block (`rm -rf .tmp/<lang>_$1` + `mkdir -p` + `cp -R $1/* .tmp/...`) | **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` |
212
+ | Staging block (`rm -rf` of the working folder + `mkdir -p` + `cp -R $1/*` into it) | **Remove entirely.** Replace with a guard: `WORKING_FOLDER="/tmp/<lang>_$(basename "$1")"; if [ ! -d "$WORKING_FOLDER" ]; then echo "Error: build folder missing — run prepare_environment_<lang>.sh first."; exit 69; fi` |
211
213
  | Dependency install / pre-build (`pip install`, `mvn install -DskipTests`, `npm ci`, `cargo build --tests`, etc.) | **Remove entirely.** Replace with a guard that the isolation location exists (`.venv/bin/activate` for Python, `.m2/` for Java, `node_modules/` for Node, etc.) and exit `69` if missing. |
212
214
  | 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. |
215
+ | Working-folder cleanup on exit (`trap 'rm -rf "$WORKING_FOLDER"' EXIT` / `Remove-Item` in `finally`) | **Remove entirely.** The activate-only variant never deletes the working folder — prepare owns its lifecycle. Leaving the trap in wipes the warmed environment after the first conformance run, so every later run fails the "missing prepared environment" guard. |
213
216
  | Test execution + "no tests discovered" guard + exit-code propagation | **Keep unchanged.** |
214
217
 
215
218
  4. **Verify the conformance script's exit codes still follow [`implement-conformance-testing-script`](../implement-conformance-testing-script/SKILL.md)** — the new "missing prepared environment" guard should exit `69` (unrecoverable invocation error), the no-tests guard should still exit `1`, and the test command's exit code should still propagate.
@@ -224,14 +227,16 @@ Adding a `prepare_environment_<lang>` script changes the contract for the corres
224
227
  ## Anti-Patterns
225
228
 
226
229
  - **(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`](../implement-unit-testing-script/SKILL.md)) 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 `prepare_environment` "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 [`prepare_environment` is conformance-only — NOT for unit tests](#prepare_environment-is-conformance-only--not-for-unit-tests) above.
227
- - **(Hard mistake) Don't install into, build into, or otherwise write to the source build folder (`$1`).** The build folder passed as `$1` is read-only input after step 4. Every install, cache, build artifact (`target/`, `build/`, `dist/`, native binaries, generated sources), log, and temp file must land in `.tmp/<lang>_<arg>`. This includes never running `pip install`, `npm install`, `mvn install`, `cargo build`, or `go build` with `$1` as their `cwd` or target; never letting a venv / `node_modules` / `.m2` / `.gocache` / `.cargo` / `.pub-cache` directory appear inside `$1`; and never producing a pre-built artifact at any path under `$1/`. The whole point of staging into `.tmp/` 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 `$1` on its own.
230
+ - **(Hard mistake) Don't install into, build into, or otherwise write to the source build folder (`$1`).** The build folder passed as `$1` is read-only input after step 4. Every install, cache, build artifact (`target/`, `build/`, `dist/`, native binaries, generated sources), log, and temp file must land in `<temp>/<lang>_<basename>`. This includes never running `pip install`, `npm install`, `mvn install`, `cargo build`, or `go build` with `$1` as their `cwd` or target; never letting a venv / `node_modules` / `.m2` / `.gocache` / `.cargo` / `.pub-cache` directory appear inside `$1`; and never producing a pre-built artifact at any path under `$1/`. The whole point of staging into the system temp directory 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 `$1` on its own.
231
+ - **(Hard mistake) Don't build any path by concatenating the raw build-folder argument.** `$1` arrives as an absolute path from current renderers. Patterns like `WORKING_FOLDER=.tmp/$1` or `WORKING_FOLDER=<lang>_$1` silently produce doubled paths (`/project//abs/path/...`) — the classic symptom is a "folder does not exist" or `ImportError: Start directory is not importable` error pointing at a path that contains the project root twice. Always take `$(basename "$1")` / `Split-Path -Leaf`.
232
+ - **(Hard mistake) Don't add a cleanup trap that deletes the working folder on exit.** The populated working folder is this script's deliverable — the activate-only conformance runner attaches to it N times per render. Deleting it on exit turns every conformance run into a "missing prepared environment" failure. (The unit-test and install-inline conformance scripts *do* clean up after themselves; prepare and activate-only conformance never do.)
228
233
  - **Don't write to the user's global dependency cache** (`~/.m2`, system-wide `pip`, `~/.cargo`, `~/.npm`, etc.). The conformance script reads from the project-local cache; a global write is invisible to it and pollutes the user's home dir.
229
234
  - **Don't use a different working folder name than the conformance script.** They must match exactly. If you change one, change the other.
230
235
  - **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.
231
236
  - **Don't propagate non-`69` exit codes from `mvn` / `pip` / `npm`.** A failed install means the environment isn't usable. Treat every failure as `exit 69` so the orchestrator can tell "prepare failed" apart from "tests failed".
232
237
  - **Don't skip the toolchain check**, even when "everyone has it installed" — exit code `69` 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.
233
238
  - **Don't print the "moved to ..." line before the `cd` success check.** The reference script does this and it lies on failure. Put the log *after* the guard, or print "attempting to enter ..." instead.
234
- - **Don't reuse the source folder in place.** Always copy into `.tmp/<lang>_<arg>` first; the conformance script relies on this isolation.
239
+ - **Don't reuse the source folder in place.** Always copy into `<temp>/<lang>_<basename>` first; the conformance script relies on this isolation.
235
240
  - **Don't change the exit-code contract between Bash and PowerShell variants.** The `.sh` and `.ps1` for the same language must use identical exit codes for identical failure modes.
236
241
  - **Don't write a cross-shell hybrid** (e.g. a `.sh` that detects PowerShell, or vice versa). Ship one script per shell, named with the appropriate extension.
237
242
  - **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.
@@ -16,16 +16,20 @@ if [ -z "$1" ]; then
16
16
  exit 1
17
17
  fi
18
18
 
19
- JAVA_BUILD_SUBFOLDER=.tmp/$1
19
+ # Working folder lives in the system temp directory. $1 may be an absolute
20
+ # path, so only its basename is used to build the working folder name.
21
+ # No cleanup trap here on purpose: this script deliberately leaves the working
22
+ # folder populated so the conformance runner can attach to it on every invocation.
23
+ JAVA_BUILD_SUBFOLDER="/tmp/java_$(basename "$1")"
20
24
 
21
25
  if [ "${VERBOSE:-}" -eq 1 ] 2>/dev/null; then
22
26
  printf "Copying generated code to main project folder: $JAVA_BUILD_SUBFOLDER\n"
23
27
  fi
24
28
 
25
- rm -rf $JAVA_BUILD_SUBFOLDER
26
- mkdir -p $JAVA_BUILD_SUBFOLDER
29
+ rm -rf "$JAVA_BUILD_SUBFOLDER"
30
+ mkdir -p "$JAVA_BUILD_SUBFOLDER"
27
31
 
28
- cp -R $1/* $JAVA_BUILD_SUBFOLDER
32
+ cp -R "$1"/* "$JAVA_BUILD_SUBFOLDER"
29
33
  printf "Copied from $1 to $JAVA_BUILD_SUBFOLDER...\n"
30
34
 
31
35
  # Move to the subfolder
@@ -27,16 +27,20 @@ fi
27
27
 
28
28
  current_dir=$(pwd)
29
29
 
30
- PYTHON_BUILD_SUBFOLDER=".tmp/$1"
30
+ # Working folder lives in the system temp directory. $1 may be an absolute
31
+ # path, so only its basename is used to build the working folder name.
32
+ # No cleanup trap here on purpose: this script deliberately leaves the working
33
+ # folder populated so the conformance runner can attach to it on every invocation.
34
+ PYTHON_BUILD_SUBFOLDER="/tmp/python_$(basename "$1")"
31
35
 
32
36
  if [ "${VERBOSE:-}" -eq 1 ] 2>/dev/null; then
33
37
  printf "Preparing Python build subfolder: $PYTHON_BUILD_SUBFOLDER\n"
34
38
  fi
35
39
 
36
- rm -rf $PYTHON_BUILD_SUBFOLDER
37
- mkdir -p $PYTHON_BUILD_SUBFOLDER
40
+ rm -rf "$PYTHON_BUILD_SUBFOLDER"
41
+ mkdir -p "$PYTHON_BUILD_SUBFOLDER"
38
42
 
39
- cp -R $1/* $PYTHON_BUILD_SUBFOLDER
43
+ cp -R "$1"/* "$PYTHON_BUILD_SUBFOLDER"
40
44
 
41
45
  # Move to the subfolder
42
46
  cd "$PYTHON_BUILD_SUBFOLDER" 2>/dev/null
@@ -31,11 +31,11 @@ The same seven-step pattern applies to both. Only the syntax changes.
31
31
  Every testing script must implement these steps **in this order**:
32
32
 
33
33
  1. **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 `69`.
34
- 2. **Argument validation.** Require exactly one positional argument: the source build folder name. If missing, print usage and exit with code `1`.
35
- 3. **Working directory setup.** Define a working folder at `.tmp/<lang>_<arg>`. If it exists, wipe its contents; otherwise create it. This folder — and **only** this folder — is where every subsequent write must land.
34
+ 2. **Argument validation.** Require exactly one positional argument: the source build folder. If missing, print usage and exit with code `1`. The renderer passes this argument as an **absolute path** (older renderer versions passed a relative one) — the script must work with both, which is why the working-folder name in step 3 is built from the argument's *basename*, never from the raw argument.
35
+ 3. **Working directory setup.** Define a working folder in the **system temp directory**: `/tmp/<lang>_$(basename "$1")` in Bash, `Join-Path ([System.IO.Path]::GetTempPath()) "<lang>_$(Split-Path $BuildFolder -Leaf)"` in PowerShell (written as `<temp>/<lang>_<basename>` below). If it exists, wipe its contents; otherwise create it. Register cleanup so the folder is removed when the script exits — `trap 'rm -rf "$WORKING_FOLDER"' EXIT` in Bash, a `Remove-Item -Recurse -Force` in the `finally` block in PowerShell. This folder — and **only** this folder — is where every subsequent write must land.
36
36
  4. **Copy the build.** Recursively copy everything from the source folder into the working folder. After this step the source folder (`$1`) is treated as **read-only** for the rest of the script.
37
- 5. **Enter the working directory.** `cd` / `Set-Location` into `.tmp/<lang>_<arg>`. If that fails, exit with code `2`. All remaining steps run from inside the working folder; they must never write back to the source build folder.
38
- 6. **Install dependencies into an isolated environment inside `.tmp/<lang>_<arg>`.** Set up a per-working-folder dependency location (a Python venv at `./.venv`, a local `./node_modules`, a project-scoped Maven repo at `./.m2`, etc.) and install/resolve all dependencies into it. **Never** install into the source build folder, the user's global cache (`~/.m2`, system-wide `pip`, `~/.cargo`, `~/.npm`, ...), or anywhere outside `.tmp/<lang>_<arg>`. If the install command fails, propagate its exit code immediately and **do not** proceed to step 7. See [Dependency isolation](#dependency-isolation) for per-language specifics.
37
+ 5. **Enter the working directory.** `cd` / `Set-Location` into `<temp>/<lang>_<basename>`. If that fails, exit with code `2`. All remaining steps run from inside the working folder; they must never write back to the source build folder.
38
+ 6. **Install dependencies into an isolated environment inside `<temp>/<lang>_<basename>`.** Set up a per-working-folder dependency location (a Python venv at `./.venv`, a local `./node_modules`, a project-scoped Maven repo at `./.m2`, etc.) and install/resolve all dependencies into it. **Never** install into the source build folder, the user's global cache (`~/.m2`, system-wide `pip`, `~/.cargo`, `~/.npm`, ...), or anywhere outside `<temp>/<lang>_<basename>`. If the install command fails, propagate its exit code immediately and **do not** proceed to step 7. See [Dependency isolation](#dependency-isolation) for per-language specifics.
39
39
  7. **Run the tests.** Invoke the language's standard test command (e.g. `mvn test`, `pytest`, `npm test`, `go test ./...`, `cargo test`), pointed at the same isolated environment from step 6. The script's final exit code is whatever the test command returns.
40
40
 
41
41
  ### The build folder is read-only — hard rule
@@ -44,12 +44,12 @@ The source build folder passed in as `$1` is **input only**. The script must nev
44
44
 
45
45
  - install dependencies into it (no `pip install` inside `$1`, no `npm install` inside `$1`, no `mvn install` writing into `$1`, no Cargo build artifacts ending up under `$1`),
46
46
  - write a virtualenv / `node_modules` / `.m2` / `.gocache` / `.cargo` directory inside it,
47
- - run the test command from inside it (every test command runs from inside `.tmp/<lang>_<arg>` after the `cd` in step 5),
47
+ - run the test command from inside it (every test command runs from inside `<temp>/<lang>_<basename>` after the `cd` in step 5),
48
48
  - create logs, caches, build outputs, or temp files inside it.
49
49
 
50
- The build folder is shared with the renderer (`plain_modules/...` by default) and downstream tooling. Writing into it corrupts the renderer's view of "what was generated" and breaks subsequent renders. Every write must go into `.tmp/<lang>_<arg>` — the whole point of staging via `.tmp` is so the source build folder stays a clean, reproducible artifact of the render.
50
+ The build folder is shared with the renderer (`plain_modules/...` by default) and downstream tooling. Writing into it corrupts the renderer's view of "what was generated" and breaks subsequent renders. Every write must go into `<temp>/<lang>_<basename>` — the whole point of staging into the system temp directory is so the source build folder stays a clean, reproducible artifact of the render and no build debris is left inside the user's project.
51
51
 
52
- If you find yourself about to issue any command whose `cwd` is the source folder, or whose target path starts with `$1/`, **stop**. Either move the operation into `.tmp/<lang>_<arg>`, or you're doing something the script must not do.
52
+ If you find yourself about to issue any command whose `cwd` is the source folder, or whose target path starts with `$1/`, **stop**. Either move the operation into `<temp>/<lang>_<basename>`, or you're doing something the script must not do.
53
53
 
54
54
  ## Conventions
55
55
 
@@ -60,7 +60,8 @@ Shared across both shell flavors:
60
60
  - `2` — filesystem problem (couldn't enter the working folder).
61
61
  - `69` — required toolchain / runtime is not installed.
62
62
  - Any other non-zero code — propagated from the underlying test command.
63
- - **Working folder naming:** `.tmp/<lang>_<arg>` where `<lang>` is a short identifier for the language (`java`, `python`, `node`, `go`, `rust`, ...). All dependency installs, build outputs, caches, and the test run itself live inside this folder. Nothing the script does should touch the source build folder after step 4.
63
+ - **Working folder naming:** `<temp>/<lang>_<basename>` — the system temp directory (`/tmp` in Bash, `[System.IO.Path]::GetTempPath()` in PowerShell) plus a short identifier for the language (`java`, `python`, `node`, `go`, `rust`, ...) and the **basename** of the build-folder argument (`$(basename "$1")` / `Split-Path $BuildFolder -Leaf`). Never embed the raw argument in the path — it may be absolute, and concatenating it produces broken doubled paths. All dependency installs, build outputs, caches, and the test run itself live inside this folder. Nothing the script does should touch the source build folder after step 4.
64
+ - **Cleanup on exit:** the working folder is temporary — remove it when the script exits, on success and failure alike (`trap 'rm -rf "$WORKING_FOLDER"' EXIT` in Bash; `Remove-Item -Recurse -Force` in the PowerShell `finally` block, after `Pop-Location`). No leftover build folders may accumulate in the temp directory or the user's project.
64
65
  - **Logging — be as verbose as possible.** The script is the only thing the operator sees between a `codeplain` render and a green/red test result; when it fails, the only forensic evidence anyone has is its stdout/stderr. Treat the script like a production runbook, not a quiet helper. Concretely:
65
66
  - **Announce every step before doing it**, with the exact value of every variable involved — the resolved source folder, the working folder, the language, the toolchain version detected, the isolation root, the dependency-manifest path, the test command about to run. "Installing dependencies" alone is useless; "Installing dependencies from `./requirements.txt` into venv `./.venv` (Python 3.11.6 at `/usr/local/bin/python3`)" is triage-ready.
66
67
  - **Echo every non-trivial command before executing it.** In Bash, use `set -x` for the body of the script (or `echo "+ <cmd>"` immediately before each call); in PowerShell, set `$VerbosePreference = 'Continue'` and use `Write-Verbose` / `Write-Host` to print each command line with its arguments.
@@ -86,7 +87,7 @@ The dependency environment must live **inside** `$WORKING_FOLDER` so the test ru
86
87
 
87
88
  Notes:
88
89
 
89
- - **Every path in the install command and test command is relative to `.tmp/<lang>_<arg>`.** That's why the script `cd`s into the working folder in step 5 — from that point on, `./.venv`, `./node_modules`, `./.m2`, etc. all resolve under `.tmp/<lang>_<arg>`, never under the source build folder.
90
+ - **Every path in the install command and test command is relative to `<temp>/<lang>_<basename>`.** That's why the script `cd`s into the working folder in step 5 — from that point on, `./.venv`, `./node_modules`, `./.m2`, etc. all resolve under `<temp>/<lang>_<basename>`, never under the source build folder.
90
91
  - **Always pass the isolation flag/env var to both the install command and the test command** — they must agree on where deps live, otherwise the test command will silently fall back to the global cache **or** (worse) the source build folder.
91
92
  - **Python is the only ecosystem where the venv is mandatory** to satisfy "into a virtual environment" literally. The others use language-native equivalents that achieve the same isolation.
92
93
  - **Pre-warming is optional for Java/Go/Rust** — their test commands will fetch deps on demand. Doing it as a separate step makes failures easier to diagnose and gives a clean "install failed vs test failed" signal.
@@ -121,9 +122,11 @@ Notes:
121
122
 
122
123
  ## Anti-Patterns
123
124
 
124
- - **(Hard mistake) Don't install into, build into, or otherwise write to the source build folder.** The build folder passed as `$1` is read-only input. Every install, cache, build artifact, log, and temp file must land in `.tmp/<lang>_<arg>`. This includes never running `pip install`, `npm install`, `mvn install`, or `cargo build` with the source folder as their `cwd` or target, never letting a venv / `node_modules` / `.m2` / `.gocache` / `.cargo` directory appear inside the source folder, and never running the test command from inside it. The whole point of staging the build into `.tmp/` is so the source folder remains a clean, reproducible artifact of the render — writing to it corrupts the renderer's view and breaks subsequent renders.
125
+ - **(Hard mistake) Don't install into, build into, or otherwise write to the source build folder.** The build folder passed as `$1` is read-only input. Every install, cache, build artifact, log, and temp file must land in `<temp>/<lang>_<basename>`. This includes never running `pip install`, `npm install`, `mvn install`, or `cargo build` with the source folder as their `cwd` or target, never letting a venv / `node_modules` / `.m2` / `.gocache` / `.cargo` directory appear inside the source folder, and never running the test command from inside it. The whole point of staging the build into the system temp directory is so the source folder remains a clean, reproducible artifact of the render — writing to it corrupts the renderer's view and breaks subsequent renders.
126
+ - **(Hard mistake) Don't build any path by concatenating the raw build-folder argument.** `$1` arrives as an absolute path from current renderers. Patterns like `WORKING_FOLDER=.tmp/$1`, `WORKING_FOLDER=<lang>_$1`, or `"$current_dir/$1"` silently produce doubled paths (`/project//abs/path/...`) — the classic symptom is an `ImportError: Start directory is not importable` or "folder does not exist" pointing at a path that contains the project root twice. Always take `$(basename "$1")` / `Split-Path -Leaf`.
125
127
  - Don't skip the toolchain check, even when "everyone has it installed" — exit code `69` is what the calling system relies on to detect a missing runtime.
126
- - Don't reuse the source folder in place. Always copy into `.tmp/<lang>_<arg>` first; the renderer relies on this isolation.
128
+ - Don't reuse the source folder in place. Always copy into `<temp>/<lang>_<basename>` first; the renderer relies on this isolation.
129
+ - Don't leave the working folder behind. Register the cleanup (`trap … EXIT` / `finally`) before the first write so even an early failure removes it.
127
130
  - Don't change the exit-code contract. Other parts of the system branch on `1`, `2`, and `69` specifically — and these codes must be identical between the Bash and PowerShell variants.
128
131
  - Don't write a cross-shell hybrid (e.g. a `.sh` that detects PowerShell, or vice versa). Ship one script per shell, named with the appropriate extension.
129
132
  - Don't install dependencies into the user's global location (`~/.m2`, system-wide `pip`, `~/.cargo`, etc.). Always isolate inside `$WORKING_FOLDER` so concurrent runs and other projects can't interfere.
@@ -16,7 +16,9 @@ if (-not (Get-Command flutter -ErrorAction SilentlyContinue)) {
16
16
  }
17
17
 
18
18
  $SOURCE_FOLDER = $args[0]
19
- $BUILD_SUBFOLDER = ".tmp/flutter_build_unittests"
19
+ # Working folder lives in the system temp directory. $SOURCE_FOLDER may be an
20
+ # absolute path, so only its leaf name is used to build the working folder name.
21
+ $BUILD_SUBFOLDER = Join-Path ([System.IO.Path]::GetTempPath()) "flutter_$(Split-Path $SOURCE_FOLDER -Leaf)"
20
22
 
21
23
  Write-Host "Current directory: $(Get-Location)"
22
24
  Write-Host "Source folder: $SOURCE_FOLDER"
@@ -79,4 +81,7 @@ try {
79
81
  Remove-Item -Path "flutter_test_stdout.txt" -ErrorAction SilentlyContinue
80
82
  Remove-Item -Path "flutter_test_stderr.txt" -ErrorAction SilentlyContinue
81
83
  Pop-Location
84
+ if (Test-Path $BUILD_SUBFOLDER) {
85
+ Remove-Item -Path $BUILD_SUBFOLDER -Recurse -Force -ErrorAction SilentlyContinue
86
+ }
82
87
  }
@@ -13,7 +13,9 @@ if (-not $args[0]) {
13
13
 
14
14
  $BuildFolder = $args[0]
15
15
 
16
- $GO_BUILD_SUBFOLDER = "go_$BuildFolder"
16
+ # Working folder lives in the system temp directory. $BuildFolder may be an
17
+ # absolute path, so only its leaf name is used to build the working folder name.
18
+ $GO_BUILD_SUBFOLDER = Join-Path ([System.IO.Path]::GetTempPath()) "go_$(Split-Path $BuildFolder -Leaf)"
17
19
 
18
20
  if ($env:VERBOSE -eq "1") {
19
21
  Write-Host "Preparing Go build subfolder: $GO_BUILD_SUBFOLDER"
@@ -65,4 +67,7 @@ try {
65
67
  exit $exit_code
66
68
  } finally {
67
69
  Pop-Location
70
+ if (Test-Path $GO_BUILD_SUBFOLDER) {
71
+ Remove-Item -Path $GO_BUILD_SUBFOLDER -Recurse -Force -ErrorAction SilentlyContinue
72
+ }
68
73
  }
@@ -16,8 +16,11 @@ if [ -z "$1" ]; then
16
16
  exit 1
17
17
  fi
18
18
 
19
- # Define the path to the java build subfolder
20
- WORKING_FOLDER=.tmp/$1
19
+ # Working folder lives in the system temp directory. $1 may be an absolute
20
+ # path, so only its basename is used to build the working folder name.
21
+ WORKING_FOLDER="/tmp/java_$(basename "$1")"
22
+
23
+ trap 'rm -rf "$WORKING_FOLDER"' EXIT
21
24
 
22
25
  # Check if the java subfolder exists
23
26
  if [ -d "$WORKING_FOLDER" ]; then
@@ -30,7 +33,7 @@ fi
30
33
 
31
34
 
32
35
  # copy all folders and files from the build folder to the subfolder
33
- cp -R $1/* $WORKING_FOLDER
36
+ cp -R "$1"/* "$WORKING_FOLDER"
34
37
  printf "Copied from $1 to $WORKING_FOLDER...\n"
35
38
  # Move to the subfolder
36
39
  cd "$WORKING_FOLDER" 2>/dev/null
@@ -23,7 +23,9 @@ if (Get-Command python3 -ErrorAction SilentlyContinue) {
23
23
  exit $UNRECOVERABLE_ERROR_EXIT_CODE
24
24
  }
25
25
 
26
- $PYTHON_BUILD_SUBFOLDER = "python_$BuildFolder"
26
+ # Working folder lives in the system temp directory. $BuildFolder may be an
27
+ # absolute path, so only its leaf name is used to build the working folder name.
28
+ $PYTHON_BUILD_SUBFOLDER = Join-Path ([System.IO.Path]::GetTempPath()) "python_$(Split-Path $BuildFolder -Leaf)"
27
29
 
28
30
  if ($env:VERBOSE -eq "1") {
29
31
  Write-Host "Preparing Python build subfolder: $PYTHON_BUILD_SUBFOLDER"
@@ -73,4 +75,7 @@ try {
73
75
  exit $exit_code
74
76
  } finally {
75
77
  Pop-Location
78
+ if (Test-Path $PYTHON_BUILD_SUBFOLDER) {
79
+ Remove-Item -Path $PYTHON_BUILD_SUBFOLDER -Recurse -Force -ErrorAction SilentlyContinue
80
+ }
76
81
  }
@@ -14,16 +14,20 @@ echo "Current directory: $current_dir"
14
14
  echo "Build folder name: $1"
15
15
  echo "--------------------------------"
16
16
 
17
- PYTHON_BUILD_SUBFOLDER=.tmp/$1
17
+ # Working folder lives in the system temp directory. $1 may be an absolute
18
+ # path, so only its basename is used to build the working folder name.
19
+ PYTHON_BUILD_SUBFOLDER="/tmp/python_$(basename "$1")"
20
+
21
+ trap 'rm -rf "$PYTHON_BUILD_SUBFOLDER"' EXIT
18
22
 
19
23
  if [ "${VERBOSE:-}" -eq 1 ] 2>/dev/null; then
20
24
  printf "Preparing Python build subfolder: $PYTHON_BUILD_SUBFOLDER\n"
21
25
  fi
22
26
 
23
- rm -rf $PYTHON_BUILD_SUBFOLDER
24
- mkdir -p $PYTHON_BUILD_SUBFOLDER
27
+ rm -rf "$PYTHON_BUILD_SUBFOLDER"
28
+ mkdir -p "$PYTHON_BUILD_SUBFOLDER"
25
29
 
26
- cp -R $1/* $PYTHON_BUILD_SUBFOLDER
30
+ cp -R "$1"/* "$PYTHON_BUILD_SUBFOLDER"
27
31
 
28
32
  # Move to the subfolder
29
33
  cd "$PYTHON_BUILD_SUBFOLDER" 2>/dev/null
@@ -16,8 +16,9 @@ if (-not $args[0]) {
16
16
 
17
17
  $BuildFolder = $args[0]
18
18
 
19
- # Define the path to the subfolder
20
- $NODE_SUBFOLDER = "node_$BuildFolder"
19
+ # Define the path to the subfolder. It lives in the system temp directory;
20
+ # $BuildFolder may be an absolute path, so only its leaf name is used.
21
+ $NODE_SUBFOLDER = Join-Path ([System.IO.Path]::GetTempPath()) "node_$(Split-Path $BuildFolder -Leaf)"
21
22
 
22
23
  if ($env:VERBOSE -eq "1") {
23
24
  Write-Host "Preparing Node subfolder: $NODE_SUBFOLDER"
@@ -80,4 +81,7 @@ try {
80
81
  exit $TEST_EXIT_CODE
81
82
  } finally {
82
83
  Pop-Location
84
+ if (Test-Path $NODE_SUBFOLDER) {
85
+ Remove-Item -Path $NODE_SUBFOLDER -Recurse -Force -ErrorAction SilentlyContinue
86
+ }
83
87
  }
@@ -29,7 +29,7 @@ If you only fixed a typo inside a `.plain` file and the testing surface didn't m
29
29
  1. Determines **how many** `config.yaml` files the project needs (one per part — see [Per-part split](#per-part-split)).
30
30
  2. For each config, gathers the decided values from the current project state (existing scripts under `test_scripts/`, the template directory, the build/dest folder choices).
31
31
  3. Emits a clean, alphabetically-grouped `config.yaml` containing **only** keys that are actually in use, using the canonical key names from the [Valid keys reference](#valid-keys-reference).
32
- 4. Verifies that every `*-script` value points at a file that exists on disk under `test_scripts/` (or wherever the user placed it), using the same lookup rule the renderer uses: absolute path → path relative to the config file's directory → path relative to the renderer's directory.
32
+ 4. Verifies that every `*-script` value points at a file that exists on disk under `test_scripts/` (or wherever the user placed it), using the same resolution rule the renderer uses: an absolute path (or a `~` path) is used as-is; a relative path in `config.yaml` resolves against the **config file's directory**. There is no other fallback — the renderer fails fast with a `FileNotFoundError` when a script path doesn't resolve to an existing file.
33
33
  5. Hands off to `plain-healthcheck` for the full validation pass.
34
34
 
35
35
  ## What this skill does NOT do
@@ -51,7 +51,7 @@ These keys reflect choices made in Phase 3 of `forge-plain` and are the bread an
51
51
 
52
52
  | Key | Type | Default | When to include |
53
53
  |---|---|---|---|
54
- | `unittests-script` | path (string) | — | **Required.** Every project gets a unit-test runner. Path resolves relative to the config file's directory (preferred) or the renderer directory. |
54
+ | `unittests-script` | path (string) | — | **Required.** Every project gets a unit-test runner. A relative path resolves against the config file's directory; the renderer fails fast if the file doesn't exist. |
55
55
  | `conformance-tests-script` | path (string) | — | Include when the user opted into conformance testing in Phase 3. |
56
56
  | `prepare-environment-script` | path (string) | — | Include only when both (a) the user opted into a prepare-environment script and (b) `conformance-tests-script` is also set. Setting prepare without conformance is a hard `plain-healthcheck` failure. |
57
57
  | `test-script-timeout` | int (seconds) | `120` | Include only when the user explicitly raised/lowered the default. |
@@ -72,7 +72,7 @@ These are useful but the defaults are almost always fine. Only include them when
72
72
  | `copy-conformance-tests` | bool | `false` | Requires `conformance-tests-script` to also be set. |
73
73
  | `conformance-tests-dest` | string | `dist_conformance_tests` | Target folder for the conformance-test copy. Must differ from `conformance-tests-folder`. |
74
74
  | `log-to-file` | bool | `true` | Disable only when the user explicitly does not want a log file. Controls whether logs are mirrored to disk — it does **not** set the log level (that's `logging-config-path`'s job). |
75
- | `log-file-name` | string | `codeplain.log` | If `log-to-file` is `false`, this key must be left out. Resolved relative to the `.plain` file directory. |
75
+ | `log-file-name` | string | `codeplain.log` | If `log-to-file` is `false`, this key must be left out. A relative value set here resolves against the config file's directory; when the key is left out entirely, the default resolves against the `.plain` file's directory. |
76
76
  | `render-machine-graph` | bool | `false` | Include only when the user wants the state-machine graph rendered. |
77
77
  | `headless` | bool | `false` | Include only when the project is meant to run in CI / non-interactive mode by default. |
78
78
  | `force-render` | bool | `false` | Almost never belongs in `config.yaml`; prefer the CLI flag for one-off forced renders. |
@@ -117,7 +117,7 @@ In other words: `logging-config-path` is the **only** knob that changes the leve
117
117
 
118
118
  ### Default behavior
119
119
 
120
- - The CLI default value for `logging-config-path` is `logging_config.yaml`. If a file by that exact name exists in the current working directory, it will be loaded automatically — even without `logging-config-path` being set in `config.yaml`.
120
+ - The CLI default value for `logging-config-path` is `logging_config.yaml`, resolved against the **`.plain` file's directory** (like every defaulted path parameter). If a file by that name exists next to the plain file, it will be loaded automatically — even without `logging-config-path` being set in `config.yaml`. A value set in `config.yaml` resolves against the config file's directory; a value passed on the CLI resolves against the invocation directory.
121
121
  - If the file does not exist, the renderer silently keeps the baseline levels from step 1 above (no warning).
122
122
  - If the file exists but fails to parse / apply, the renderer warns (`Failed to load logging configuration from …`) and falls back to the baseline.
123
123
 
@@ -201,7 +201,7 @@ For each part:
201
201
  - `conformance-tests-folder` ≠ `conformance-tests-dest`.
202
202
  - `copy-conformance-tests: true` requires `conformance-tests-script`.
203
203
  - `log-file-name` is set ⇒ `log-to-file` is not `false`.
204
- - All `*-script` paths resolve on disk (absolute → relative to config dir → relative to renderer dir).
204
+ - All `*-script` paths resolve on disk (absolute / `~` paths as-is; relative paths against the config file's directory — no other fallback; the renderer fails fast on a missing script).
205
205
  - No script path crosses stacks (e.g. `backend/config.yaml` must not reference `run_unittests_js.sh`).
206
206
 
207
207
  ### Step 3 — Emit `config.yaml`