← Back to Projects

leptons1618@anishgiri.dev:~/case-studies/artifact-report less writeup.md

DevTools2026 September 21, 2026 4 min read

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.

ModeForWhat it looks like
indexaudits, research briefs, status recordsCool white, ultramarine trace, dense sans/mono hierarchy
signalrelease, security and operational reviewsNear-black, cyan trace, high-contrast decision band
essaypostmortems and chronology-led explanationsCool 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

  1. 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.
  2. Three visual modes (`index`, `signal`, `essay`) selected from the findings' shape, or pinned with `--variant` when the subject demands one.
  3. 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.
  4. Severity is always a text label first — `CRITICAL`, `MAJOR`, `MINOR`, `NOTE`, `PASS` — so a report reads correctly in print and in high-contrast mode.
  5. Nothing is marked `PASS` without naming the check that passed, and no score appears unless the report supplies the rubric it was calculated from.

04Stack

PythonHTMLCSSJSONMarkdown