Repository navigation
Commit ce3ddb1
feat: render structured output for step log, job summary, and PR comments (#251)
* feat: render structured output for step log, job summary, and PR comments
Collect results via commit-check --format json and render all three
output surfaces from the structured data instead of scraping plain text:
- step log: grouped sections with ::error annotations per rule ID
- job summary: policy report table with rule links and collapsible details
- PR comment: compact table with rule links and collapsible details
- new result output exposing structured JSON for downstream jobs
* fix: make stdout UTF-8 for Windows runners
Emoji and check marks (✔ ✖ ❌) cannot be encoded by the default cp1252
codec on Windows, crashing the action. Reconfigure stdout/stderr to
UTF-8, matching commit-check core's _reconfigure_io.
* refactor: unify job summary and PR comment rendering
Use a single report renderer for both surfaces with a plain '# Commit
Check' title (dropping 'Policy Report') and identical content on success
and failure. PR comment matching also accepts the old hyphenated title
so existing comments are still updated rather than duplicated.
* refactor: split report title and status into separate lines
The report now opens with a plain '# Commit Check' title line followed
by the status line ('✅ All checks passed (N scopes)' or the failure
count), keeping the PR comment and job summary identical.
* feat: show passed checks in a collapsible section on success
The success report now includes a '<details>' block listing every check
that passed per scope, so users can confirm which rules were actually
evaluated without leaving the summary or PR comment.
* refactor: render passed checks like the step log in the details block
Replace the passed-checks table with a fenced block mirroring the
step log layout (group name followed by indented ✔ scope lines), so the
success report reads the same as the action log.
* feat: show checked values in the success details block
Each scope line now shows the concrete value that was checked (PR
title, commit subject, branch name, author name/email), truncated to
one line, so the details block reads like the step log.
* refactor: truncate checked values at 60 chars in the details block
Long commit subjects wrapped inside the fenced details block, misaligning
the scope lines. Trimming the value at 60 characters keeps the full line
(prefix + value + parentheses) short enough to stay on one line.
* feat: show checked values in the failure table
The scope table now carries a 'Checked value' column (mirroring the
success details), so a failing report answers what exactly was checked
without expanding the failure details.
* feat: show only failed values in the table and all checks in details
The report table now fills the checked-value column only for failed
scopes so failures stand out, and the collapsible details block lists
every scope's value in step-log style (✔/✖) with the failure reason and
suggestion under each failing rule, unifying the pass and fail layouts.
* feat: add CodSpeed-style pass/fail stats to the report header
The status line now reads '❌ 2 failures · ✅ 4 passed (6 scopes)' on
failure and '✅ 11 passed (11 scopes)' on success, mirroring the compact
emoji-plus-count style of CodSpeed reports so the pass ratio is visible
without scanning the table.
* feat: reduce the report table to failed scopes
Passing scopes no longer pad the failure table with dash rows; the
table lists only the failed scopes with their checked value and rule
links, while the collapsible details block keeps the full pass/fail
picture in step-log style.
* docs: document the report output spec and pin it with golden tests
Add an output specification block above render_report showing the exact
success and failure layouts, and golden tests that assert the full
rendered report byte-for-byte so the spec stays accurate as the output
evolves.
* fix: wire the result output through action.yml
The output was declared with only a description, and the step that produces it
had no id. Composite actions do not forward step outputs automatically, so
steps.<id>.outputs.result resolved to the empty string and the fromJSON call in
the README documentation would have failed the calling workflow. The Python
side was already writing the payload to $GITHUB_OUTPUT correctly; the mapping
above it was missing.
The unit tests could not catch this — they exercise set_result_output, not the
action.yml plumbing around it.
Also notes in the README that a downstream step reading the result needs
dry-run or continue-on-error, since a failing check otherwise ends the job
before that step runs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
* feat: rework the report format and identify comments by a hidden marker
Comment ownership was decided by `body.startswith("# Commit Check")`, and every
match but the newest was deleted. A person opening a comment with that heading
would have had it removed with no trace. Comments now carry a hidden
`<!-- commit-check-action -->` marker, the way Codecov, SonarQube and CodSpeed
identify theirs, and deletion is restricted to comments carrying it. A report
from an earlier version has no marker, so it is adopted in place — but only
when a bot posted it, since the only other signal is the title a person can
type by accident.
The header counted two different things at once: failures were counted in
checks while "passed" and the total counted scopes, so one commit failing two
rules rendered "2 failures · 2 passed (3 scopes)" and the numbers did not
reconcile. Counts are now checks throughout — "2 of 4 checks failed" — and the
details summary repeats the same total so the two can be checked against each
other.
The table dropped its Result column: only failed scopes reach the table, so it
read ❌ on every row. The title moved to h2 with the project logo beside it, an
h1 being louder than anything else in a PR comment, and the footer now names
the commit-check version that produced the result, which is the first thing
worth knowing when an outcome looks wrong.
Smaller things found while reading:
- an ImportError from PyGithub made the `except GithubException` clause raise
NameError, which propagates past the `except Exception` under it and would
have killed a step designed to never be fatal
- GITHUB_STEP_SUMMARY was read with os.environ[] at import time, so main.py
could not be imported outside Actions
- the two details renderers differed only by a branch; the unparsable-output
fallback now shows in the report instead of rendering an empty scope
- build_result_body had no caller but its own test, and include_footer was
never passed False
- re and tempfile were imported but unused
- GITHUB_REPOSITORY was passed to get_repo() unchecked, which mypy flags once
PyGithub types are available
The logo ships as PNG in this repository rather than as the SVG on
commit-check.com: GitHub proxies comment images through camo, which handles SVG
unreliably, and a local asset keeps the report free of a cross-repository
dependency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
* fix: count things checked, not rule evaluations
The header on this pull request read "1 of 100 checks failed". The number was
arithmetically right — sixteen commit messages against six enabled rules, plus
two branch rules and one each for author name and email — but it was the wrong
number to show.
It measured the size of the pull request rather than the strictness of the
policy: the same configuration reports 100 on a fifteen-commit branch and 10 on
a one-commit branch. Ninety-six of the hundred were the same six rules run
again per commit, so the total also overstated the work done. Worst of all, the
inflated denominator made a real failure look negligible: one bad commit out of
fifteen is not "1 of 100".
A check is now one thing that was checked — a commit message, the branch, the
author — so the same report reads "1 of 19 checks failed", and the total is
what the reader can count in the details block. Rule-level detail is unchanged;
it lives in the table and the details, which is where someone goes to find out
what to fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
* fix: print the failure reason once and keep the log tree intact
The job log showed the same sentence twice in a row under every failure:
Error: Commit message should use imperative mood (...)
value: feat: settle the report format ...
Commit message should use imperative mood (...)
The first line was an ::error workflow command carrying the first line of the
error, the third was the listing printing the whole error underneath it.
The annotation also broke the listing it sat inside. An ::error renders as a
line of its own wherever it appears, so printing one between the ✖ scope line
and its details split the indented tree apart. And its title= — the only place
the rule ID was written — is shown in the annotations UI, never inline, so the
visible line lost the rule ID entirely.
Annotations are now emitted after every group, one per failure, with the scope
in the message since the title cannot carry it. The listing keeps the reason,
the rule ID, the value, the suggestion and the docs link, indented under the
scope that failed. Passing scopes show the value that was checked, which the
Markdown report already did.
Also drops the "(0 failures)" that unparsable output rendered next to its ✖:
there is no check list to count, and zero next to a cross reads as a
contradiction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
* fix: show the failing value in full and render one tree, not two
The details block gave every passing scope its checked value but replaced it
with the failure count on the one scope that failed, so the offending commit
subject appeared nowhere in the report except the table, truncated at sixty
characters. The value a reader has to act on was the only one they could not
read. It now prints in full underneath the failing scope, where the step log
already showed it.
The two surfaces also disagreed about layout despite a docstring claiming one
mirrored the other — four spaces of indent in the Markdown, six and eight in
the log, and the rule name folded into the reason on one side but not the
other. They now come from a single renderer, with the docs URL as the only
difference: the Markdown report already links the rule ID from the table above.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>1 parent 6337ec1 commit ce3ddb1
5 files changed
Lines changed: 1377 additions & 496 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
201 | 201 | | |
202 | 202 | | |
203 | 203 | | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
204 | 232 | | |
205 | 233 | | |
206 | 234 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
40 | 48 | | |
41 | 49 | | |
42 | 50 | | |
43 | 51 | | |
| 52 | + | |
44 | 53 | | |
45 | 54 | | |
46 | 55 | | |
| |||
Loading
0 commit comments