# Summary: extract `hxprobe` into a standalone top-level project Plan: [docs/plans/2026-07-02-09-32-hxprobe-standalone-project.md](../plans/2026-07-02-09-32-hxprobe-standalone-project.md) ## What was built `hxprobe` moved from `python/hxprobe/` to a new top-level `hxprobe/` directory (sibling of `go/` and `python/`), structured as a genuinely independent project: own `pyproject.toml`, own venv (`hxprobe/.venv`), own `tests/` directory. It imports nothing from `python/latprobe` — verified with `grep -rn "latprobe" hxprobe/` returning zero matches in code (a few comments mention Go's `http.DefaultClient` for context, which is fine; no comment references `latprobe` as a concrete path/module anymore either). Files: - `hxprobe/pyproject.toml` — own manifest, `httpx[http2]` dependency, `packages = ["hxprobe"]` - `hxprobe/hxprobe/probe.py` — moved from `python/hxprobe/probe.py`, with `Options`, `Phase`, `Result`, `VerboseDetail`, `CertInfo` dataclasses and `_parse_cert`/`_parse_cert_date` helpers now defined locally (copied from `latprobe/probe.py`) instead of imported; all the httpx-instrumentation logic (`_Trace`, `_TimingStream`, `_TimingBackend`, `_TimingTransport`, `measure()`) is unchanged - `hxprobe/hxprobe/aggregate.py`, `duration.py` — verbatim copies of `latprobe`'s; their imports were already package-relative (`from .probe import Result`), so copying required zero edits - `hxprobe/hxprobe/cli.py` — rewritten as a full standalone CLI. Previously a 27-line wrapper delegating to `latprobe.cli.run()` via an injected `measure_fn`; now has its own exit codes, argparse, and text/JSON rendering (copied from the shared `latprobe/cli.py`, then stripped of the `measure_fn`/`prog`/`description`/`protocol_flags` parameterization that only existed to let two packages share one `run()`) - `hxprobe/tests/{test_probe,test_cli,test_integration}.py` — moved from `python/tests/test_hx_*.py` / `test_integration_hx.py`, `hx`-prefix dropped, imports repointed from `latprobe.*` to `hxprobe.*` - `python/latprobe/{cli.py,probe.py}` — reverted via `git checkout --` to their exact pre-`hxprobe` committed state (confirmed zero diff afterward) - `Makefile` — `py-deps`/`PY_VENV*`/shared `hx-run`/`hx-test-integration` removed from the Python section; new standalone `hx-deps`/`hx-run`/ `hx-test`/`hx-test-integration`/`hx-check`/`hx-clean` targets under `HX_DIR`/`HX_VENV*`; umbrella `test`/`check`/`clean` now run all three (`go-*`/`py-*`/`hx-*`) - `docs/usage/py-hxprobe.md` → `docs/usage/hxprobe.md` (renamed + rewritten for the new structure) - Old `python/hxprobe/`, `python/pyproject.toml`, `python/.venv`, `python/latprobe_python.egg-info/` deleted ## Key design decisions - **Move, don't rewrite, the already-debugged files.** `probe.py` and the three test files carry real bug fixes found during the original implementation (the `mark_dns` present-on-failure bug, verbose detail not populated on the failure path, `Content-Length` needed once the test fixtures switched to HTTP/1.1 keep-alive). Rewriting from scratch would have risked reintroducing them. - **`aggregate.py`/`duration.py` needed zero import changes** — their existing relative imports (`from .probe import ...`) already resolve correctly once copied into a new package with its own `probe.py`. Not every file needed the same treatment as `probe.py`/`cli.py`. - **`cli.py`'s starting point was the *current* shared `latprobe/cli.py`**, not a from-scratch rewrite — it already contained 100% of the needed logic (including the `--no-http2`/`--no-follow-redirects` flags and the verbose Protocol row/JSON keys, both added earlier specifically for hxprobe). The only work was deleting the generalization scaffolding (`measure_fn`, `MeasureFn`, `prog`/`description`/`protocol_flags` params) that existed solely to let two packages share one `run()`. - **Reverting `latprobe` via `git checkout --` rather than hand-editing** — confirmed first via `git log`/`git diff --stat` that `cli.py`/`probe.py` had no changes besides the hxprobe-sharing scaffolding since the last commit, making this a safe, exact, zero-risk revert. - **Comments referencing `latprobe` by file path were reworded**, not just the imports. A comment like "already used by latprobe/probe.py" becomes a dangling reference once this directory is genuinely portable to another repo. Reworded ~5 comments/docstrings to describe the technique generically (e.g., "ports the getaddrinfo → connect split" → "splits DNS and TCP connect into two timed steps") instead of naming the other project. ## Deviations from the plan None of substance. One judgment call not spelled out in the plan: the old `test_cli.py`'s docstring said its tests "focus on what's different... since run() delegates almost entirely to latprobe.cli.run()" — no longer true now that `cli.py` is a full standalone reimplementation. Rewrote that docstring for accuracy rather than leaving a stale claim, without expanding the test suite itself (that would be a larger, separate scope-creep beyond what was asked — see the coverage note below). ## Notable follow-up worth flagging `hxprobe/tests/test_cli.py` has ~11 tests versus `latprobe/tests/test_cli.py`'s ~23. That gap was fine when hxprobe's `cli.py` was a thin wrapper around already-tested shared code; it's a real gap now that `cli.py` is an independent ~440-line reimplementation with its own copy of every rendering branch. Existing tests do exercise the core paths (single URL, `--fail`, JSON, DNS/connect failures) so this isn't uncovered, but reaching `latprobe`-level depth (multi-URL separator, sampling aggregate output, JSON error grouping, all-failed multi-header) would be a reasonable next step if full independent confidence in the standalone project matters. Not done here — out of scope for a decoupling/move task. ## Verification - `grep -rn "latprobe" hxprobe/` — zero matches in code; comment mentions reworded to be self-contained - `git diff --stat python/latprobe/` — empty (exact revert to last commit) - `make hx-deps` — creates `hxprobe/.venv`, installs `httpx[http2]` from `hxprobe/pyproject.toml` - `make hx-run ARGS="-v http://github.com"` — same output as before the move (HTTP/2, 1 redirect followed, IP/TLS/cert shown) - `make hx-test` — all hermetic hxprobe tests pass standalone (own venv, own `PYTHONPATH=hxprobe`) - `make hx-test-integration` — live HTTP/2 + redirect tests still pass - `make py-test` — `latprobe`'s own suite passes with zero setup (no `py-deps`/venv needed), confirming the revert didn't leave residue - `make check` (top-level) — Go + latprobe + hxprobe all green in one gate - `python -m latprobe --help` output confirmed unchanged from before the whole hxprobe feature existed (no leftover `--no-http2` etc.)