Plans (docs/plans/): - 2026-07-01-23-47-py-hxprobe-httpx.md — initial httpx probe design - 2026-07-02-09-32 through 14-05 — standalone project, toolchain, usage doc + Makefile, file input (-f), simplification pass, run-summary footer Summaries (docs/summaries/): one per completed feature, recording what was actually built, deviations from the plan, and verification steps Explanations (docs/explanations/): two deep-dives written during review — hxprobe concurrency model and worst-exit-code + render-loop analysis Usage (docs/usage/hxprobe.md): overview with pointer to hxprobe/USAGE.md for the full runnable reference Walkthrough (docs/py-latprobe-walkthrough.md): narrative tour of the latprobe Python package for interview / code-review context CHANGELOG.md: entries for all hxprobe features (toolchain, usage doc, file input, simplification, run-summary footer) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.5 KiB
Plan: extract hxprobe into a fully standalone top-level project
Context
hxprobe currently lives at python/hxprobe/ and imports shared code from
latprobe (latprobe.probe.{Options,Phase,Result,VerboseDetail,_parse_cert},
latprobe.cli.run). That coupling was a deliberate reuse choice at the time,
but the user now wants hxprobe to be a genuinely independent project — no
from latprobe import ... anywhere, duplication accepted — and, per their
follow-up answer, independent enough that it could be cp -r'd into its own
repo tomorrow: own pyproject.toml, own venv, own Makefile section, own
top-level directory (sibling to go/ and python/), not nested under python/.
Confirmed via git status/git log: everything hxprobe-related
(python/hxprobe/, python/pyproject.toml, python/tests/test_hx_*.py,
docs/usage/py-hxprobe.md, the two docs/plans/docs/summaries entries) is
still uncommitted from this session, and python/latprobe/cli.py /
python/latprobe/probe.py only diverge from the last commit (24ea9c9) by
the reuse-oriented additions made to support hxprobe. So this is a clean,
low-risk restructuring: move already-debugged code, revert latprobe with
git checkout --, no git history surgery needed.
Target layout
hxprobe/ (new, top-level, sibling of go/ and python/)
├── pyproject.toml (own manifest: httpx[http2] dependency)
├── hxprobe/
│ ├── __init__.py
│ ├── probe.py (own Options/Phase/Result/VerboseDetail/CertInfo
│ │ + own _parse_cert/_parse_cert_date + existing
│ │ _Trace/_TimingStream/_TimingBackend/
│ │ _TimingTransport/measure() — unchanged logic)
│ ├── aggregate.py (verbatim copy of latprobe/aggregate.py —
│ │ its `from .probe import Result` is already
│ │ package-relative, needs zero edits)
│ ├── duration.py (verbatim copy of latprobe/duration.py — no
│ │ imports at all)
│ ├── cli.py (full standalone CLI — see below)
│ └── __main__.py (unchanged: `from .cli import run`)
└── tests/
├── __init__.py (empty, matches python/tests/__init__.py)
├── test_probe.py (moved from python/tests/test_hx_probe.py)
├── test_cli.py (moved from python/tests/test_hx_cli.py)
└── test_integration.py (moved from python/tests/test_integration_hx.py)
python/ reverts to containing only latprobe — zero third-party deps, no
pyproject.toml, no venv, exactly its pre-hxprobe state.
Step-by-step
1. Move already-debugged files (preserve the bug fixes already made):
git mv/mv (untracked, so plain mv is fine) python/hxprobe/{probe.py,__init__.py,__main__.py}
to hxprobe/hxprobe/, and the three python/tests/test_hx_*.py /
test_integration_hx.py files to hxprobe/tests/ with the hx_/_hx name
segments dropped (test_probe.py, test_cli.py, test_integration.py).
Do not rewrite these from scratch — they already have the mark_dns /
verbose-on-failure-path / Content-Length-on-keep-alive fixes found during
the original implementation.
2. Inline the dataclasses into hxprobe/hxprobe/probe.py:
Replace from latprobe.probe import Options, Phase, Result, VerboseDetail, _parse_cert
with local definitions copied verbatim from python/latprobe/probe.py:
CertInfo, VerboseDetail (its http_version/redirect_count fields are
now simply always-meaningful, no more "populated only by hxprobe" caveat
comment needed), Options (with follow_redirects/http2 as normal fields,
no more "ignored by socket measure()" caveat), Phase, Result,
_parse_cert, _parse_cert_date. Everything else in the file (_Trace,
_TimingStream, _TimingBackend, _TimingTransport, measure(),
_classify, _unwrap, _fill_phases, _fill_verbose) is untouched.
3. Create hxprobe/hxprobe/aggregate.py and duration.py:
Verbatim copies of python/latprobe/aggregate.py and duration.py.
4. Write hxprobe/hxprobe/cli.py as a full standalone CLI:
Start from the current python/latprobe/cli.py (it already has 100% of
the needed logic, including the --no-http2/--no-follow-redirects flags,
the verbose "Protocol" row, and the JSON http_version/redirect_count
keys — all added earlier specifically for hxprobe). Strip the
generalization scaffolding that only existed to let latprobe share this
code:
- Remove the
MeasureFntype alias and themeasure_fnparameter from_run_samples/run— callmeasuredirectly (module-level import from.probe). - Remove
run()'sprog/description/protocol_flagsparameters — hardcodeprog="hxprobe"and the httpx-specific description. - Make the
--no-http2/--no-follow-redirectsadd_argumentcalls unconditional (drop theif protocol_flags:guard). - Everything else (exit codes,
_ArgExit/_Parser, phase-label/verbose constants, all_print_*/_build_json_entryrendering) carries over unchanged — it's already correct standalone logic.
5. Revert python/latprobe/ to its pre-hxprobe state:
git checkout -- python/latprobe/cli.py python/latprobe/probe.py (safe:
confirmed these are the only diffs since the last commit, and both diffs
are exactly the reuse scaffolding being removed here).
6. Clean up the old shared-package artifacts:
Delete python/hxprobe/, python/pyproject.toml, python/.venv/, and the
three python/tests/test_hx_*/test_integration_hx.py files (now moved).
7. Makefile:
- Remove
PY_VENV/PY_VENV_PYTHONvars and thepy-depstarget; revertpy-test/py-test-integration/py-checkto invoke$(PYTHON)directly with nopy-depsprerequisite (their pre-hxprobe form). Removehx-run/hx-test-integrationfrom the Python section (moving out). - Add
HX_DIR := hxprobe,HX_VENV := $(HX_DIR)/.venv,HX_VENV_PYTHON := $(HX_VENV)/bin/$(PYTHON)and a new "── hxprobe (standalone) ──" section:hx-deps(idempotent venv+pip install, mirrors the removedpy-deps),hx-run,hx-test(hermetic,test_[!i]*.pyglob),hx-test-integration(test_integration.pypattern),hx-check,hx-clean. - Fold into the umbrella targets:
test: go-test py-test hx-test,check: go-check py-check hx-check,clean: go-clean py-clean hx-clean.
8. Docs:
- Rename
docs/usage/py-hxprobe.md→docs/usage/hxprobe.md(drop thepy-prefix — it's no longer part of the Python port). Update: remove the "reuseslatprobe.cli.run()in full" claim (now false — say it's a fully standalone implementation sharing only the design, not the code, withlatprobe); update setup/Makefile-target sections tomake hx-deps/hx-run/hx-test/hx-test-integration; update file paths frompython/hxprobe/tohxprobe/hxprobe/. Keep the TCP_NODELAY/Nagle finding and the example transcripts (still accurate — behavior is unchanged, only location/packaging changed). CHANGELOG.md: new entry describing the extraction.docs/summaries/: new dated summary per the CLAUDE.md convention (leave the original2026-07-02-00-29-py-hxprobe-httpx.mdas-is — it's a historical record of that implementation; this is a follow-up).docs/plans/: save this plan asdocs/plans/<yyyy-mm-dd-hh-mm>-hxprobe-standalone-project.mdat implementation start, per CLAUDE.md.
Verification
rm -rf hxprobe/.venv python/.venv(fresh state) thenmake hx-deps— createshxprobe/.venv, installshttpx[http2]fromhxprobe/pyproject.toml.grep -rn "latprobe" hxprobe/— must return nothing (proves the decoupling).make hx-run ARGS="-v http://github.com"— same output as before (HTTP/2, 1 redirect followed, IP/TLS/cert shown).make hx-test— all hermetic hxprobe tests pass standalone.make hx-test-integration— live HTTP/2 + redirect tests still pass.make py-test— confirmslatprobe's own suite is back to its original, dependency-free form and still green (nopy-depsneeded to run it).make check(top-level) — Go + latprobe + hxprobe all green in one gate.diff <(python -m latprobe --help) <(git show 24ea9c9:python/latprobe/cli.py | ...)— or simpler: confirmpython -m latprobe --helpoutput is byte-identical to before this whole feature existed (no leftover--no-http2etc.).