docs: hxprobe plans, summaries, explanations, usage, and changelog

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>
This commit is contained in:
2026-07-02 13:22:59 +02:00
parent 13a562e966
commit f487a4b1bd
19 changed files with 2367 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
# Summary: hxprobe reads target URLs from a file
Plan: [docs/plans/2026-07-02-11-14-hxprobe-file-input.md](../plans/2026-07-02-11-14-hxprobe-file-input.md)
## What was built
- **`hxprobe/hxprobe/cli.py`**: new `-f`/`--file PATH` flag, mutually
exclusive with positional `url` args. `urls` positional changed
`nargs="+"``nargs="*"`. New `_load_urls()` helper (same format as
`simple.py`'s `load_sites()`: one URL per line, `#` comments, blank
lines skipped, first token per line) — reimplemented locally rather than
imported, keeping hxprobe's "no imports outside its own directory" rule
intact. Validation added right after `parser.parse_args()`, inside the
same `try/except _ArgExit` block: both-given and neither-given are usage
errors via `parser.error()`; missing/unreadable/empty file are usage
errors via direct `stderr.write()` + `return EXIT_USAGE` (mirroring the
existing `--timeout` invalid-value handling style already in the file).
- **`hxprobe/tests/test_cli.py`**: new `TestCLIFileInput` class, 5 tests —
reads URLs from a temp file successfully, missing file, empty file,
both-sources error, neither-given error.
- **`hxprobe/configs/*.txt`**: 7 new fixtures mirroring
`python/configs/`'s exact set. Each header comment states an "Expected
exit code" that was verified by actually running the fixture through
`hxprobe -f ...` during implementation (not assumed from the
`simple.py` originals, whose blanket 0/1 exit scheme is fundamentally
different from hxprobe's per-failure-class 06 worst-code-wins scheme).
- **`hxprobe/USAGE.md`**: new "Reading URLs from a file (`-f`)" section,
placed after "Multiple URLs", with real captured output for the
successful case, the mutually-exclusive error case, and one failure
fixture (`dns-failure.txt`), plus a table listing all 7 fixtures and
their expected exit codes.
## Key design decisions
- **Manual post-parse validation instead of `argparse`'s
`add_mutually_exclusive_group`** — a variadic positional (`nargs="*"`)
doesn't mix cleanly with argparse's built-in mutually-exclusive-group
machinery. Manual checks after `parser.parse_args()` (still inside the
same `try/except _ArgExit`, using `parser.error()`) give the same
usage-error behavior with full control over the message text.
- **File I/O errors don't go through `parser.error()`** — they use the
same direct `stderr.write()` + `return EXIT_USAGE` pattern already
established for `--timeout` parsing failures, since they're discovered
after parsing succeeds, not during it.
- **Every fixture's exit code was verified live, not assumed.** Two
required real judgment calls the `simple.py` originals didn't need:
`http-errors.txt` and `mixed.txt` both needed `--fail` added to their
demo command (hxprobe treats 4xx as success without it, unlike
`simple.py` which always raises on `HTTPError`) to actually demonstrate
a failure — without `--fail` both would silently show exit 0.
## Deviations from the plan
None of substance. The plan anticipated needing to verify exit codes
live rather than assume them; that anticipation paid off exactly as
expected for `http-errors.txt`/`mixed.txt` (needed `--fail` added) and
`timeout.txt` (needed `--timeout 2s` added to keep the demo fast, and a
note added about this sandbox occasionally short-circuiting one of the two
timeout targets to an immediate "connection refused" — observed directly:
in this session's test run, `10.255.255.1` genuinely timed out; in an
earlier, unrelated test earlier in the session it instead got refused
immediately. The fixture keeps both targets so at least one demonstrates
the real timeout path regardless).
## Verification
- `all-ok.txt` → exit 0, `dns-failure.txt` → 2, `connection-refused.txt`
→ 3, `tls-errors.txt` → 5, `timeout.txt` (with `--timeout 2s`) → 4,
`http-errors.txt` (with `--fail`) → 6, `mixed.txt` (with `--fail`) → 6 —
all confirmed via real `$?` checks (not through a `grep` pipe, which
masks the real exit code — caught and corrected this during testing)
- `uv run pytest tests/test_cli.py -m "not integration"` — 16/16 pass
(11 pre-existing + 5 new)
- `uv run ruff check .` / `uv run ruff format --check .` — clean
- Both-sources-given and missing-file error messages verified against the
doc's pasted output, including the `usage:` block that's actually
printed (an early draft of the doc omitted it — caught on review)