# AGENTS.md Notes for agents working on Crafter.Debug itself. The README is the user manual. `tooling/AGENTS.consumer.md` is what consumers' agents get. ## Project overview Crafter.Debug lets an agent inspect and drive an app while it runs. `project.cpp` makes four configurations: | Configuration | Holds | |---|---| | `Crafter.Debug.Core` (static) | What the app and the client share. Partitions:
• `:Value`: JSON value with its own parser and printer
• `:Reflect`: aggregate members and scoped-enum names, without ``
• `:Codec`: encode, decode, describe
• `:Result`: Error, Result, Async
• `:Protocol`: envelope, codes, versions, base64
• `:BuildInfo`: source id and client path, generated
• `:Discovery`: instance files and the lifetime lock | | `Crafter.Debug.Cli` (static) | The client's logic: `Cli::Run`, with the transport injected. | | `crafter.debug-cli` (executable) | `crafter-debug`: `main.cpp` plus the transport. | | `Crafter.Debug` (static, returned) | What an app links. Partitions:
• `:Registry`: commands, watches, samples, Group
• `:Timeline`: Log, Check, Emit
• `:Condition`: what wait and pause --when evaluate
• `:Session`: serving requests, Pump, Advance | The client is one of the library's dependencies only so that a consumer's debug build compiles it in the same pass. `tooling/` is a separate project, `Crafter.Debug.Build`. Consumers load it with `#pragma crafter dependency ... project "tooling/project.cpp"`. Its `Debug::Use` adds the library under the consumer's flag and writes the consumer's `build/crafter-debug` launcher at load time. `examples/Basic` is a consumer that uses this checkout through a local pragma path. ## Dev environment crafter-build comes from its Flatpak. Spell it out, since the Bash tool may not expand aliases: `flatpak run --die-with-parent net.catcrafts.CrafterBuild`. The host is Linux x86_64. Tests run inside the Flatpak sandbox, where: - `/tmp` is private; - the toolchain is clang 23 with libc++; - `/.flatpak-info` exists. Every `flatpak run` is its own sandbox with its own pid namespace. ## Build and test commands ```bash flatpak run --die-with-parent net.catcrafts.CrafterBuild # the library, core, client flatpak run --die-with-parent net.catcrafts.CrafterBuild test # every test flatpak run --die-with-parent net.catcrafts.CrafterBuild test 'Should*Client*' cd examples/Basic && flatpak run --die-with-parent net.catcrafts.CrafterBuild --debug-api # a consumer's debug build ``` A test's own output appears only when it fails. To see its PASS lines, run the binary from `bin/-*/` with `flatpak run --die-with-parent --command= net.catcrafts.CrafterBuild`. ## Testing instructions **Registration:** tests are declared in `project.cpp`, and only when this is the top-level project: - `coreTests` link the core; - `libraryTests` link the library; - `ShouldRunClientCommands` links the library and the client. **Shape of a test:** - Name it `Should`. - Open `main.cpp` with a comment on the behaviour and the failure it guards against. - Use a `Check(ok, what)` helper that prints PASS or FAIL, and return nonzero at the end. **Session tests** use `Session({ .listen = false })` and hand requests to `Session::Handle` through a fake `Detail::Exchange`. Pump from the test's own thread, or from one thread playing the app. **Files and discovery:** - Temporary files go under `fs::temp_directory_path() / "crafter-debug-"`. - Discovery tests set `CRAFTER_DEBUG_DIR` there. **Hangs:** anything that can hang gets a watchdog that calls `std::_Exit(1)` before the runner's 60 s. **Pointers into temporaries:** `Value::Find` returns a pointer into its value. Never keep one from a temporary such as `ResultOf(...).Find(...)` past the full expression; copy the value into a local first. Two tests failed on exactly this. ## Code style **Nothing enforces it.** No lint rules are registered: a `#pragma crafter dependency` on Crafter.LintRules would be built on every load of every consumer. **Headers and imports:** - `// SPDX-License-Identifier: LGPL-3.0-only`, with a space after `//`, then the copyright line, then a blank line. Examples are MIT. - `import std;`. C headers go only in a global module fragment, or before the imports in a test. **Formatting and naming:** - Four-space indentation. - `PascalCase` for types and functions, `camelCase` for variables, members and constants. - `std::println` and `std::format`. - Fixed-width integers. - `enum class`. **Comments** say why: the failure mode, the race, the rejected alternative. Long comments are deliberate. ## Design rules 1. **Release builds never link Crafter.Debug.** Consumers gate it through `Debug::Use` and their own define. The library has no no-op mode. 2. **No thread of Crafter.Debug's own, and nothing ever blocks one.** Transport threads only queue work or answer from the session's own state; the Pump thread runs app code; `Detail::Exchange::Reply` and `Stream` never block. The thread pool belongs to the host app. 3. **App code runs only on the Pump thread:** handlers, getters, setters, and condition evaluation. Never hold the session mutex while calling it, since app code logs, and logging re-enters the session. 4. **Nothing the Flatpak compiles runs on the host.** The launcher is POSIX sh and starts the client with `flatpak run`. On older hosts, the bundled libc++ and the runtime's glibc do not load. 5. **The client is built with the app.** `debug.hello`, the ALPN `crafter-debug/1` and the discovery record's `protocol` and `sourceId` are frozen. Bump `protocolVersion` on any wire change. 6. **Errors name the next step.** Decoding never throws. A request that timed out before `Pump` never runs. 7. **Discovery names instances by random ids and tells liveness by flock, never by pid.** Pids collide across `flatpak run` sandboxes. ## Commit and PR guidelines - **Branch:** off `master`. - **Subject:** `type(scope): imperative summary`. Types: `feat`, `fix`, `docs`, `test`. Scopes in use: `core`, `registry`, `session`, `discovery`, `cli`, `tooling`, `transport`. - **Body:** what, why, and why this shape, wrapped at about 75 columns. Add a `Co-Authored-By:` trailer when an agent wrote it. - **Never commit** `bin/`, `build/` or `AGENTS.md`. - **Push and open PRs only when asked.** ## Security - **Debug builds only.** The session listens on 127.0.0.1, and every request must carry the instance's 128-bit token. - **Discovery files** are 0600 in a 0700 directory. - **Debuggers:** the session calls `prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY)`, so a debugger the agent starts can attach. That is another reason it never ships in a release build. ## Gotchas - **`Group` is neither copyable nor movable,** and neither is its owner. Its watches refer to the owner's members. - **Inside a `Codec` specialization, call the free functions qualified** (`Debug::Encode`): the member `Encode` hides them. - **`std::format` needs a compile-time format string.** A ternary between two literals does not compile. - **Tests don't inherit defines.** Add them with `.Define()`. - **A consumer's `#ifdef`'d `import Crafter.Debug;`** is harmless in its plain builds. - **`project.cpp` regenerates `build/generated//Crafter.Debug.Core-BuildInfo.cpp`** when the sources or the client's path change. `crafter-build clean` removes it, and the next load writes it again. - **The launcher is written by `Debug::Use` while crafter-build loads the consumer,** not by the build. A plain build keeps an existing launcher; a fresh plain build writes the stub.