* feat(core): add Axiom OTLP transport and an anonymizing tracer
Adds the export path for shipping traces and metrics to Axiom, plus the
anonymization guarantee that has to come with it. Nothing emits yet — the CLI
still routes telemetry to Sentry; this is the engine-side groundwork.
`layerAxiomTraces` and `layerAxiomMetrics` are composed by hand rather than
through `Otlp.layer`, because Axiom routes each signal to a different dataset
via a different header and `Otlp.layer` passes one `headers` object to every
signal. Serialization is protobuf, not JSON: Axiom's `/v1/metrics` accepts
`application/x-protobuf` only. `OtlpSerialization.layerProtobuf` ships with
Effect, so this adds no dependency.
They are separate layers on purpose. Effect tracks delta-temporality state on
the metrics exporter instance, so a second exporter in one process starts with
no previous snapshot and re-reports every counter's full value — doubling every
metric. `telemetry-payload.test.ts` pins that behavior so a future refactor that
merges them fails loudly instead of silently doubling data.
OTLP has no `beforeSend` hook, so Sentry's central scrubber has no equivalent.
`makeScrubbingTracer` wraps the tracer and runs every span name, attribute, and
event payload through `anonymizeText` on the way to the exporter, and is applied
inside `layerAxiomTraces` so an attribute added later cannot bypass it.
`anonymize-text.ts` moves to core (both its scrubbers already lived there) and
`run-inspect.ts` now scrubs `inspect.directory` at the source rather than relying
on the backstop.
`TELEMETRY_SHUTDOWN_TIMEOUT_MS` is 1s, tighter than Sentry's 2s error flush: the
metrics exporter passes `maxBatchSize: "disabled"` internally, which skips
Effect's empty-buffer short-circuit, so it POSTs on every scope close whether or
not anything was recorded. On a firewalled machine that request cannot fail fast,
and unlike the Sentry flush it would run on every scan rather than only on crashes.
Verified against a local collector: correct per-signal datasets and protobuf
content type, and no home directory, username, or secret reaches the wire.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(core): close three review findings in the Axiom transport
Scrub the failure a span ended with. The OTLP tracer runs `Cause.prettyErrors`
over the exit and exports each result as `exception.type` / `exception.message` /
`exception.stacktrace`. None of those go through `span.attribute`, so they
bypassed the scrubbing tracer entirely — and stack traces are full of absolute
paths, meaning any failing scan shipped the user's home directory. This was the
one route into the payload the backstop didn't cover. Interrupt-only causes are
left alone; the exporter emits a fixed label for them and never touches the
error text.
Honor `exportIntervalMs` for traces. It was threaded through to the metrics
exporter but not the tracer, so a long-lived process — the language server —
would hold spans for the full ten-minute default no matter what interval it
asked for.
Trim trailing slashes without a regex. `/\/+$/` backtracks quadratically on a
domain made mostly of slashes, which CodeQL flags as a polynomial-regex denial
of service because the value comes from the caller.
Also corrects a doc comment that still described metrics as being built at exit,
which stopped being true when the CLI moved to a single shared scope.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: add AI training restriction to license
Adds a no-training clause to prevent use of the source code as training,
fine-tuning, or evaluation data for ML models without prior written
permission. Academic researchers can request a no-cost exemption at
founders@million.dev.
Also updates the title to reflect the additional restrictions, and removes
the "outputs" language (legally overreaching — users own output from their
own data).
* chore: simplify training restriction to uniform written-permission requirement
* feat(cli): warn when react-doctor is run inside an AI/ML training pipeline
Detects presence-based env vars for HuggingFace, CUDA/GPU, experiment
trackers (W&B, MLflow, Comet, Neptune), Ray workers, RL simulators
(MuJoCo, Gymnasium), cloud ML platforms (SageMaker, Azure ML, Vertex AI),
AI agent sandboxes (Daytona, E2B, Modal, RunPod, Harbor), and coding-agent
eval harnesses (SWE-bench, SWE-agent). Prints a warning to stderr pointing
to founders@million.dev for written permission.
* feat(cli): expand CI provider and AI/ML training environment detection
CI providers (+18): Semaphore, AppVeyor, Harness, Buddy, Codefresh,
Netlify, Railway, Vercel, Codemagic, Prow, Agola, Cirrus CI, Render,
Fly.io, Blacksmith, WarpBuild, Namespace, Ubicloud
ML/AI training (+21): Kaggle, Google Colab, Databricks, SageMaker
(SM_TRAINING_ENV + TRAINING_JOB_ARN), Azure ML (AZUREML_RUN_ID),
Vertex AI (CLOUD_ML_PROJECT_ID), W&B sweeps, DVC stages, ClearML,
Flyte, Determined AI, Lightning AI, Argo Workflows, Kubeflow Pipelines,
HuggingFace Spaces, Replicate, Vast.ai, Google TPU, ROCm
Coding agents (+3): Cline, Augment, Trae AI
* feat(api,packaging): add AI training warning to programmatic API; fix license labels
- Adds AI/ML environment detection to diagnose() (both single-dir and
batch forms) so programmatic users (e.g. CodeRabbit integrations) see
the same license warning as CLI users. Warns once per process via a
module-level guard. Kept inline to avoid a CLI→API dep.
- Changes all package.json "license" fields from "MIT" to
"SEE LICENSE IN LICENSE" so automated scanners (Fossa, Snyk,
TLDR-Legal) report the actual terms instead of plain MIT.
* feat(website): add /license page with commercial contact CTA