Crafter.Debug/AGENTS.template.md

119 lines
8 KiB
Markdown
Raw Normal View History

# 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:<br>• `:Value`: JSON value with its own parser and printer<br>• `:Reflect`: aggregate members and scoped-enum names, without `<meta>`<br>• `:Codec`: encode, decode, describe<br>• `:Result`: Error, Result, Async<br>• `:Protocol`: envelope, codes, versions, base64<br>• `:BuildInfo`: source id and client path, generated<br>• `: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:<br>• `:Registry`: commands, watches, samples, Group<br>• `:Timeline`: Log, Check, Emit<br>• `:Condition`: what wait and pause --when evaluate<br>• `: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.
`tooling/AGENTS.consumer.md` is the only notes file consumers get, and it covers both driving the app and coding with it. Crafter.Build merges a dependency's notes only when that dependency is in the build:
- **The tooling** comes in through the pragma, which is unconditional, so its notes are there after every build.
- **The root library** comes in only under the consumer's debug flag. Notes beside the root `project.cpp` would vanish after plain builds and appear twice after debug builds, so the root has none.
`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/<Test>-*/` with `flatpak run --die-with-parent --command=<path> 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<Verb><What>`.
- 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-<Test>"`.
- 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<T>` 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/<core variant>/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.