From e9d06d18ec091e6261161e199ff48269bd27347a Mon Sep 17 00:00:00 2001 From: Bryan English Date: Wed, 4 Nov 2026 12:11:11 -0500 Subject: [PATCH] docs: add AGENTS.md for coding agents AGENTS.md tells coding agents how this repository works: its layout and commands, the Node.js core and OpenTelemetry conventions the code follows, how the tests are built around init() activating once per process, how to measure changes to the traced path, and the rules for pull requests. CLAUDE.md imports it, so Claude Code reads the same file. Signed-off-by: Bryan English --- AGENTS.md | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 1 + 2 files changed, 131 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..235d885 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,130 @@ +# AGENTS.md + +Guidance for coding agents working in this repository. Read README.md and +CONTRIBUTING.md as well: README.md documents the public API and its behavior, +and CONTRIBUTING.md has the rules for contributing. + +## What this is + +microtel is a small implementation of parts of OpenTelemetry for Node.js. It +traces `http` and `https` clients and servers, HTTP/2 servers, and undici and +`fetch()` through `diagnostics_channel`, propagates W3C trace context and +baggage, and exports spans as OTLP/HTTP JSON. It's meant to be included in +Node.js core as a dependency, so it's written the way Node.js core code is. + +- It's CommonJS, with `'use strict'`, for Node.js 26.9 or newer (the `engines` + field in package.json). +- It has no runtime dependencies. The dev dependencies only build, test, and + format it. +- All configuration goes through `init(options)`. There is no configuration + through environment variables. +- The public API is `init()`, `isActive()`, and `flush()`, from index.js. + +## Layout + +| Path | What it holds | +|---|---| +| `index.js` | The public API. | +| `lib/core.js` | `init()` option validation and the state it sets. | +| `lib/instrumentations.js` | The `diagnostics_channel` subscribers that create spans and propagate context. | +| `lib/span.js` | The `Span` class, trace flags, and `traceparent` parsing and formatting. | +| `lib/sampler.js` | The root sampling decision for `sampleRatio`. | +| `lib/id.js` | Trace and span ID generation. | +| `lib/flush.js` | The span buffer, OTLP JSON serialization, the export, and `flush()`. | +| `lib/guard.js` | `guard()`, which keeps a bug in microtel from breaking the app it traces. | +| `lib/validate.js` | Ports of Node.js core's argument validators and error helpers. | +| `scripts/` | The bundle build and the benchmark comparison that CI runs. | +| `test/` | The tests (`test-*.js`), shared helpers in `test/common/`, and child process fixtures in `test/fixtures/`. | +| `test/collector/` | A smoke test against a real OpenTelemetry Collector, which runs in its own CI job. | +| `benchmark/` | The `node:bench` benchmarks. | +| `dist/microtel.js` | The generated CommonJS bundle from `npm run build`. It's ignored, and `test/test-bundle.js` loads and tests it. | + +## Commands + +```sh +npm ci +npm run build # writes dist/microtel.js +npm test # builds the bundle, then runs node --test 'test/test-*.js' +npm run format:check # prettier --check . +npx prettier --write +npm run bench # node --experimental-bench --bench 'benchmark/*.bench.js' +``` + +The collector smoke test needs Docker. The header of +`test/collector/test-collector.js` says how to run it locally. + +CI runs separate `build`, `test`, `prettier`, and `collector` jobs, and the +benchmark workflow runs the benchmarks on every push and compares them against +the base branch on pull requests. + +## Code conventions + +- Write it the way Node.js core would. An error for a bad argument uses core's + error code and message, through the helpers in `lib/validate.js`. +- A validator ported from core goes into `lib/validate.js` and into the parity + battery in `test/common/validate-battery.js`, which + `test/test-validate-parity.js` runs against core's own validators under + `--expose-internals`. +- `init()` validates every option before it changes any state, and it copies + any object it keeps, so a caller changing that object later has no effect. +- Nothing microtel hands to Node.js may throw into the app. That covers + `diagnostics_channel` subscribers, event listeners, and timers. Wrap each one + with `guard()` from `lib/guard.js`, once rather than on every call. +- Every export settles, on every path, by its `exportTimeout` deadline at the + latest. `flush()` never rejects for a failed export. Failures are reported as + throttled `OTelExportWarning` process warnings instead. +- The traced request path is hot. Avoid allocations and closures there, and + measure any change to it (see Performance below). +- Prettier formats everything except Markdown and the built bundle, with + single quotes. Match the style of the code around a change. +- Use comments for non-obvious decisions and constraints. A TODO names its + author, as `TODO(handle):`, and says everything needed to act on it, with no + links to pull requests. +- Don't name specific observability vendors in code, docs, commit messages, or + pull request descriptions. Say "an OTLP backend", and use example.com in + examples. + +## OpenTelemetry behavior + +Follow the OpenTelemetry specification and semantic conventions, and match what +the official SDKs and instrumentations do. Where microtel differs on purpose, +say so in the pull request, and in README.md if users can see it. + +## Tests + +- Tracing activates once per process. Tests that activate tracing in-process + normally call `startTracing()` once in `before()` and share its collector. + Use `spawnMicrotel()` for another activation in the same test file. Tracing + fixtures usually receive JSON init options in `process.argv[2]`. +- `spawnMicrotel()` accepts a `timeout` option and returns `code: null` when + that timeout kills the child. Use it for a child that could block, and close + servers and collectors a test creates. +- A test must not depend on timing or randomness. Wait on events rather than + sleeping. +- Add tests for the change itself, and check that each new test fails when the + behavior it covers is broken. +- Keep tests DRY, with table-driven cases and `assert.partialDeepStrictEqual` + where they fit. + +## Performance + +For pull requests, CI compares the benchmarks of the change and its base over +five interleaved rounds. It reports the mean of each run's mean rate and the +spread, and highlights a change only when every run on one side is faster or +slower than every run on the other. + +When measuring by hand, interleave runs of main and the change on the same +machine, report several runs rather than one, and use the untraced benchmarks +as a control for noise. For small changes, CPU profiles of microtel's own time +are steadier than throughput. + +## Pull requests and commits + +- Commit subjects use conventional commits, with lowercase text after the + type. Examples in the history include `feat:`, `fix:`, `perf:`, `test:`, + `ci:`, `chore:`, and `docs:`. +- Contributions are made under the Developer's Certificate of Origin in + CONTRIBUTING.md. +- A pull request stays open for at least 24 hours before it merges, unless the + maintainers judge it trivial. +- Update README.md for any change users can see. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md