leptons1618@anishgiri.dev:~/case-studies/artifact-report less writeup.md
Proofline — Reports That Read Like Documents and Parse Like Data
Why the fix for a report nobody can read and a report nobody can parse is to write one of them and project it twice.
- published
- September 21, 2026
- read
- 4 min
- stack
- 5 tools
- outcomes
- 5
- media
- —
01The Problem
A report has two readers. The person needs a document they can scan and act on; the agent needs structured data it can parse without guessing. Existing tooling forces the choice — JSON and Markdown are machine-readable and ugly, styled HTML and PDF are readable and opaque — so the workaround is maintaining a human document and a machine copy side by side, which agree on the day they are written and quietly drift apart after that.
02The Solution
Normalize the content once, then render the human view from it and embed that same normalized object as a JSON block in the page. A single 670-line Python renderer using only the standard library produces one self-contained HTML file in three visual modes — index, signal and essay — chosen from the shape of the findings or pinned explicitly, with severity carried by a text label before any colour and no score emitted unless the report ships the rubric it came from.
A report has two readers
Ask for an audit as a file and you tend to get one of two things. You get JSON — complete, parseable, and unreadable at 2am when the one thing you need to decide is buried in finding fourteen. Or you get a styled page, which reads beautifully and tells a machine absolutely nothing.
The usual fix is to write both: a human document and a machine copy, maintained side by side. They agree on the day you write them. They do not agree on the day somebody adds a finding to one of them.
So build one thing and project it twice
Proofline’s renderer normalizes the input first, then renders the human view from that normalized object. The embedded machine block is the same object, serialized. There is no second authoring surface to drift out of sync — a finding that appears in the prose is in the JSON because both came out of the same dict.
<script type="application/json" id="report-data">{ … }</script>
The build command runs its own structural checks before it writes the file, and one of them is that the human finding order and IDs match the JSON’s. Drift is a build failure, not something a reader has to notice.
Three modes, chosen by shape
The modes are index, signal and essay. They are not themes — each one is a different reading posture.
| Mode | For | What it looks like |
|---|---|---|
index | audits, research briefs, status records | Cool white, ultramarine trace, dense sans/mono hierarchy |
signal | release, security and operational reviews | Near-black, cyan trace, high-contrast decision band |
essay | postmortems and chronology-led explanations | Cool gray, restrained burgundy, serif display headings |
Leave it on auto and the choice is one line of code — if any finding is CRITICAL or MAJOR, you get signal, otherwise index. That is a good default because the only question it is really answering is does this report need to say STOP at a glance? A postmortem has no verdict band to shout with, so it goes to essay and reads like prose.
Severity is a word before it is a colour
Five severities, and every one of them is a text label: CRITICAL, MAJOR, MINOR, NOTE, PASS. Colour reinforces them; it never carries them alone. That is the difference between a report that survives being printed and one that turns into five shades of grey.
The palette in signal is worth a look, because it is the whole mode in about 200 characters:
.variant-signal {
--bg: #080a0f; --surface: #10151d; --ink: #f5f7fb;
--crit: #ff8090; --major: #ffc65c; --minor: #8ebcff; --pass: #68e5aa;
}
One cool accent, five fixed status colours, nothing invented per report.
The two rules that do the most work are unglamorous. Nothing is marked PASS without naming the check that passed — a command, a scenario, an observation. And no score is emitted unless the report ships the rubric it was calculated from, which kills the 73/100 that nobody can reconstruct. A claim with no direct evidence is NOTE, and the report says what would confirm it.
Zero dependencies is a constraint, not a boast
The whole renderer is 670 lines using nothing but the Python standard library. It reads one local JSON file, writes one local HTML file, makes no network requests, embeds no external assets, and never executes report content — the only <script> in the output is the JSON block.
That is not asceticism for its own sake. The moment a report pulls a webfont or a CDN stylesheet, “open the file and it works” stops being true on a plane, and these reports get opened in odd places. The validate subcommand checks for exactly this — one H1, a valid viewport, no link/iframe/img/video tags — and refuses a file that breaks it.
What it deliberately does not do
No filters, no tabs, no collapsible sections. If you want to slice the findings a different way, the JSON block is right there in the page source. That keeps the artifact a real artifact: archivable, diffable, greppable, printable.
No charts, no images, no decorative anything. And no fake metrics — if a finding needs a screenshot, the evidence block holds the path to it rather than embedding it.
Where it lives
It is an Agent Skill in the open skills format, so npx skills add Leptons1618/proofline installs it into whichever agents it detects. The skill is the instructions; the renderer is the deterministic part underneath them. You can also skip the skill entirely and run the script directly:
python scripts/proofline.py build report.json --output report.html
python scripts/proofline.py validate report.html --input report.json
MIT, no dependencies, and the honest caveat the README makes itself: structural validation is not visual inspection. Open it in a browser before you send it.
03Achievements
- One 670-line renderer on the Python standard library alone — no npm, no build step, no network request, and no external asset in the output.
- Three visual modes (`index`, `signal`, `essay`) selected from the findings' shape, or pinned with `--variant` when the subject demands one.
- The embedded `#report-data` JSON block and the visible findings are checked against each other at build time, so drift is a failed build rather than a silent divergence.
- Severity is always a text label first — `CRITICAL`, `MAJOR`, `MINOR`, `NOTE`, `PASS` — so a report reads correctly in print and in high-contrast mode.
- Nothing is marked `PASS` without naming the check that passed, and no score appears unless the report supplies the rubric it was calculated from.