New metrics.py: collect_metrics() does the per-section extraction a flat
dict walk can't (latency needs label/host/method, TLS label/host, path hops
target/ttl/ip/asn), producing a flat Metric list that render_prometheus()
and render_csv() each format in about 15 lines. Simpler than treating them
as two separate renderers. FORMAT_EXTENSIONS gained "prom"/"csv" as the
single wiring point both parse_formats() and Output._normalize_formats()
already key off of.
New history.py finds a previous JSON report for the same ASN by reusing
the exact filename convention exporter.report_filename() already writes,
so the lookup key can't drift from what's on disk. Wired into diagnose()
before write_report() - a compact vs-previous-run summary
(compare.py::render_diff_brief(), new) prints automatically whenever this
run writes JSON and an earlier one exists for the same network; silent on
every other case (first run, prior run wrote no JSON prints one note
instead of guessing). New --no-auto-diff escapes it per run,
history.auto_diff in config disables it entirely.
New trend.py aggregates N saved JSON reports into per-label sparkline
series via --trend N - read-only, and it doesn't touch the network to find
"this network's" history either: history.latest_key() just reads it off
the newest report filename already on disk.
Growing FORMAT_EXTENSIONS to 5 entries broke three pre-existing tests that
hardcoded the old 3-format set; fixed in this commit.
Live-tested: prom/csv artifacts render correctly, auto-diff fired on a
second run, --trend 3 produced readable output, --compare unaffected.
Part of plans/zesty-zooming-wilkinson.md (gitignored).
Every run used to write all three files (en.md, ru.md, json) unconditionally.
Default is now English Markdown only, sourced from output.formats in
config.yaml; --format/--ru/--json pick exactly what a run writes, and any
explicit --format replaces the default rather than adding to it (all/none
supported). write_report() moved from a fixed 3-positional-arg signature to
a {format: text} mapping so writes are conditional instead of always-three.
The en/ru Markdown cross-link header is now only set when both languages are
actually being written.