plain-forge 1.0.1

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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +247 -0
  3. package/bin/cli.mjs +143 -0
  4. package/forge/docs/.gitkeep +0 -0
  5. package/forge/rules/definitions.md +57 -0
  6. package/forge/rules/exported-concepts.md +39 -0
  7. package/forge/rules/func-specs.md +72 -0
  8. package/forge/rules/impl-reqs.md +50 -0
  9. package/forge/rules/import-modules.md +51 -0
  10. package/forge/rules/required-concepts.md +45 -0
  11. package/forge/rules/requires-modules.md +59 -0
  12. package/forge/rules/test-reqs.md +47 -0
  13. package/forge/skills/add-acceptance-test/SKILL.md +98 -0
  14. package/forge/skills/add-concept/SKILL.md +67 -0
  15. package/forge/skills/add-feature/SKILL.md +136 -0
  16. package/forge/skills/add-functional-spec/SKILL.md +81 -0
  17. package/forge/skills/add-functional-specs/SKILL.md +115 -0
  18. package/forge/skills/add-implementation-requirement/SKILL.md +73 -0
  19. package/forge/skills/add-resource/SKILL.md +108 -0
  20. package/forge/skills/add-template/SKILL.md +65 -0
  21. package/forge/skills/add-test-requirement/SKILL.md +68 -0
  22. package/forge/skills/analyze-2-func-specs/SKILL.md +102 -0
  23. package/forge/skills/analyze-func-specs/SKILL.md +124 -0
  24. package/forge/skills/analyze-if-func-spec-too-complex/SKILL.md +152 -0
  25. package/forge/skills/break-down-func-spec/SKILL.md +156 -0
  26. package/forge/skills/check-plain-env/SKILL.md +288 -0
  27. package/forge/skills/consolidate-concepts/SKILL.md +193 -0
  28. package/forge/skills/create-import-module/SKILL.md +98 -0
  29. package/forge/skills/create-requires-module/SKILL.md +104 -0
  30. package/forge/skills/debug-specs/SKILL.md +189 -0
  31. package/forge/skills/forge-integration/SKILL.md +443 -0
  32. package/forge/skills/forge-plain/SKILL.md +333 -0
  33. package/forge/skills/implement-conformance-testing-script/SKILL.md +247 -0
  34. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +324 -0
  35. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +100 -0
  36. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +102 -0
  37. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +92 -0
  38. package/forge/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +100 -0
  39. package/forge/skills/implement-prepare-environment-script/SKILL.md +242 -0
  40. package/forge/skills/implement-prepare-environment-script/assets/prepare_environment_java.sh +42 -0
  41. package/forge/skills/implement-prepare-environment-script/assets/prepare_environment_python.sh +81 -0
  42. package/forge/skills/implement-unit-testing-script/SKILL.md +133 -0
  43. package/forge/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +82 -0
  44. package/forge/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +68 -0
  45. package/forge/skills/implement-unit-testing-script/assets/run_unittests_java.sh +45 -0
  46. package/forge/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +76 -0
  47. package/forge/skills/implement-unit-testing-script/assets/run_unittests_python.sh +90 -0
  48. package/forge/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +83 -0
  49. package/forge/skills/init-config-file/SKILL.md +261 -0
  50. package/forge/skills/init-plain-project/SKILL.md +124 -0
  51. package/forge/skills/load-plain-reference/SKILL.md +646 -0
  52. package/forge/skills/plain-healthcheck/SKILL.md +132 -0
  53. package/forge/skills/refactor-module/SKILL.md +197 -0
  54. package/forge/skills/resolve-spec-conflict/SKILL.md +88 -0
  55. package/forge/skills/run-codeplain/SKILL.md +540 -0
  56. package/package.json +42 -0
@@ -0,0 +1,242 @@
1
+ ---
2
+ name: implement-prepare-environment-script
3
+ description: >-
4
+ Implement a prepare-environment script (Bash on macOS/Linux, PowerShell on
5
+ Windows) for an arbitrary programming language, following the same conceptual
6
+ pattern as the bundled Java reference script in assets/. Use when the user
7
+ wants to add a one-time per-build setup step (install deps, pre-build
8
+ artifacts, populate caches) for a new language (Python, Node.js, Go, Rust,
9
+ Flutter, etc.) to a ***plain project, or wants to regenerate / adapt the
10
+ existing Java runner.
11
+ ---
12
+
13
+ # Implement Prepare Environment Script
14
+
15
+ This skill produces a single executable script that performs the **one-time per-build setup** that precedes a conformance-test run, following a consistent, language-agnostic pattern.
16
+
17
+ The reference implementation is [assets/prepare_environment_java.sh](assets/prepare_environment_java.sh) and [assets/prepare_environment_python.sh](assets/prepare_environment_python.sh). Read it first — every script you produce must be a faithful translation of that pattern into the target language's tooling **and** the user's shell environment.
18
+
19
+ ## What a prepare-environment script is for
20
+
21
+ `prepare_environment_<lang>` is an **optional** sibling of [`run_conformance_tests_<lang>`](../implement-conformance-testing-script/SKILL.md). It runs **immediately before** the conformance script and exists to:
22
+
23
+ 1. **Stage the build** into a working folder (the same one the conformance script will use).
24
+ 2. **Pre-warm the dependency cache / build artifacts** so the conformance script can start cold without re-downloading or re-compiling anything.
25
+
26
+ When this script exists for a project, the corresponding conformance script's own dependency-install phase degrades to "activate only" (see [`implement-conformance-testing-script/SKILL.md`](../implement-conformance-testing-script/SKILL.md) → "Skipping setup when `prepare_environment` exists"). When it doesn't exist, the conformance script does the setup inline.
27
+
28
+ ### Why this script exists at all (the structural reason)
29
+
30
+ The conformance test runner is invoked **once per functional spec** by the renderer — not once per render. Each functional spec in a module has its own `conformance_tests/<module>/<spec>/` folder, and after the renderer finishes generating code for a new spec, it runs the conformance tests of **every previous spec** in the same module to detect regressions. For a module with N functional specs, the conformance script is invoked roughly N times on every render.
31
+
32
+ Without a prepare script, every one of those N invocations does the full dependency install (Python venv + `pip install`, full Maven dependency tree, `npm ci`, `cargo build`, ...) from scratch. That cost — paid N times per render — dominates the wall-clock time of rendering a non-trivial project.
33
+
34
+ `prepare_environment_<lang>` exists to **amortize that cost to one** install per render:
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`).
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
+ - Net effect: install cost goes from `N × install-cost` to `1 × install-cost + N × cheap-attach-cost`.
39
+
40
+ This is the **whole reason** the prepare-then-conformance split exists. If a project has so few functional specs that the install overhead is negligible, generating a prepare script is wasted effort — the install-inline variant of the conformance script is fine. If a project has many specs (or expensive dependencies, GPU builds, browser binaries, etc.), prepare is mandatory in practice. The user decides per project; this skill is the tool to execute that decision.
41
+
42
+ ### `prepare_environment` is conformance-only — NOT for unit tests
43
+
44
+ > **Common and costly mistake:** assuming `prepare_environment_<lang>` is a generic "warm up the environment for all the testing scripts" step that the unit-test runner also depends on. It is not.
45
+
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
+
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.
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
+ - 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
+
53
+ When authoring `prepare_environment_<lang>`, scope it strictly to what the **conformance** script needs. Do not bake in dependency installs the unit-test runner needs but conformance doesn't; do not stage files the unit-test runner reads; do not assume the unit-test runner will be the one consuming what you produce. If you find yourself reaching for those, **stop** — the right answer is to leave `prepare_environment` alone and let the unit-test runner handle its own dependencies inline.
54
+
55
+ ## How prepare-environment scripts differ from the others
56
+
57
+ This script shares most of its structure with its two siblings ([`run_unittests_<lang>`](../implement-unit-testing-script/SKILL.md) and [`run_conformance_tests_<lang>`](../implement-conformance-testing-script/SKILL.md)) but with these differences:
58
+
59
+ 1. **One positional argument: `<build_folder>`.** No conformance tests folder — that's the conformance script's input, not this one's.
60
+ 2. **No test execution step.** This script only stages and installs/builds. It never runs unit tests or conformance tests.
61
+ 3. **No "no tests discovered" guard.** Same reason — no tests are run here.
62
+ 4. **Side effects must be visible to the conformance script.** Anything this script does (working-folder name, dependency isolation location, installed artifacts) must match exactly what the conformance script expects to find. See [Coordination contract](#coordination-contract).
63
+
64
+ Everything else — toolchain check, build staging, dependency isolation, exit codes — is the same.
65
+
66
+ ## Pick the Shell First
67
+
68
+ Before writing anything, decide which shell flavor the script must target — it depends on the **host machine running this skill**, not on the language. **Detect the host OS proactively; do not default to Bash.**
69
+
70
+ | Host OS | Emit | How to detect |
71
+ |---|---|---|
72
+ | macOS | `.sh` | `uname -s` returns `Darwin` |
73
+ | Linux (incl. WSL, CI runners) | `.sh` | `uname -s` returns `Linux` |
74
+ | Native Windows (PowerShell, cmd) | `.ps1` | `$OS` env var contains `Windows_NT`, or `uname -s` returns `MINGW*` / `MSYS*` / `CYGWIN*` (Git Bash / MSYS2 / Cygwin shells on Windows) |
75
+
76
+ Run `uname -s 2>/dev/null || echo "$OS"` if unsure — don't ask the user before checking.
77
+
78
+ If the project will be used on **both** macOS/Linux and Windows by different team members or CI runners, generate **both** `.sh` and `.ps1` versions of this script — they're mechanical translations of each other, share exit codes and isolation paths, and the orchestrator (or the user's CI) picks the right one at runtime. The corresponding `run_conformance_tests_<lang>` script must be produced in matching pairs too.
79
+
80
+ If you genuinely can't tell (e.g. running in a sandbox with no shell access), ask the user — but only after the detection above failed.
81
+
82
+ The same pattern applies to both shell flavors. Only the syntax changes.
83
+
84
+ ## The Pattern
85
+
86
+ Every prepare-environment script must implement these steps **in this order**:
87
+
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.
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.
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.
96
+
97
+ ### The build folder is read-only — hard rule
98
+
99
+ The source build folder passed in as `$1` is **input only**. Prepare reads from it once in step 4 to populate the working folder, and after that the script must never:
100
+
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
+ - 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>`),
104
+ - create logs, caches, or temp files inside it.
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.
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.
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.
111
+
112
+ ## Coordination contract
113
+
114
+ This is the most important part of the skill. A prepare-environment script that doesn't agree with its conformance sibling on **where** it puts things is worse than no prepare script at all — it costs time and creates the *appearance* of a warm environment without actually warming the right one.
115
+
116
+ The two scripts must agree on:
117
+
118
+ | What | Why it matters |
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. |
121
+ | **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
+ | **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
+ | **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. |
124
+
125
+ When in doubt, **read the conformance script first** and mirror its assumptions exactly.
126
+
127
+ ## Conventions
128
+
129
+ Shared across both shell flavors:
130
+
131
+ - **Exit codes:**
132
+ - `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
+ - `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
+ - **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
+ - **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
+ - **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.
138
+ - **Print clear section banners.** Each pattern step gets a banner like `===== [4/6] Copy build into working folder =====` so a long log can be navigated by eye.
139
+ - **Print resolved absolute paths**, not just the relative names. Operators reading the log on a different machine need to know where the venv / `.m2` / `.gocache` / `.cargo` / `.pub-cache` actually landed.
140
+ - **Print the toolchain's own `--version` output** (and the path it resolved from) during step 1, even on success. This is the single most useful piece of forensic data when prepare succeeds locally but fails in CI.
141
+ - **Surface the install / pre-build output verbatim** — do not pipe it to `/dev/null`, do not redirect to a log file inside the working folder. The operator must see every dependency-resolver line, every compile-warning, every artifact-install line in the script's own output stream. Drop the `VERBOSE`-gated wrapping from the Java reference — prepare is the slowest phase and the place where forensic data is most valuable.
142
+ - **On failure, print what was about to run before exiting `69`** — the exact command, the working directory, the relevant environment variables (`PYTHONPATH`, `NODE_PATH`, `GOMODCACHE`, `CARGO_HOME`, `MAVEN_OPTS`, `PUB_CACHE`, `PATH`).
143
+ - **Print a final summary line** that names the language, the working folder, the isolation root, the wall-clock duration, and exit code `0` so the operator knows exactly what was prepared and where conformance will look for it.
144
+ Verbosity is not noise here — a chatty script that documents itself in its own output is the difference between a 30-second triage and a 30-minute one. Never trade verbosity for terseness.
145
+ - **Time the dependency setup** with `date +%s.%N` (Bash) / `Get-Date` (PowerShell) and print a duration line at the end. This is the slowest phase and the whole reason this script exists; the duration tells you whether it's actually saving time. The duration line is part of the final summary required by the verbose-logging rule above.
146
+
147
+ ### Dependency isolation
148
+
149
+ The dependency environment must live **inside** `$WORKING_FOLDER` so the conformance script can find it and so concurrent runs of other languages / projects can't interfere. Pick the most idiomatic isolation mechanism for the language — and make sure it matches what the conformance script reads from:
150
+
151
+ | Language | Isolation mechanism | Prepare command (run inside `$WORKING_FOLDER`) |
152
+ |---|---|---|
153
+ | Python | `venv` at `./.venv` | `python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt` |
154
+ | Node.js | local `./node_modules` (default) | `npm ci` (preferred) or `npm install` |
155
+ | Java | project-scoped Maven repo at `./.m2` | `mvn -Dmaven.repo.local="$(pwd)/.m2" install -DskipTests` (builds and installs the project's own jar into the repo so dependent test projects can resolve it) |
156
+ | Go | module cache at `./.gocache` | `GOMODCACHE="$PWD/.gocache" go mod download && GOMODCACHE="$PWD/.gocache" go build ./...` |
157
+ | Rust | cargo home at `./.cargo` | `CARGO_HOME="$PWD/.cargo" cargo build --tests` (compiles deps + tests in one shot) |
158
+ | Flutter | pub cache at `./.pub-cache` | `PUB_CACHE="$PWD/.pub-cache" flutter pub get && flutter precache` |
159
+
160
+ Notes:
161
+
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.
163
+ - **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
+ - **Python and Node.js only need to install** — they're interpreted/JIT-compiled at test time, so `pip install` / `npm ci` is sufficient.
165
+ - **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.
166
+ - **Treat install failures as `exit 69`.** Unlike the conformance script (which propagates the test command's exit code), prepare has no notion of "the user's tests legitimately failed" — every failure here means the environment isn't usable, period.
167
+
168
+ ### Bash specifics
169
+
170
+ - **Shebang:** `#!/bin/bash`.
171
+ - **File naming:** `prepare_environment_<lang>.sh`, placed in `assets/`.
172
+ - **Argument:** `$1`.
173
+ - **Make it executable:** `chmod +x assets/prepare_environment_<lang>.sh`.
174
+ - **`cd` failure check:** use the `cd ... 2>/dev/null` + `[ $? -ne 0 ]` pattern from the reference. Put the success log line *after* the failure check, not before — otherwise it lies on failure.
175
+
176
+ ### PowerShell specifics
177
+
178
+ - **No shebang.** Use a `param([Parameter(Mandatory=$true)][string]$BuildFolder)` block at the top instead.
179
+ - **File naming:** `prepare_environment_<lang>.ps1`, placed in `assets/`.
180
+ - **Exit codes:** use `exit 69` etc. (PowerShell honors them just like Bash).
181
+ - **Toolchain check:** prefer `Get-Command <tool> -ErrorAction SilentlyContinue` and, where a specific version is needed, parse the tool's `--version` output.
182
+ - **Filesystem:** use `Test-Path`, `Remove-Item -Recurse -Force`, `New-Item -ItemType Directory`, `Copy-Item -Recurse`, `Set-Location`. Quote paths to handle spaces.
183
+ - **No `chmod` step needed.** If execution policy is likely to block the script, mention `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` to the user — don't bake it into the script.
184
+
185
+ ## Workflow
186
+
187
+ 1. **Detect the host OS** to pick the script flavor. Run `uname -s 2>/dev/null || echo "$OS"` and apply the rules in [Pick the Shell First](#pick-the-shell-first): `Darwin`/`Linux` → `.sh`, `Windows_NT`/`MINGW*`/`MSYS*`/`CYGWIN*` → `.ps1`. If the project targets both macOS/Linux **and** Windows (multi-OS team or CI), plan to produce **both** `.sh` and `.ps1` files — repeat steps 3–8 for each.
188
+ 2. Confirm the target **language**, **dependency manifest** (`pom.xml`, `requirements.txt` / `pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`, ...), and — critically — **read the corresponding `run_conformance_tests_<lang>` script first** if one already exists, so you know what isolation paths and toolchain versions to mirror. Ask if any is unclear.
189
+ 3. Read [assets/prepare_environment_java.sh](assets/prepare_environment_java.sh) to refresh the exact structure. Note: that reference still has divergences from the contract above (see Anti-Patterns) — follow the contract, not the bugs.
190
+ 4. Translate each of the six pattern steps into the equivalent commands for the target language **and** shell. The toolchain check and dependency install/build are the language-specific parts; the rest is mechanical translation between Bash and PowerShell syntax.
191
+ 5. Pick the dependency-isolation mechanism from the [Dependency isolation](#dependency-isolation) table. **Verify it matches the path used by the corresponding `run_conformance_tests_<lang>` script.**
192
+ 6. Save the new script to the appropriate `test_scripts/` location (e.g. `test_scripts/prepare_environment_<lang>.sh` / `.ps1`). For Bash, `chmod +x` it.
193
+ 7. **Update `config.yaml` to reference the new script.** Add or update the `prepare-environment-script:` key with the path to the newly created script (e.g., `prepare-environment-script: test_scripts/prepare_environment_<lang>.sh`). This is mandatory — the `codeplain` renderer needs this reference to invoke the prepare script before running conformance tests.
194
+ 8. **Link the script into the base `.plain` files as a linked resource.** Once the script exists on disk, it must be referenced from the project's base `.plain` module(s) — typically the one carrying `***test reqs***` for the conformance-testing strategy — so the renderer sees the script's contents as ground truth for the prepare contract. Use the `add-resource` skill to add a standard markdown link (e.g. `[prepare environment](test_scripts/prepare_environment_<lang>.sh)`) inside the relevant section of the spec, with the path resolved relative to the `.plain` file. The script is a single text file, so it satisfies the linked-resource contract directly — do not link the `test_scripts/` directory, do not paraphrase the script into the spec, and do not duplicate its commands inline. If multiple base `.plain` modules reference the prepare contract (e.g. a host module and an integration module that inherits from it), link the script from each module that needs it; the linked resource lives next to the spec that owns the test req, not at the top of the chain by default. After linking, read the spec back to confirm the link path resolves to the script you just wrote.
195
+ 9. **Reconcile the conformance script (see next section).** This is mandatory whenever a matching `run_conformance_tests_<lang>` already exists in the project.
196
+ 10. After both scripts are in place and `config.yaml` is updated, do a paired re-read: open prepare and conformance side by side and confirm they agree on working folder name, isolation path, and toolchain version.
197
+
198
+ ## Reconcile the existing conformance script
199
+
200
+ Adding a `prepare_environment_<lang>` script changes the contract for the corresponding `run_conformance_tests_<lang>` script — anything prepare now handles must be **removed** from conformance, otherwise prepare's work is wiped (by re-staging) or duplicated (by re-installing). Run this reconciliation **every time** this skill is used.
201
+
202
+ ### Step-by-step
203
+
204
+ 1. **Look for an existing conformance script** in the project. Check the conventional locations (`test_scripts/run_conformance_tests_<lang>.sh` / `.ps1`, or wherever the project's `config.yaml` points its `conformance-tests-script:` key).
205
+ 2. **If it doesn't exist → stop. Nothing to reconcile.** The conformance script will be generated later by [`implement-conformance-testing-script`](../implement-conformance-testing-script/SKILL.md), which already knows about the activate-only variant.
206
+ 3. **If it does exist → patch it.** Identify and remove the steps that prepare now owns:
207
+
208
+ | Step in conformance | If prepare exists, you must... |
209
+ |---|---|
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` |
211
+ | 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
+ | 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. |
213
+ | Test execution + "no tests discovered" guard + exit-code propagation | **Keep unchanged.** |
214
+
215
+ 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.
216
+ 5. **Run a smoke check**: `prepare_environment_<lang>.sh <build_folder> && run_conformance_tests_<lang>.sh <build_folder> <conformance_tests_folder>` should succeed end-to-end. If conformance fails with "missing prepared environment" right after a successful prepare, the two scripts disagree on either the working-folder path or the isolation location — go back to [Coordination contract](#coordination-contract).
217
+
218
+ ### When to skip this reconciliation
219
+
220
+ - **The conformance script doesn't exist yet.** Nothing to reconcile.
221
+ - **The conformance script already shows no signs of inline staging or install.** It was previously generated as the activate-only variant — leave it alone.
222
+ - **The user explicitly asks to keep prepare and conformance independent** (e.g. so conformance can run standalone without prepare). Document this clearly in a comment at the top of both scripts and skip the reconciliation. Note that this loses all the speedup prepare was meant to provide.
223
+
224
+ ## Anti-Patterns
225
+
226
+ - **(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.
228
+ - **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
+ - **Don't use a different working folder name than the conformance script.** They must match exactly. If you change one, change the other.
230
+ - **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
+ - **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
+ - **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
+ - **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.
235
+ - **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
+ - **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
+ - **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.
238
+ - **Don't leave an existing `run_conformance_tests_<lang>` script untouched after generating prepare.** If the conformance script still does its own staging and install, prepare's work is wiped (by the `rm -rf`) or duplicated (by re-running install) on every run — defeating the purpose of this skill entirely. Always run the [Reconcile the existing conformance script](#reconcile-the-existing-conformance-script) step.
239
+ - **Don't default to Bash without checking the host OS.** A `.sh` script on native Windows (outside Git Bash / WSL) won't even run, and a `.ps1` script on macOS/Linux is equally useless. Always run the detection in [Pick the Shell First](#pick-the-shell-first) before deciding the file extension. If the project supports both, produce both files — and remember to reconcile the matching conformance script in **both** flavors too.
240
+ - **Don't forget to update `config.yaml`.** After creating the prepare environment script, always add or update the `prepare-environment-script:` key in `config.yaml` to reference the new script. Without this entry, the `codeplain` renderer won't know to invoke the prepare script before running conformance tests, and the entire optimization will be bypassed.
241
+ - **Don't forget to link the script as a resource in the base `.plain` files.** A script that is referenced only from `config.yaml` is invisible to the spec contract — the renderer treats `prepare-environment-script:` as a build-system pointer, not as part of the test-req contract the model reads. Use the `add-resource` skill to add a markdown link to the script from the `.plain` module that owns the conformance / prepare test reqs (see Workflow step 8). Omitting the linked resource means the model authors and reviews conformance-test code without ever seeing the actual environment-prep contract it has to satisfy — and it cannot reason about which isolation paths conformance must activate.
242
+ - **Don't write a terse script.** Silent steps, `>/dev/null` redirects on the install / pre-build output, missing `--version` prints, absent failure banners, and a missing final summary line all make the script harder to debug than the code it is preparing. Follow the *Logging — be as verbose as possible* rule under [Conventions](#conventions) literally: every step announces itself, every command is echoed, every failure prints what was about to run and where, and the final summary names the language, working folder, isolation root, and duration.
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+
3
+ # Check that Java 21 is installed
4
+ if ! /usr/libexec/java_home -v 21 >/dev/null 2>&1; then
5
+ printf "Error: Java 21 is not installed.\n"
6
+ exit 69
7
+ fi
8
+
9
+ export JAVA_HOME=$(/usr/libexec/java_home -v 21)
10
+ java --version
11
+
12
+ # Check if build folder name is provided
13
+ if [ -z "$1" ]; then
14
+ printf "Error: No build folder name provided.\n"
15
+ printf "Usage: $0 <build_folder_name>\n"
16
+ exit 1
17
+ fi
18
+
19
+ JAVA_BUILD_SUBFOLDER=.tmp/$1
20
+
21
+ if [ "${VERBOSE:-}" -eq 1 ] 2>/dev/null; then
22
+ printf "Copying generated code to main project folder: $JAVA_BUILD_SUBFOLDER\n"
23
+ fi
24
+
25
+ rm -rf $JAVA_BUILD_SUBFOLDER
26
+ mkdir -p $JAVA_BUILD_SUBFOLDER
27
+
28
+ cp -R $1/* $JAVA_BUILD_SUBFOLDER
29
+ printf "Copied from $1 to $JAVA_BUILD_SUBFOLDER...\n"
30
+
31
+ # Move to the subfolder
32
+ cd "$JAVA_BUILD_SUBFOLDER" 2>/dev/null
33
+ printf "Moved to $JAVA_BUILD_SUBFOLDER...\n"
34
+
35
+ if [ $? -ne 0 ]; then
36
+ printf "Error: Java build folder '$JAVA_BUILD_SUBFOLDER' does not exist.\n"
37
+ exit 2
38
+ fi
39
+
40
+ echo "Runinng maven install in the build folder..."
41
+
42
+ mvn clean install -DskipTests
@@ -0,0 +1,81 @@
1
+ #!/bin/bash
2
+ UNRECOVERABLE_ERROR_EXIT_CODE=69
3
+
4
+ # Check if build folder name is provided
5
+ if [ -z "$1" ]; then
6
+ printf "Error: No build folder name provided.\n"
7
+ printf "Usage: $0 <build_folder_name> <conformance_tests_folder>\n"
8
+ exit $UNRECOVERABLE_ERROR_EXIT_CODE
9
+ fi
10
+
11
+ # Check if conformance tests folder name is provided
12
+ if [ -z "$2" ]; then
13
+ printf "Error: No conformance tests folder name provided.\n"
14
+ printf "Usage: $0 <build_folder_name> <conformance_tests_folder>\n"
15
+ exit $UNRECOVERABLE_ERROR_EXIT_CODE
16
+ fi
17
+
18
+ # Try to find Python interpreter (python3 first, then python)
19
+ if command -v python3 &> /dev/null; then
20
+ PYTHON_CMD="python3"
21
+ elif command -v python &> /dev/null; then
22
+ PYTHON_CMD="python"
23
+ else
24
+ printf "Error: Python interpreter not found. Please install Python.\n"
25
+ exit $UNRECOVERABLE_ERROR_EXIT_CODE
26
+ fi
27
+
28
+ current_dir=$(pwd)
29
+
30
+ PYTHON_BUILD_SUBFOLDER=".tmp/$1"
31
+
32
+ if [ "${VERBOSE:-}" -eq 1 ] 2>/dev/null; then
33
+ printf "Preparing Python build subfolder: $PYTHON_BUILD_SUBFOLDER\n"
34
+ fi
35
+
36
+ rm -rf $PYTHON_BUILD_SUBFOLDER
37
+ mkdir -p $PYTHON_BUILD_SUBFOLDER
38
+
39
+ cp -R $1/* $PYTHON_BUILD_SUBFOLDER
40
+
41
+ # Move to the subfolder
42
+ cd "$PYTHON_BUILD_SUBFOLDER" 2>/dev/null
43
+
44
+ if [ $? -ne 0 ]; then
45
+ printf "Error: Python build folder '$PYTHON_BUILD_SUBFOLDER' does not exist.\n"
46
+ exit $UNRECOVERABLE_ERROR_EXIT_CODE
47
+ fi
48
+
49
+ printf "Creating and activating virtual environment...\n"
50
+
51
+ # Time the virtual environment creation and activation
52
+ start_time=$(date +%s.%N)
53
+
54
+ VENV_DIR=".venv"
55
+
56
+ if ! $PYTHON_CMD -m venv "$VENV_DIR"; then
57
+ printf "Error: Failed to create virtual environment in '$VENV_DIR'.\n"
58
+ exit $UNRECOVERABLE_ERROR_EXIT_CODE
59
+ fi
60
+
61
+ # shellcheck disable=SC1091
62
+ source "$VENV_DIR/bin/activate"
63
+
64
+ if [ $? -ne 0 ]; then
65
+ printf "Error: Failed to activate virtual environment at '$VENV_DIR/bin/activate'.\n"
66
+ exit $UNRECOVERABLE_ERROR_EXIT_CODE
67
+ fi
68
+
69
+ # Install requirements if requirements.txt exists
70
+ if [ -f "requirements.txt" ]; then
71
+ pip install --upgrade pip
72
+ pip install -r requirements.txt
73
+ else
74
+ echo "Warning: requirements.txt not found. Cannot proceed with setting up requirements. The requirements may also already be installed"
75
+ fi
76
+
77
+ end_time=$(date +%s.%N)
78
+
79
+ # Calculate and display the time taken
80
+ duration=$(echo "$end_time - $start_time" | bc)
81
+ printf "Requirements setup completed in %.2f seconds\n\n" "$duration"
@@ -0,0 +1,133 @@
1
+ ---
2
+ name: implement-unit-testing-script
3
+ description: >-
4
+ Implement a unit-test runner script (Bash on macOS/Linux, PowerShell on
5
+ Windows) for an arbitrary programming language, following the same conceptual
6
+ pattern as the bundled Java reference script in assets/. Use when the user
7
+ wants to add a testing script for a new language (Python, Node.js, Go, Rust,
8
+ etc.) to a ***plain project, or wants to regenerate / adapt the existing
9
+ Java runner.
10
+ ---
11
+
12
+ # Implement Unit Testing Script
13
+
14
+ This skill produces a single executable script that runs the unit tests for a generated build folder, following a consistent, language-agnostic pattern.
15
+
16
+ The reference implementation is [assets/run_unittests_java.sh](assets/run_unittests_java.sh). Read it first — every script you produce must be a faithful translation of that pattern into the target language's tooling **and** the user's shell environment. There are also Windows PowerShell equivalents of these scripts in [assets/run_unittests_*.ps1](assets/run_unittests_*.ps1).
17
+
18
+ ## Pick the Shell First
19
+
20
+ Before writing anything, decide which shell flavor the script must target — it depends on the user's environment, not on the language:
21
+
22
+ - **Bash (`.sh`)** — macOS, Linux, WSL, CI runners on Linux. Default unless the user is on native Windows.
23
+ - **PowerShell (`.ps1`)** — native Windows / PowerShell-only environments.
24
+
25
+ If you can't tell from the project (no obvious OS hints, no existing scripts), ask the user.
26
+
27
+ The same seven-step pattern applies to both. Only the syntax changes.
28
+
29
+ ## The Pattern
30
+
31
+ Every testing script must implement these steps **in this order**:
32
+
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.
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.
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
+
41
+ ### The build folder is read-only — hard rule
42
+
43
+ The source build folder passed in as `$1` is **input only**. The script must never:
44
+
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
+ - 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),
48
+ - create logs, caches, build outputs, or temp files inside it.
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.
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.
53
+
54
+ ## Conventions
55
+
56
+ Shared across both shell flavors:
57
+
58
+ - **Exit codes:**
59
+ - `1` — bad usage (missing argument).
60
+ - `2` — filesystem problem (couldn't enter the working folder).
61
+ - `69` — required toolchain / runtime is not installed.
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.
64
+ - **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
+ - **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
+ - **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.
67
+ - **Print clear section banners.** Each of the seven steps gets a banner like `===== [3/7] Working directory setup =====` so a long log can be navigated by eye.
68
+ - **Print resolved absolute paths**, not just the relative names. Operators reading the log on a different machine need to know where things actually landed.
69
+ - **Print the toolchain's own `--version` output** (and the path it resolved from) during step 1, even on success. This is the single most useful piece of forensic data when a test passes locally but fails in CI.
70
+ - **Surface the install command's output verbatim** — do not pipe it to `/dev/null`, do not redirect to a log file inside the working folder. The operator must see every dependency-resolver line in the script's own output stream.
71
+ - **On failure, print what was about to run before exiting** — the exact command, the working directory, the relevant environment variables (`PYTHONPATH`, `NODE_PATH`, `GOMODCACHE`, `CARGO_HOME`, `MAVEN_OPTS`, `PATH`).
72
+ - **Print a final summary line** that names the test command, the exit code, and the working folder so the operator knows exactly what to re-run by hand if needed.
73
+ Verbosity is not noise here — a chatty script that documents itself in its own output is the difference between a 30-second triage and a 30-minute one. Never trade verbosity for terseness.
74
+
75
+ ### Dependency isolation
76
+
77
+ The dependency environment must live **inside** `$WORKING_FOLDER` so the test run can't be polluted by — or pollute — the user's global caches. Pick the most idiomatic isolation mechanism for the language:
78
+
79
+ | Language | Isolation mechanism | Install command (run inside `$WORKING_FOLDER`) | Test command |
80
+ |---|---|---|---|
81
+ | Python | `venv` at `./.venv` | `python3 -m venv .venv && ./.venv/bin/pip install -r requirements.txt` (or `pyproject.toml` / `uv sync` / `poetry install`) | `./.venv/bin/pytest` (or `./.venv/bin/python -m pytest`) |
82
+ | Node.js | local `./node_modules` (default) | `npm ci` (preferred) or `npm install` | `npm test` |
83
+ | Java | project-scoped Maven repo at `./.m2` | `mvn -Dmaven.repo.local=./.m2 dependency:resolve` (optional pre-warm) | `mvn -Dmaven.repo.local=./.m2 test` |
84
+ | Go | module cache at `./.gocache` | `GOMODCACHE="$PWD/.gocache" go mod download` (optional pre-warm) | `GOMODCACHE="$PWD/.gocache" go test ./...` |
85
+ | Rust | cargo home at `./.cargo` | `CARGO_HOME="$PWD/.cargo" cargo fetch` (optional pre-warm) | `CARGO_HOME="$PWD/.cargo" cargo test` |
86
+
87
+ Notes:
88
+
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
+ - **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
+ - **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
+ - **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.
93
+ - **Don't activate the venv** in Bash via `source .venv/bin/activate` — call `./.venv/bin/<tool>` directly. It's more portable and avoids subshell weirdness. In PowerShell, use `& .\.venv\Scripts\<tool>.exe` similarly.
94
+ - **Propagate the install exit code immediately.** In Bash: `<install cmd> || exit $?`. In PowerShell: check `$LASTEXITCODE` and `exit $LASTEXITCODE` if non-zero.
95
+
96
+ ### Bash specifics
97
+
98
+ - **Shebang:** `#!/bin/bash`.
99
+ - **File naming:** `run_unittests_<lang>.sh`, placed in `assets/`.
100
+ - **Argument:** `$1`.
101
+ - **Make it executable:** `chmod +x assets/run_unittests_<lang>.sh`.
102
+
103
+ ### PowerShell specifics
104
+
105
+ - **No shebang.** Use a `param([Parameter(Mandatory=$true)][string]$Subfolder)` block at the top instead.
106
+ - **File naming:** `run_unittests_<lang>.ps1`, placed in `assets/`.
107
+ - **Exit codes:** use `exit 69` etc. (PowerShell honors them just like Bash).
108
+ - **Toolchain check:** prefer `Get-Command <tool> -ErrorAction SilentlyContinue` and, where a specific version is needed, parse the tool's `--version` output.
109
+ - **Filesystem:** use `Test-Path`, `Remove-Item -Recurse -Force`, `New-Item -ItemType Directory`, `Copy-Item -Recurse`, `Set-Location`. Quote paths to handle spaces.
110
+ - **No `chmod` step needed.** If execution policy is likely to block the script, mention `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` to the user — don't bake it into the script.
111
+
112
+ ## Workflow
113
+
114
+ 1. Confirm the target **language**, **shell flavor** (Bash or PowerShell), and **dependency manifest** (`pom.xml`, `requirements.txt` / `pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`, ...). Ask if any is unclear.
115
+ 2. Read [assets/run_unittests_java.sh](assets/run_unittests_java.sh) to refresh the exact structure.
116
+ 3. Translate each of the seven steps above into the equivalent commands for the target language **and** shell. The toolchain check, dependency install, and test invocation are the language-specific parts; the rest is mechanical translation between Bash and PowerShell syntax.
117
+ 4. Pick the dependency-isolation mechanism from the [Dependency isolation](#dependency-isolation) table and use it consistently in both step 6 and step 7.
118
+ 5. Save the new script to `assets/run_unittests_<lang>.sh` or `assets/run_unittests_<lang>.ps1`. For Bash, `chmod +x` it.
119
+ 6. **Update `config.yaml` to reference the new script.** Add or update the `unit-tests-script:` key with the path to the newly created script (e.g., `unit-tests-script: test_scripts/run_unittests_<lang>.sh`). This is mandatory — the `codeplain` renderer needs this reference to invoke the unit test script during the development workflow.
120
+ 7. **Link the script into the base `.plain` files as a linked resource.** Once the script exists on disk, it must be referenced from the project's base `.plain` module(s) into its ***implementation reqs*** section using the `add-resource` skill. After linking, read the spec back to confirm the link path resolves to the script you just wrote.
121
+
122
+ ## Anti-Patterns
123
+
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
+ - 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.
127
+ - 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
+ - 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
+ - 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.
130
+ - Don't run the test command without first verifying the install step succeeded. A failed install followed by a "test" run produces misleading errors that look like test failures.
131
+ - **Don't forget to update `config.yaml`.** After creating the unit test script, always add or update the `unit-tests-script:` key in `config.yaml` to reference the new script. Without this entry, the `codeplain` renderer won't know where to find the unit test script.
132
+ - **Don't forget to link the script as a resource in the base `.plain` files.** A script that is referenced only from `config.yaml` is invisible to the spec contract — the renderer treats `unit-tests-script:` as a build-system pointer, not as part of the test-req contract the model reads. Use the `add-resource` skill to add a markdown link to the script from the `.plain` module that owns the unit-testing test reqs (see Workflow step 7). Omitting the linked resource means the model authors and reviews test-related code without ever seeing the actual runner it has to satisfy.
133
+ - **Don't write a terse script.** Silent steps, `>/dev/null` redirects on the install output, missing `--version` prints, and absent failure banners all make the script harder to debug than the code it is testing. Follow the *Logging — be as verbose as possible* rule under [Conventions](#conventions) literally: every step announces itself, every command is echoed, every failure prints what was about to run and where.
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env pwsh
2
+
3
+ $ErrorActionPreference = 'Stop'
4
+
5
+ $UNRECOVERABLE_ERROR_EXIT_CODE = 69
6
+
7
+ if (-not $args[0]) {
8
+ Write-Host "Error: No source folder name provided."
9
+ Write-Host "Usage: $($MyInvocation.MyCommand.Name) <source_folder_name>"
10
+ exit $UNRECOVERABLE_ERROR_EXIT_CODE
11
+ }
12
+
13
+ if (-not (Get-Command flutter -ErrorAction SilentlyContinue)) {
14
+ Write-Host "Error: flutter is not available in PATH."
15
+ exit $UNRECOVERABLE_ERROR_EXIT_CODE
16
+ }
17
+
18
+ $SOURCE_FOLDER = $args[0]
19
+ $BUILD_SUBFOLDER = ".tmp/flutter_build_unittests"
20
+
21
+ Write-Host "Current directory: $(Get-Location)"
22
+ Write-Host "Source folder: $SOURCE_FOLDER"
23
+ Write-Host "--------------------------------"
24
+
25
+ if (Test-Path $BUILD_SUBFOLDER) {
26
+ Remove-Item -Path $BUILD_SUBFOLDER -Recurse -Force
27
+ }
28
+ New-Item -ItemType Directory -Path $BUILD_SUBFOLDER -Force | Out-Null
29
+
30
+ Copy-Item -Path "$SOURCE_FOLDER/*" -Destination "$BUILD_SUBFOLDER/" -Recurse -Force
31
+
32
+ if (-not (Test-Path $BUILD_SUBFOLDER)) {
33
+ exit $UNRECOVERABLE_ERROR_EXIT_CODE
34
+ }
35
+
36
+ Push-Location $BUILD_SUBFOLDER
37
+
38
+ try {
39
+ Write-Host "Resolving Flutter dependencies..."
40
+
41
+ if (Test-Path "pubspec.yaml") {
42
+ flutter pub get
43
+ } else {
44
+ Write-Host "Warning: pubspec.yaml not found. Dependencies might be missing."
45
+ }
46
+
47
+ Write-Host "Running Flutter unittests in $BUILD_SUBFOLDER..."
48
+
49
+ # Run flutter test with a timeout
50
+ $process = Start-Process -FilePath "flutter" -ArgumentList "test", "--reporter", "expanded" `
51
+ -NoNewWindow -PassThru -RedirectStandardOutput "flutter_test_stdout.txt" -RedirectStandardError "flutter_test_stderr.txt"
52
+
53
+ $timedOut = $false
54
+ if (-not $process.WaitForExit(120000)) {
55
+ $timedOut = $true
56
+ $process | Stop-Process -Force
57
+ }
58
+
59
+ $output = ""
60
+ if (Test-Path "flutter_test_stdout.txt") {
61
+ $output += Get-Content "flutter_test_stdout.txt" -Raw -ErrorAction SilentlyContinue
62
+ }
63
+ if (Test-Path "flutter_test_stderr.txt") {
64
+ $output += Get-Content "flutter_test_stderr.txt" -Raw -ErrorAction SilentlyContinue
65
+ }
66
+
67
+ $exit_code = $process.ExitCode
68
+
69
+ if ($timedOut) {
70
+ Write-Host "`nError: Unittests timed out after 120 seconds."
71
+ exit 124
72
+ }
73
+
74
+ Write-Host $output
75
+
76
+ exit $exit_code
77
+ } finally {
78
+ # Clean up temp files
79
+ Remove-Item -Path "flutter_test_stdout.txt" -ErrorAction SilentlyContinue
80
+ Remove-Item -Path "flutter_test_stderr.txt" -ErrorAction SilentlyContinue
81
+ Pop-Location
82
+ }