Reports#
Every table, chart, badge, and changelog entry on this site is rendered from a baseline, or a pair of them, rather than typed by hand. The example below calls the real renderer directly on a small baseline of its own.
Perf tables and badges#
use serde_json::json;
use soothfast_report::{badges, perf_table};
fn main() {
let baseline = json!({ "items": { "demo::checksum": {
"perfcnt": { "instructions": 812 },
"walltime": { "median_ns": 41.2, "p99_ns": 58.0 },
"alloc": { "allocs": 0, "bytes": 0 }
}}});
print!("{}", perf_table::markdown(&baseline));
println!("{}", badges::coverage_badge("docs", 92));
println!("{}", badges::gate_badge(Some(true)));
}
recorded outputcargo soothfast docs capture
| item | instructions | median | p99 | allocs | polls |
|---|---:|---:|---:|---:|---:|
| `demo::checksum` | 812 | 41.2ns | 58.0ns | 0 | n/a |
{"color":"brightgreen","label":"docs","message":"92%","schemaVersion":1}
{"color":"brightgreen","label":"soothfast gate","message":"passing","schemaVersion":1}perf_table::rows is the shared data extraction step. markdown and html
both call it and are otherwise identical. badges::badge is the raw
shields.io endpoint shape ({schemaVersion, label, message, color}), and
coverage_badge and gate_badge are the two callers that pick color for
you: green at 90% or above, yellow at 70% or above, red below that, and
passing, failing or unknown for a gate verdict.
cargo soothfast report render#
$ cargo soothfast report render -p mylib --baseline self
report: wrote docs/perf/summary.md
report: wrote docs/perf/summary.html
report: wrote docs/perf/badges/gate.json
report: wrote docs/perf/llms.txt
summary.md and summary.html are perf_table::markdown and html over
the baseline. badges/gate.json reads .soothfast/gate-status.json, written
by the last gate run, through badges::gate_badge.
The trend SVGs (trend-instructions.svg, trend-walltime_median_ns.svg,
trend-allocs.svg) only appear once .soothfast/trend.jsonl holds at least
two points for that metric. Below that trend_chart::render returns None
and report render skips the file rather than draw an empty axis.
llms.txt is written only when -p PKG is given. It is PKG's public
surface, meaning each signature and its first doc line, joined against the
baseline by id and falling back to covers=. An agent reading it can answer
"what does X measure right now" without guessing from a README that may be
out of date.
Trend and changelog#
cargo soothfast trend append -p mylib appends one line per run
({unix, commit, noise_pct, items}) to .soothfast/trend.jsonl. That file
feeds both the SVG charts above and trend render, which prints each
metric's drift from first to last point straight to stdout. It writes no
file of its own, since charting is report render's job.
--from-baseline NAME builds the point from a saved baseline instead of
measuring again — a pipeline whose gate already ran with --save-baseline
appends the very numbers it just verified. -p PKG scopes the point the
same way it scopes a measured run: other packages' entries and buildcost
pseudo-items in the shared baseline file stay out.
cargo soothfast report changelog -p mylib --against-ref v1.0 merges a
fresh "Unreleased" section into CHANGELOG.md in place. Every
## [released] section already there survives. Re-running it is idempotent:
it replaces the Unreleased section rather than appending a duplicate.
The draft leads with what merged since v1.0, grouped into Features, Fixes,
Performance, Documentation, Dependencies and Internal by each commit's type,
each entry carrying the pull request its subject named. A scope survives as
a prefix on the entry (fix(server): drop a cache lists as server: Drop a
cache), a deps scope files the entry under Dependencies whatever its type,
and a subject marked ! leads the section list under Breaking changes. That
comes from git log, not a forge API, since every merge lands as a squash
whose subject already holds the number. Release commits are dropped, and so
is everything the soothfast bot authored, so a release does not list its own
paperwork; any other subject that is not a conventional commit is printed
when the draft is written, so an omission is visible rather than silent.
The section icons come from a shipped default. Override any subset in
soothfast.toml; a name that is not a section is an error rather than a
silent default:
[changelog.icons]
features = "📐"
performance = "⏱"The same file carries the flags report changelog would otherwise need on
every invocation:
[changelog]
features = "full"
packages = ["core", "server"]
bot-author = "soothfast-bot[bot]"features decides what the API surface diff can see: a public item behind a
feature that is not enabled never shows up in it, so too narrow a set shortens
the diff with nothing to say it did. It falls back to [gate] features when
absent. packages supplies -p when the command line gives none. An explicit
--features or -p still wins over either.
bot-author names the commit author whose entries are dropped: the bot that
lands regenerated artifacts, whose commits are paperwork rather than changes.
It defaults to soothfast-bot[bot], what the action commits as, so only a
repo that renamed bot-slug has to set it; --bot-author NAME overrides it
for one run. That one author is dropped and no other. Every other bot stays,
dependabot[bot] included: it writes the whole Dependencies section, and a
rule matching any [bot] author would delete it without a word.
Below a rule sit the derived sections, evidence rather than narrative: the
public API diff against v1.0, and the measured movement past gate
thresholds. A section with nothing to report is omitted rather than shipped
holding a sentence saying so, and a draft with nothing at all to report is
its heading alone.
Hand-written prose survives the same regeneration by living inside a
<!-- soothfast:notes --> / <!-- /soothfast:notes --> pair anywhere in the
Unreleased section. Add it once; every later run carries it forward, spliced
back in right after the heading, until the section is renamed to a real
version and frozen like the rest of the released history.
A section with no notes yet gets that pair with commented-out prompts for the two things no measurement supplies: what the release means for someone using it, and what a consumer has to do. They stay comments until someone writes into them, so an untouched block renders as nothing.
Pass several -p PKG flags to cover more than one crate, and leave
--against-ref off entirely for a first release, where there is no earlier
version to diff against and the draft lists the surface it ships instead.
Once CHANGELOG.md carries a prior release, omitting it is refused rather
than silently replacing a populated Unreleased section with an empty one.