chore(dev): split CraftOS launch recipes

This commit is contained in:
Guillaume ARM 2026-06-09 01:44:11 +02:00
parent 72bb8f9d23
commit 59eca218ef
10 changed files with 192 additions and 17 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.craftos
.craftos-vanilla
.env

View File

@ -11,7 +11,7 @@ Use `docs/README.md` as the entrypoint for CC:Tweaked, CraftOS-PC, Advanced Peri
## Constraints
- Do not add a standalone Lua test harness unless asked. Local execution happens through the CraftOS-PC harness (see `docs/install-craftos-pc.md`, `docs/craftos_pc_glossary.md`, and ADR-0005); code otherwise executes in-game.
- Do not run `just repl` as an LLM agent; it is a human-only interactive CraftOS-PC wrapper. Use `just craftos --headless ...` for automated probes.
- Do not run `just repl` as an LLM agent; it is a human-only interactive CraftOS-PC wrapper. Use `just trapos --headless --exec '<lua>; os.shutdown()'` for automated probes against the TrapOS dev environment, or `just craftos --headless --exec '<lua>; os.shutdown()'` for probes against vanilla CraftOS (no TrapOS mounts). Headless probes are the recommended way to verify hypotheses about CC:Tweaked behavior; see ADR-0012.
- When changing behavior, add as many useful CraftOS-PC tests as practical. It is acceptable to skip tests that require human-only validation, such as complex turtle motion, in-game UX feel, or visual approval, but still add unit-style non-regression tests for deterministic parts when possible.
- Use `/apis/libtest.lua` for test scripts under `tests/`; `/programs/runtest.lua` prints `__TRAPOS_TEST_OK__` only after the suite passes.
- `libtest` cancels each case after `3`s (`--timeout <s>` / `--no-timeout` to override); never commit a hanging test to `tests/`. Slow harness fixtures go in `tests/harness/` behind dedicated recipes. See `docs/adrs/adr-0009-layered-test-timeouts.md`.

View File

@ -14,7 +14,7 @@ just install
This installs the local Git hooks: pre-commit runs `just test`, and pre-push runs `just ci`.
`just ci` is the full local verification entry point. Today it verifies that `craftos --version` reports v2.8.3 or newer, runs `just check` for `luacheck`, runs `just test` for CraftOS-PC headless tests, and runs the harness timeout regression guard. Use `just craftos` to launch CraftOS-PC with repo-local save data under `.craftos` and read-only mounts for `/trapos`, `/apis`, `/programs`, `/servers`, and `/startup`. `just repl` opens the same environment with `--cli` for human interactive use only; LLM agents must not run it. Use the CraftOS-PC glossary when adjusting `--headless`, `--exec`, `--script`, `--rom`, or `--mount-*` usage.
`just ci` is the full local verification entry point. Today it verifies that `craftos --version` reports v2.8.3 or newer, runs `just check` for `luacheck`, runs `just test` for CraftOS-PC headless tests, and runs the harness timeout regression guard. Three launch recipes wrap CraftOS-PC: `just trapos` launches the TrapOS dev environment with repo-local save data under `.craftos` and read-only mounts for `/trapos`, `/apis`, `/programs`, `/servers`, and `/startup`; `just craftos` launches a vanilla CraftOS-PC under `.craftos-vanilla` with no mounts (useful when a probe should not see TrapOS files, or to confirm a behavior is upstream); and `just trapos-install` exercises the real ccpm bootstrap (`install-ccpm.lua` → `ccpm update``ccpm install trapos`) end-to-end on a fresh ephemeral state. `just repl` opens the TrapOS dev environment with `--cli` for human interactive use only; LLM agents must not run it. `--headless --exec '<lua>; os.shutdown()'` against either `just trapos` or `just craftos` is the canonical way to verify a hypothesis about CC:Tweaked behavior before writing code or tests — see [`docs/adrs/adr-0012-headless-craftos-pc-as-hypothesis-probe.md`](docs/adrs/adr-0012-headless-craftos-pc-as-hypothesis-probe.md). Use the CraftOS-PC glossary when adjusting `--headless`, `--exec`, `--script`, `--rom`, or `--mount-*` usage.
When asking an agent to commit and/or push, rely on these hooks instead of duplicating `just test` manually before Git operations: pre-commit owns `just test`, and pre-push owns `just ci`. Manual `just test` and `just ci` remain appropriate when validating changes outside a commit/push workflow.

View File

@ -106,10 +106,11 @@ generate-env:
printf '%s\n' 'Generated .env'
# Pass args through to `craftos`, for example:
# just craftos --headless --exec 'print("__TRAPOS_TEST_OK__"); os.shutdown()'
# Launch CraftOS-PC with repo-local data and read-only repo mounts.
# just trapos --headless --exec 'print("__TRAPOS_TEST_OK__"); os.shutdown()'
# Launch the TrapOS dev environment in CraftOS-PC with repo-local data
# (.craftos/) and read-only repo mounts. See ADR-0005 and ADR-0012.
[positional-arguments]
craftos *args: check-install
trapos *args: check-install
#!/usr/bin/env bash
set -euo pipefail
repo='{{justfile_directory()}}'
@ -125,9 +126,86 @@ craftos *args: check-install
done
exec craftos "${argv[@]}" "$@"
# Pass args through to a fresh, vanilla `craftos` with no TrapOS mounts.
# Persistent state lives under .craftos-vanilla/. Useful for probes that
# should not see TrapOS files (e.g. verifying upstream CC:Tweaked behavior)
# and as the base for `just trapos-install`. See ADR-0012.
[positional-arguments]
craftos *args: check-install
#!/usr/bin/env bash
set -euo pipefail
repo='{{justfile_directory()}}'
argv=(--directory "$repo/.craftos-vanilla")
if [ "$(uname -s)" = "Darwin" ]; then
argv+=(--rom /Applications/CraftOS-PC.app/Contents/Resources)
fi
exec craftos "${argv[@]}" "$@"
# End-to-end install probe: drive the real ccpm bootstrap
# (install-ccpm.lua -> `ccpm update` -> `ccpm install trapos`) on a fresh,
# ephemeral CraftOS-PC state. Reflects the currently checked-out git branch:
# `master` -> --stable, `next` -> --beta (confirmation stubbed). Other
# branches are rejected because install-ccpm only knows master/next.
# Network-dependent and slower than `just test`, so not part of `just ci`.
# Override timeout with TRAP_CCLIBS_INSTALL_TIMEOUT_SECONDS (default 60).
# See ADR-0012.
trapos-install: check-install
#!/usr/bin/env bash
set -uo pipefail
repo='{{justfile_directory()}}'
timeout_seconds="${TRAP_CCLIBS_INSTALL_TIMEOUT_SECONDS:-60}"
case "$timeout_seconds" in ''|*[!0-9]*) printf '%s\n' 'TRAP_CCLIBS_INSTALL_TIMEOUT_SECONDS must be a positive integer' >&2; exit 1 ;; esac
branch="$(git -C "$repo" rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')"
case "$branch" in
master)
install_flag='--stable'
stub_read=''
;;
next)
install_flag='--beta'
stub_read="_G.read = function() return 'y' end; "
;;
*)
printf '%s\n' "trapos-install only supports the master or next branch (current: ${branch:-unknown}). install-ccpm.lua does not accept other branches." >&2
exit 1
;;
esac
printf '%s\n' "trapos-install: branch=$branch flag=$install_flag"
rom_arg=()
if [ "$(uname -s)" = "Darwin" ]; then
rom_arg=(--rom /Applications/CraftOS-PC.app/Contents/Resources)
fi
data_dir="$(mktemp -d)"
stage_dir="$(mktemp -d)"
cp "$repo/install-ccpm.lua" "$stage_dir/install-ccpm.lua"
tmp="$(mktemp)"
exec_code="${stub_read}shell.run('/staging/install-ccpm', '$install_flag'); shell.run('/programs/ccpm', 'update'); local ok = shell.run('/programs/ccpm', 'install', 'trapos'); if ok then print('__TRAPOS_INSTALL_OK__') end; os.shutdown()"
craftos --directory "$data_dir" --headless "${rom_arg[@]}" --mount-ro "/staging=$stage_dir" --exec "$exec_code" >"$tmp" 2>&1 &
pid="$!"
( sleep "$timeout_seconds"; kill -TERM "$pid" >/dev/null 2>&1 ) &
watchdog="$!"
wait "$pid" >/dev/null 2>&1
status="$?"
kill "$watchdog" >/dev/null 2>&1 || true
wait "$watchdog" >/dev/null 2>&1 || true
red=$(printf '\033[31m'); reset=$(printf '\033[0m')
if grep -q __TRAPOS_INSTALL_OK__ "$tmp"; then
rm -f "$tmp"; rm -rf "$data_dir"; rm -rf "$stage_dir"
printf '%s\n' 'OK: trapos installed end-to-end on fresh CraftOS-PC'
else
if [ "$status" -eq 143 ]; then
printf '%s\n' "${red}FAIL${reset} trapos-install timed out after ${timeout_seconds}s" >&2
else
printf '%s\n' "${red}FAIL${reset} trapos-install did not print __TRAPOS_INSTALL_OK__ (status=$status)" >&2
fi
cat "$tmp" >&2
rm -f "$tmp"; rm -rf "$data_dir"; rm -rf "$stage_dir"
exit 1
fi
# Human-only interactive REPL. LLM agents must not execute this command.
repl:
@just craftos --cli
@just trapos --cli
# Local CI entry point used by Git hooks. Pass args through to `test`.
ci *args: check-craftos check

View File

@ -19,3 +19,4 @@ Future ADRs can reuse the shape of the existing files when it is useful.
- [`adr-0009-layered-test-timeouts.md`](adr-0009-layered-test-timeouts.md) - Layered test timeouts (libtest per-case + shell watchdog).
- [`adr-0010-ccpm-package-manager.md`](adr-0010-ccpm-package-manager.md) - ccpm package manager (packages, registries, package-aware bootstrap).
- [`adr-0011-git-hooks-own-commit-push-verification.md`](adr-0011-git-hooks-own-commit-push-verification.md) - Git hooks own commit/push verification.
- [`adr-0012-headless-craftos-pc-as-hypothesis-probe.md`](adr-0012-headless-craftos-pc-as-hypothesis-probe.md) - Headless CraftOS-PC as the canonical hypothesis probe (rename `just craftos``just trapos`, add vanilla `just craftos`).

View File

@ -27,7 +27,8 @@ Treat CraftOS-PC as a first-class local development dependency.
- **Documented install** in [`docs/install-craftos-pc.md`](../install-craftos-pc.md), with a SHA-256-verified macOS flow and pointers to the official Windows/Linux artifacts.
- **Documented upstream navigation** in [`docs/craftos_pc_glossary.md`](../craftos_pc_glossary.md), covering CLI flags, mounts, `periphemu`, save data, and troubleshooting pages.
- **Verified by `just install`** via `check-install`, which checks `craftos`, `jq`, and `luacheck`. `check-craftos` runs `craftos --version` and requires v2.8.3 or newer. Failure prints a one-line pointer to the install guide instead of a long stack trace.
- **Repository-local launch recipe.** `just craftos` runs CraftOS-PC with `--directory .craftos`, keeps the macOS `--rom /Applications/CraftOS-PC.app/Contents/Resources` workaround, mounts the repository root read-only at `/trapos`, and mounts each manifest top-level directory read-only at its ComputerCraft root path.
- **Repository-local launch recipe.** `just trapos` runs CraftOS-PC with `--directory .craftos`, keeps the macOS `--rom /Applications/CraftOS-PC.app/Contents/Resources` workaround, mounts the repository root read-only at `/trapos`, and mounts each manifest top-level directory read-only at its ComputerCraft root path. (Originally `just craftos`; renamed when a separate vanilla-emulator recipe was added — see ADR-0012.)
- **Vanilla launch recipe.** `just craftos` launches CraftOS-PC under `.craftos-vanilla/` with no mounts, for probes that should not see TrapOS files and for the `just trapos-install` end-to-end install verification. See ADR-0012.
- **`just ci` is the local verification entry point.** Today it runs `check-craftos`, `check`, and `test`. The installed pre-commit hook invokes `just ci` directly, and `just test` owns CraftOS-PC-driven integration tests.
The existing `CLAUDE.md` constraint ("Do not run Lua locally or add a test harness unless asked") is reframed rather than removed: there is still no standalone Lua harness, and we are not adding a Busted-style test runner. The harness *is* CraftOS-PC, invoked deliberately.
@ -39,8 +40,8 @@ The existing `CLAUDE.md` constraint ("Do not run Lua locally or add a test harne
- Headless tests live under `tests/` and are driven by `just test`. Basic boot tests still prove CraftOS-PC boots and the event queue works, and API behavior tests use `/apis/libtest.lua` for named cases and assertions. The `Justfile` launches `/programs/runtest.lua`, which discovers tests, invokes them with `shell.run`, and prints `__TRAPOS_TEST_OK__` only after the suite passes.
- Each test process is guarded by `TRAP_CCLIBS_TEST_TIMEOUT_SECONDS`, defaulting to `3`, so a blocked ComputerCraft event loop fails quickly and prints captured output.
- The macOS install symlinks the binary into `/usr/local/bin`, which makes CraftOS-PC unable to auto-discover the ROM that ships inside the `.app` bundle (`Could not mount ROM`). The `test:` recipe works around this by passing `--rom /Applications/CraftOS-PC.app/Contents/Resources` on Darwin. Linux (AppImage) and Windows (installer) auto-discover correctly, so no flag is passed there.
- `just craftos` uses repository-local save data under `.craftos/config/` and `.craftos/computer/`. This keeps emulator state out of `~/Library/Application Support/CraftOS-PC` during repository work and keeps repo files visible through read-only mounts instead of copying them into the VM save.
- `just repl` is a human-only interactive wrapper around `just craftos --cli`; automation and LLM agents must use headless `just craftos ...` invocations instead.
- `just trapos` uses repository-local save data under `.craftos/config/` and `.craftos/computer/`. This keeps emulator state out of `~/Library/Application Support/CraftOS-PC` during repository work and keeps repo files visible through read-only mounts instead of copying them into the VM save.
- `just repl` is a human-only interactive wrapper around `just trapos --cli`; automation and LLM agents must use headless `just trapos --headless --exec '<lua>; os.shutdown()'` (TrapOS dev env) or `just craftos --headless --exec '<lua>; os.shutdown()'` (vanilla emulator) invocations instead. ADR-0012 frames these as the canonical hypothesis-probe pattern.
- The harness version becomes a project-level concern. When CC:Tweaked ships breaking changes that require a newer CraftOS-PC build, we bump the minimum version in `docs/install-craftos-pc.md` and `check-craftos` keeps contributors honest.
- No CI integration yet. Running CraftOS-PC headless in GitHub Actions is feasible (the AppImage works on Ubuntu runners) but is out of scope here; the contract is local-only for now.

View File

@ -88,9 +88,9 @@ first to refresh available versions.
## Consequences
- The repo gains a `packages/` descriptor tree; the flat source layout is untouched.
- `just craftos` no longer derives mounts from `manifest.json .files` (it is now
`.packages`); it mounts a fixed list of top-level dirs instead. `just test` was
already on fixed mounts and is unaffected.
- `just trapos` (formerly `just craftos`; see ADR-0012) no longer derives mounts
from `manifest.json .files` (it is now `.packages`); it mounts a fixed list of
top-level dirs instead. `just test` was already on fixed mounts and is unaffected.
- ccpm logic is covered by `tests/ccpm.lua` (URL resolution, dependency ordering,
cycle/missing detection, already-installed, registry CRUD, cache update, available
status, upgrade, uninstall dependency guard) with an injected `http` stub — no

View File

@ -0,0 +1,89 @@
# ADR 0012: Headless CraftOS-PC As The Canonical Hypothesis Probe
## Status
Accepted
## Date
2026-06-09
## Context
[ADR-0005](adr-0005-craftos-pc-harness.md) made CraftOS-PC the local harness and
[ADR-0007](adr-0007-use-libtest-for-craftos-tests.md) /
[ADR-0008](adr-0008-keep-tests-runnable-in-craftos-and-in-game.md) /
[ADR-0009](adr-0009-layered-test-timeouts.md) wired it into the test suite via `just test`.
That work focused on the *test* path. Headless CraftOS-PC is also a cheap, deterministic
*interactive* tool: `craftos --headless --exec '<lua>; os.shutdown()'` boots the emulator,
runs an arbitrary Lua snippet against the real CC:Tweaked ROM, prints output to stdout,
and exits in well under a second. Humans and LLM agents can use it to verify hypotheses
about CC:Tweaked behavior *before* writing code or tests — "does `os.epoch('utc')` return
ms?", "does my new API factory `require` cleanly?", "does `fs.exists` follow symlinks
inside `--mount-ro`?".
Today this usage was implicit: the harness existed, but no document framed
`--headless --exec '...'` as the recommended first move when an agent is unsure about
CC:Tweaked behavior. The original recipe was also named `just craftos` even though it
mounted the entire TrapOS dev environment — so probes against it were never against
vanilla CC:Tweaked, even when the agent thought they were.
Two concrete changes triggered this ADR:
1. **Recipe split.** The old `just craftos` (TrapOS dev mounts + persistent `.craftos/`) is
renamed `just trapos`. A new `just craftos` launches a fresh, mount-less CraftOS-PC
under `.craftos-vanilla/`. A `just trapos-install` recipe exercises the real ccpm
bootstrap on an ephemeral state to validate the install path end-to-end.
2. **Explicit guidance.** Agents working in this repo should reach for a headless probe
the moment they catch themselves guessing about CC:Tweaked behavior, instead of
speculating or committing changes that only run `luacheck`.
## Decision
Frame headless CraftOS-PC as the canonical hypothesis-probe pattern, with two flavors:
- `just trapos --headless --exec '<lua>; os.shutdown()'` — probe against the **TrapOS dev
environment**. Mounts of `/apis`, `/programs`, `/servers`, `/startup`, `/tests`, and the
repo root at `/trapos` are live, so `require('/apis/eventloop')` and friends work
against the current branch. Use this when the question involves repo code.
- `just craftos --headless --exec '<lua>; os.shutdown()'` — probe against **vanilla
CraftOS-PC**. No mounts, no startup scripts. Use this when the question is purely about
CC:Tweaked behavior and TrapOS files would be a distraction, or to confirm a behavior
is upstream rather than something the dev env layered on.
- `just trapos-install` — drive the full real install (`install-ccpm.lua` →
`ccpm update``ccpm install trapos`) on a fresh ephemeral state. This is the probe
to run when changing anything in the install path itself.
Conventions:
- Always terminate the snippet with `os.shutdown()`. The shell watchdog from
[ADR-0009](adr-0009-layered-test-timeouts.md) governs `just test`, not these recipes;
a missing shutdown will hang until the user kills the process.
- Keep snippets minimal and side-effect-free. If the probe reveals a fact worth defending,
add a `libtest` case under `tests/` — probes are not a substitute for committed tests.
- LLM agents SHOULD prefer a quick headless probe over speculation when answering
"does X work in CC:Tweaked?" or "does my refactor still load?". The cost is one extra
emulator boot (~1s); the benefit is grounded answers instead of plausible-sounding ones.
## Consequences
- Higher CraftOS-PC invocation traffic during agent sessions; cheap enough that this is a
good trade.
- Faster convergence on correct fixes: agents stop committing speculative changes that pass
`luacheck` but fail in-game.
- A named pattern (`--headless --exec '<lua>; os.shutdown()'`) shows up in `CLAUDE.md` and
`DEVELOPMENT.md`, so contributors and agents reach for it without rediscovery.
- `.craftos-vanilla/` is added to `.gitignore` alongside `.craftos/`.
- `just trapos-install` is *not* part of `just ci`: it is network-dependent and slower
than `just test`. Run it manually when touching `install-ccpm.lua` or ccpm package
descriptors.
## Cross-references
- [ADR-0005](adr-0005-craftos-pc-harness.md) — CraftOS-PC as the local harness.
- [ADR-0007](adr-0007-use-libtest-for-craftos-tests.md) — libtest for CraftOS tests.
- [ADR-0009](adr-0009-layered-test-timeouts.md) — layered test timeouts.
- [ADR-0010](adr-0010-ccpm-package-manager.md) — ccpm package manager (drives the
`just trapos-install` flow).

View File

@ -86,17 +86,22 @@ On macOS, use the `--rom` form shown above if the command fails with `Could not
## Repository-local launches
`just craftos` launches CraftOS-PC with persistent save data rooted at `.craftos` instead of the platform default user-data directory. Generated files live under `.craftos/config/` and `.craftos/computer/`, while the root `.gitignore` keeps that state untracked.
`just trapos` launches CraftOS-PC with the TrapOS dev environment: persistent save data rooted at `.craftos` instead of the platform default user-data directory, and read-only mounts of the repository at `/trapos` plus each shipped top-level directory at its ComputerCraft root path (such as `/apis`, `/programs`, `/servers`, `/startup`, and `/tests`). Generated files live under `.craftos/config/` and `.craftos/computer/`, while the root `.gitignore` keeps that state untracked.
The recipe mounts the repository root read-only at `/trapos` so code can read `/trapos/manifest.json`. It also uses `jq` to read `manifest.json` and mount each shipped top-level directory read-only at its ComputerCraft root path, such as `/apis`, `/programs`, `/servers`, and `/startup`.
`just craftos` launches a vanilla CraftOS-PC with no mounts, persistent under `.craftos-vanilla/` (also gitignored). Use it when a probe should not see TrapOS files — for example, to confirm a behavior is upstream rather than TrapOS-specific.
`just trapos-install` exercises the real ccpm bootstrap (`install-ccpm.lua` → `ccpm update``ccpm install trapos`) end-to-end on a fresh, ephemeral CraftOS-PC state. Network-dependent and slower than `just test`, so not part of `just ci`. Override the watchdog with `TRAP_CCLIBS_INSTALL_TIMEOUT_SECONDS` (default `60`).
Pass CraftOS-PC flags directly after the recipe name, for example:
```sh
just craftos --headless --exec 'print("__TRAPOS_TEST_OK__"); os.shutdown()'
just trapos --headless --exec 'print("__TRAPOS_TEST_OK__"); os.shutdown()'
just craftos --headless --exec 'print(_HOST); os.shutdown()'
```
`just repl` delegates to `just craftos --cli` for human interactive use only. LLM agents must not run `just repl`.
See [`docs/adrs/adr-0012-headless-craftos-pc-as-hypothesis-probe.md`](adrs/adr-0012-headless-craftos-pc-as-hypothesis-probe.md) for the canonical headless probe pattern used to verify hypotheses about CC:Tweaked behavior.
`just repl` delegates to `just trapos --cli` for human interactive use only. LLM agents must not run `just repl`.
See [Command-Line Flags](https://www.craftos-pc.cc/docs/cli) for `--headless`, `--exec`, `--script`, `--rom`, and `--mount-*`. See [Error Messages](https://www.craftos-pc.cc/docs/error-messages) for `Could not mount ROM` and other boot-time failures.

View File

@ -96,7 +96,7 @@ Expected output on a working setup: `pong` (default prompt is `reply with exactl
## 5. CraftOS-PC (no Minecraft)
```bash
just craftos --headless -- /programs/ai-helloworld.lua
just trapos --headless -- /programs/ai-helloworld.lua
```
Set settings inside the harness before running, or inject them via the test API.