--- description: The action runs src/.ts directly with node on node:24-bookworm (no build); protect-source.sh copies only src/*.ts, package.json, profiles.json, prompt.md --- `action.yml` is a composite action running three phases as `node "/src/{prepare,run,post}.ts"`. Node's built-in type stripping runs the TypeScript; there is **no build step, no bundle, and no runtime dependency**. Hence `tsconfig.json` has `erasableSyntaxOnly` and `allowImportingTsExtensions`: only erasable syntax (no `enum`, no parameter properties, no decorators) and relative `.ts` imports. - **Image:** the steps assume the `node:24-bookworm` job container. Node must be 24.2+ for `import.meta.main` in `post.ts`. That image is the only Node pin (`package.json` has no `engines`); `check.yml` prints `node --version` to confirm the cached image's minor. - **Root-owned source copy:** `scripts/protect-source.sh` copies the action into `$RUNNER_TEMP` so an agent editing the checkout can't swap a later phase. It copies exactly `src/*.ts`, `package.json` (`"type": "module"` makes Node load the sources as ES modules), `profiles.json`, and `prompt.md`. **A new runtime file or subdirectory must be added there**, or it is missing at run time. Runtime code in `src/` is flat; a subfolder would not be copied. The only subfolder, `src/testing/`, is test-only. - `tests/entrypoints.test.ts` runs each phase under real Node with a cleared environment and expects its first "X is not set" error, which proves the whole import graph loads without a build. Keep it in sync with the phases. - **None of the app build conventions apply here:** no `vp build`, no frontend or Rust/Tauri output paths, no Containerfile, no decorators plugin, no lit-check, no generated JS. Don't add any of them. - **CI** (`.gitea/workflows/check.yml`) runs on the self-hosted runner's default image as root: `npm ci`, `npx vp check`, `npx vp test`. It does not use `setup-vp`. YAML stays declarative ([[yaml-stays-declarative]]).