plain-forge 1.0.16 → 1.0.18

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 (36) hide show
  1. package/forge/rules/definitions.md +0 -2
  2. package/forge/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +14 -3
  3. package/forge/rules/exported-concepts.md +0 -2
  4. package/forge/rules/func-specs.md +0 -2
  5. package/forge/rules/impl-reqs.md +0 -2
  6. package/forge/rules/import-modules.md +0 -2
  7. package/forge/rules/integration-embedded-testing.md +9 -9
  8. package/forge/rules/integration-embedded.md +4 -6
  9. package/forge/rules/integration-standalone.md +0 -2
  10. package/forge/rules/integrations.md +0 -2
  11. package/forge/rules/line-length.md +0 -2
  12. package/forge/rules/linked-resources.md +0 -2
  13. package/forge/rules/required-concepts.md +0 -2
  14. package/forge/rules/requires-modules.md +0 -2
  15. package/forge/rules/test-reqs.md +0 -2
  16. package/forge/skills/implement-conformance-testing-script/SKILL.md +56 -43
  17. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +34 -21
  18. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +16 -5
  19. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +22 -11
  20. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +13 -2
  21. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +16 -6
  22. package/forge/skills/implement-prepare-environment-script/SKILL.md +23 -18
  23. package/forge/skills/implement-prepare-environment-script/assets/prepare_environment_java.sh +8 -4
  24. package/forge/skills/implement-prepare-environment-script/assets/prepare_environment_python.sh +8 -4
  25. package/forge/skills/implement-unit-testing-script/SKILL.md +14 -11
  26. package/forge/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +6 -1
  27. package/forge/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +6 -1
  28. package/forge/skills/implement-unit-testing-script/assets/run_unittests_java.sh +6 -3
  29. package/forge/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +6 -1
  30. package/forge/skills/implement-unit-testing-script/assets/run_unittests_python.sh +8 -4
  31. package/forge/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +6 -2
  32. package/forge/skills/init-config-file/SKILL.md +5 -5
  33. package/forge/skills/load-plain-reference/SKILL.md +97 -42
  34. package/forge/skills/plain-healthcheck/SKILL.md +1 -1
  35. package/forge/skills/run-codeplain/SKILL.md +12 -3
  36. package/package.json +1 -1
@@ -69,6 +69,17 @@ function Cleanup {
69
69
  if ($script:build_output -and (Test-Path $script:build_output)) {
70
70
  Remove-Item $script:build_output -Force -ErrorAction SilentlyContinue
71
71
  }
72
+
73
+ # Step out of any temp subfolder we may still be in before deleting it
74
+ Set-Location -Path ([System.IO.Path]::GetTempPath()) -ErrorAction SilentlyContinue
75
+
76
+ # Remove temporary build subfolders if they exist
77
+ if ($script:NODE_SUBFOLDER -and (Test-Path $script:NODE_SUBFOLDER)) {
78
+ Remove-Item -Path $script:NODE_SUBFOLDER -Recurse -Force -ErrorAction SilentlyContinue
79
+ }
80
+ if ($script:NODE_CONFORMANCE_TESTS_SUBFOLDER -and (Test-Path $script:NODE_CONFORMANCE_TESTS_SUBFOLDER)) {
81
+ Remove-Item -Path $script:NODE_CONFORMANCE_TESTS_SUBFOLDER -Recurse -Force -ErrorAction SilentlyContinue
82
+ }
72
83
  }
73
84
 
74
85
  # Check for and kill any existing Node server from previous runs
@@ -98,20 +109,21 @@ if ($args[2] -eq "-v" -or $args[2] -eq "--verbose") {
98
109
  $current_dir = Get-Location
99
110
 
100
111
  try {
101
- # Define the path to the subfolder
102
- $NODE_SUBFOLDER = "node_$BuildFolder"
112
+ # Define the path to the subfolder. It lives in the system temp directory;
113
+ # $BuildFolder may be an absolute path, so only its leaf name is used.
114
+ $script:NODE_SUBFOLDER = Join-Path ([System.IO.Path]::GetTempPath()) "node_$(Split-Path $BuildFolder -Leaf)"
103
115
 
104
116
  # Running React application
105
- Write-Host "### Step 1: Starting the React application in folder $NODE_SUBFOLDER..."
117
+ Write-Host "### Step 1: Starting the React application in folder $script:NODE_SUBFOLDER..."
106
118
 
107
119
  if ($env:VERBOSE -eq "1") {
108
- Write-Host "Preparing Node subfolder: $NODE_SUBFOLDER"
120
+ Write-Host "Preparing Node subfolder: $script:NODE_SUBFOLDER"
109
121
  }
110
122
 
111
123
  # Check if the node subfolder exists
112
- if (Test-Path $NODE_SUBFOLDER) {
124
+ if (Test-Path $script:NODE_SUBFOLDER) {
113
125
  # Delete all files and folders except "node_modules", "plain_modules", and "package-lock.json"
114
- Get-ChildItem -Path $NODE_SUBFOLDER -Force |
126
+ Get-ChildItem -Path $script:NODE_SUBFOLDER -Force |
115
127
  Where-Object {
116
128
  $_.Name -ne "node_modules" -and
117
129
  $_.Name -ne "plain_modules" -and
@@ -126,18 +138,18 @@ try {
126
138
  Write-Host "Subfolder does not exist. Creating it..."
127
139
  }
128
140
 
129
- New-Item -ItemType Directory -Path $NODE_SUBFOLDER -Force | Out-Null
141
+ New-Item -ItemType Directory -Path $script:NODE_SUBFOLDER -Force | Out-Null
130
142
  }
131
143
 
132
- Copy-Item -Path "$BuildFolder/*" -Destination $NODE_SUBFOLDER -Recurse -Force
144
+ Copy-Item -Path "$BuildFolder/*" -Destination $script:NODE_SUBFOLDER -Recurse -Force
133
145
 
134
146
  # Move to the subfolder
135
- if (-not (Test-Path $NODE_SUBFOLDER)) {
136
- Write-Host "Error: Node build folder '$NODE_SUBFOLDER' does not exist."
147
+ if (-not (Test-Path $script:NODE_SUBFOLDER)) {
148
+ Write-Host "Error: Node build folder '$script:NODE_SUBFOLDER' does not exist."
137
149
  exit $UNRECOVERABLE_ERROR_EXIT_CODE
138
150
  }
139
151
 
140
- Push-Location $NODE_SUBFOLDER
152
+ Push-Location $script:NODE_SUBFOLDER
141
153
 
142
154
  # Temporarily allow stderr output without throwing (npm may write warnings to stderr)
143
155
  # ForEach-Object converts ErrorRecord objects (from stderr) to plain strings to avoid verbose error formatting
@@ -245,17 +257,18 @@ try {
245
257
  # Move back to the original directory
246
258
  Set-Location $current_dir
247
259
 
248
- # Define the path to the conformance tests subfolder
249
- $NODE_CONFORMANCE_TESTS_SUBFOLDER = "node_$ConformanceTestsFolder"
260
+ # Define the path to the conformance tests subfolder. It lives in the system
261
+ # temp directory; $ConformanceTestsFolder may be absolute, so only its leaf name is used.
262
+ $script:NODE_CONFORMANCE_TESTS_SUBFOLDER = Join-Path ([System.IO.Path]::GetTempPath()) "node_$(Split-Path $ConformanceTestsFolder -Leaf)"
250
263
 
251
264
  if ($env:VERBOSE -eq "1") {
252
- Write-Host "Preparing conformance tests Node subfolder: $NODE_CONFORMANCE_TESTS_SUBFOLDER"
265
+ Write-Host "Preparing conformance tests Node subfolder: $script:NODE_CONFORMANCE_TESTS_SUBFOLDER"
253
266
  }
254
267
 
255
268
  # Check if the conformance tests node subfolder exists
256
- if (Test-Path $NODE_CONFORMANCE_TESTS_SUBFOLDER) {
269
+ if (Test-Path $script:NODE_CONFORMANCE_TESTS_SUBFOLDER) {
257
270
  # Delete all files and folders except "node_modules", "plain_modules", and "package-lock.json"
258
- Get-ChildItem -Path $NODE_CONFORMANCE_TESTS_SUBFOLDER -Force |
271
+ Get-ChildItem -Path $script:NODE_CONFORMANCE_TESTS_SUBFOLDER -Force |
259
272
  Where-Object {
260
273
  $_.Name -ne "node_modules" -and
261
274
  $_.Name -ne "plain_modules" -and
@@ -270,18 +283,18 @@ try {
270
283
  Write-Host "Subfolder does not exist. Creating it..."
271
284
  }
272
285
 
273
- New-Item -ItemType Directory -Path $NODE_CONFORMANCE_TESTS_SUBFOLDER -Force | Out-Null
286
+ New-Item -ItemType Directory -Path $script:NODE_CONFORMANCE_TESTS_SUBFOLDER -Force | Out-Null
274
287
  }
275
288
 
276
- Copy-Item -Path "$ConformanceTestsFolder/*" -Destination $NODE_CONFORMANCE_TESTS_SUBFOLDER -Recurse -Force
289
+ Copy-Item -Path "$ConformanceTestsFolder/*" -Destination $script:NODE_CONFORMANCE_TESTS_SUBFOLDER -Recurse -Force
277
290
 
278
291
  # Move to the subfolder with Cypress tests
279
- if (-not (Test-Path $NODE_CONFORMANCE_TESTS_SUBFOLDER)) {
280
- Write-Host "Error: conformance tests Node folder '$NODE_CONFORMANCE_TESTS_SUBFOLDER' does not exist."
292
+ if (-not (Test-Path $script:NODE_CONFORMANCE_TESTS_SUBFOLDER)) {
293
+ Write-Host "Error: conformance tests Node folder '$script:NODE_CONFORMANCE_TESTS_SUBFOLDER' does not exist."
281
294
  exit $UNRECOVERABLE_ERROR_EXIT_CODE
282
295
  }
283
296
 
284
- Push-Location $NODE_CONFORMANCE_TESTS_SUBFOLDER
297
+ Push-Location $script:NODE_CONFORMANCE_TESTS_SUBFOLDER
285
298
 
286
299
  # Temporarily allow stderr output without throwing (npm may write warnings to stderr)
287
300
  # ForEach-Object converts ErrorRecord objects (from stderr) to plain strings to avoid verbose error formatting
@@ -23,7 +23,15 @@ $ConformanceTestsFolder = $args[1]
23
23
 
24
24
  $current_dir = Get-Location
25
25
 
26
- $GO_BUILD_SUBFOLDER = "go_$BuildFolder"
26
+ # Resolve conformance tests folder to an absolute path so it can be used
27
+ # from the build subfolder (where we'll Push-Location to next).
28
+ if (-not [System.IO.Path]::IsPathRooted($ConformanceTestsFolder)) {
29
+ $ConformanceTestsFolder = Join-Path $current_dir $ConformanceTestsFolder
30
+ }
31
+
32
+ # Working folder lives in the system temp directory. $BuildFolder may be an
33
+ # absolute path, so only its leaf name is used to build the working folder name.
34
+ $GO_BUILD_SUBFOLDER = Join-Path ([System.IO.Path]::GetTempPath()) "go_$(Split-Path $BuildFolder -Leaf)"
27
35
 
28
36
  if ($env:VERBOSE -eq "1") {
29
37
  Write-Host "Preparing Go build subfolder: $GO_BUILD_SUBFOLDER"
@@ -60,9 +68,9 @@ try {
60
68
  go get
61
69
 
62
70
  # Move to conformance tests folder
63
- Set-Location "$current_dir/$ConformanceTestsFolder"
71
+ Set-Location $ConformanceTestsFolder
64
72
  if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) {
65
- Write-Host "Error: Conformance tests folder '$current_dir/$ConformanceTestsFolder' does not exist."
73
+ Write-Host "Error: Conformance tests folder '$ConformanceTestsFolder' does not exist."
66
74
  exit $UNRECOVERABLE_ERROR_EXIT_CODE
67
75
  }
68
76
 
@@ -75,7 +83,7 @@ try {
75
83
  }
76
84
 
77
85
  # Move back to build directory
78
- Set-Location "$current_dir/$GO_BUILD_SUBFOLDER"
86
+ Set-Location $GO_BUILD_SUBFOLDER
79
87
 
80
88
  # Execute Go lang conformance tests
81
89
  Write-Host "Running Golang conformance tests...`n"
@@ -83,7 +91,7 @@ try {
83
91
  # Temporarily allow stderr output without throwing (Go may write to stderr)
84
92
  # ForEach-Object converts ErrorRecord objects (from stderr) to plain strings to avoid verbose error formatting
85
93
  $ErrorActionPreference = 'Continue'
86
- $output = go run "$current_dir/$ConformanceTestsFolder/conformance_tests.go" 2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { $_.Exception.Message } else { $_ } } | Out-String
94
+ $output = go run (Join-Path $ConformanceTestsFolder "conformance_tests.go") 2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { $_.Exception.Message } else { $_ } } | Out-String
87
95
  $exit_code = $LASTEXITCODE
88
96
  $ErrorActionPreference = 'Stop'
89
97
 
@@ -97,4 +105,7 @@ try {
97
105
  exit $exit_code
98
106
  } finally {
99
107
  Pop-Location
108
+ if (Test-Path $GO_BUILD_SUBFOLDER) {
109
+ Remove-Item -Path $GO_BUILD_SUBFOLDER -Recurse -Force -ErrorAction SilentlyContinue
110
+ }
100
111
  }
@@ -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
  }