You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The default text formatter prints one flat list per severity in rule-execution order: a single file's problems are scattered across non-adjacent clusters, the full path is repeated on every line, and verbatim-identical messages are never collapsed. This proposes the layout every comparable linter (eslint, ruff) settled on — group by file with one header per file and aligned line / severity / rule / message rows, dedup repeated identical messages, and add a ruff-style --statistics flag for per-rule counts. Text output only; JSON/SARIF/HTML are untouched.
Text is the default format every user sees, and the payoff lands hardest during onboarding, when violation counts are highest and the flat list is at its worst.
Why
Verified against main (5d2dbf9, 2026-07-06):
src/skillsaw/formatters/text.py (lines 45–72) emits three flat per-severity lists in rule-execution order — no file grouping, no sort, no dedup, full relative path repeated on every row.
Self-linting this repo shows the cost concretely: 70 info violations shown (74 found, 4 baseline-suppressed); skills/skillsaw-onboard/SKILL.md appears on 8 non-adjacent lines across 3 rule clusters; one verbatim-identical terminology message repeats 11 times.
No --statistics flag exists anywhere (src, docs, README).
Partial precedent already in-tree: src/skillsaw/formatters/html.py:46 sorts violations by (severity, file, line) — but HTML only, and without file headers.
Proposed implementation
Rewrite the violation-list section of format_text in src/skillsaw/formatters/text.py: sort by (file, line, severity), emit one relative-path header per file, then aligned columns of line:col, severity, rule id, message. Severity moves from section headers into a per-row column. Preserve the existing show_info/fail_level filtering and NO_COLOR handling.
Dedup in two layers: within a file, collapse runs of identical rule+message into one row listing the line numbers; across files, fold verbatim-identical messages (the terminology case above) under the first occurrence as "same in N other files". This dedup design is the only contested surface and the main reason this is M rather than S — worth agreeing on the exact presentation before writing code.
Add --statistics to the lint subparser in src/skillsaw/cli/_parser.py and wire it through src/skillsaw/cli/_lint.py: ruff-style per-rule violation counts, sorted descending. Text format only.
Leave the Scanned: / Summary: / Rule docs sections and the grade block untouched; leave JSON/SARIF/HTML formatters unchanged.
Gotchas found during verification:
Breakage risk is low. Existing tests assert loosely on strings like Summary:, Errors:, and All checks passed — the Summary: block retains those labels, so most pass unchanged; there are no golden snapshots of text output. Tests that assert on the old per-severity section layout in tests/test_formatters.py will need updating.
action.yml never parses text violation lines, so CI consumers can't break on the layout change. It does have a pre-existing, unrelated bug worth its own issue: the lint step writes the report with --format text --output "$REPORT_FILE" and then json.loads it for the errors/warnings outputs — that parse always fails and is silently masked by 2>/dev/null || echo "0".
Effort: M — a focused rewrite of one formatter function plus one new flag; a few days including the dedup design discussion and test updates, not weeks.
Out of scope:
Any change to JSON/SARIF/HTML output or the Scanned:/Summary:/grade sections.
TTY detection, --color, or hyperlink work (separate idea, separate issue).
Fixing the action.yml summary-parsing bug noted above (separate issue; this change neither fixes nor worsens it).
The default text formatter prints one flat list per severity in rule-execution order: a single file's problems are scattered across non-adjacent clusters, the full path is repeated on every line, and verbatim-identical messages are never collapsed. This proposes the layout every comparable linter (eslint, ruff) settled on — group by file with one header per file and aligned
line / severity / rule / messagerows, dedup repeated identical messages, and add a ruff-style--statisticsflag for per-rule counts. Text output only; JSON/SARIF/HTML are untouched.Text is the default format every user sees, and the payoff lands hardest during onboarding, when violation counts are highest and the flat list is at its worst.
Why
Verified against main (
5d2dbf9, 2026-07-06):src/skillsaw/formatters/text.py(lines 45–72) emits three flat per-severity lists in rule-execution order — no file grouping, no sort, no dedup, full relative path repeated on every row.skills/skillsaw-onboard/SKILL.mdappears on 8 non-adjacent lines across 3 rule clusters; one verbatim-identical terminology message repeats 11 times.--statisticsflag exists anywhere (src, docs, README).src/skillsaw/formatters/html.py:46sorts violations by (severity, file, line) — but HTML only, and without file headers.Proposed implementation
format_textinsrc/skillsaw/formatters/text.py: sort by (file, line, severity), emit one relative-path header per file, then aligned columns ofline:col, severity, rule id, message. Severity moves from section headers into a per-row column. Preserve the existingshow_info/fail_levelfiltering andNO_COLORhandling.--statisticsto the lint subparser insrc/skillsaw/cli/_parser.pyand wire it throughsrc/skillsaw/cli/_lint.py: ruff-style per-rule violation counts, sorted descending. Text format only.Scanned:/Summary:/Rule docssections and the grade block untouched; leave JSON/SARIF/HTML formatters unchanged.Gotchas found during verification:
Summary:,Errors:, andAll checks passed— theSummary:block retains those labels, so most pass unchanged; there are no golden snapshots of text output. Tests that assert on the old per-severity section layout intests/test_formatters.pywill need updating.action.ymlnever parses text violation lines, so CI consumers can't break on the layout change. It does have a pre-existing, unrelated bug worth its own issue: the lint step writes the report with--format text --output "$REPORT_FILE"and thenjson.loads it for the errors/warnings outputs — that parse always fails and is silently masked by2>/dev/null || echo "0".[*]/[?]fixability markers insidefmt_violation. The new grouped row format must carry those markers as a column; plan to land after Close the lint-to-fix loop: fixable markers and fix hints in lint output #409 (or rebase onto it) rather than racing it.Files to touch:
src/skillsaw/formatters/text.pysrc/skillsaw/formatters/__init__.pysrc/skillsaw/cli/_parser.pysrc/skillsaw/cli/_lint.pytests/test_formatters.pytests/test_integration.pydocs/cli.mdREADME.mdScope
Effort: M — a focused rewrite of one formatter function plus one new flag; a few days including the dedup design discussion and test updates, not weeks.
Out of scope:
Scanned:/Summary:/grade sections.--color, or hyperlink work (separate idea, separate issue).action.ymlsummary-parsing bug noted above (separate issue; this change neither fixes nor worsens it).Interactions with open PRs:
formatters/text.py— see gotcha above; sequence after it.formatters/__init__.pyandtests/test_formatters.py.skillsaw context: context-window cost report for agent content #398 / Addskillsaw badge --large: self-contained SVG report card #400 / Add git-aware diff linting:skillsaw lint --since REF#401: all touchcli/_parser.py; trivial conflicts on the new--statisticsflag.Surfaced and adversarially verified by a multi-agent codebase exploration. 🤖 Generated with Claude Code