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
Fold table structure (MD055/056/058) into MDS025 table-format (#353)
* Add MDS060 table-structure rule (plan 181)
New default-enabled rule covering markdownlint MD055
(table-pipe-style), MD056 (table-column-count), and MD058
(blanks-around-tables). MD055 and MD058 are autofixed; MD056
is flagged only since a missing cell's content is unknown.
Uses line-based GFM table detection (edge pipes optional) so
it sees the borderless and mixed-pipe tables MDS025's tablefmt
parser cannot. The default `consistent` style is loop-stable
with MDS025 enabled, since MDS025's canonical bordered output
already satisfies it. Generated catalog/include table bodies
are skipped so the source file stays the owner.
Closes the MD055/MD056/MD058 gap in the linter comparison.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* MDS060: insert CRLF-matching blank lines in fix
The MD058 blank-line insertion emitted a bare "" line, which on
a CRLF file produced a lone-LF blank among CRLF rows (mixed
endings). Detect the file's newline style and insert a matching
blank line, mirroring the edge-normalization path that already
preserves trailing carriage returns.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* MDS060: lint blockquoted and indented tables
Extend prefix detection to consume a `>` blockquote-marker
chain (mirroring MDS025's tablefmt), so MD055/MD056/MD058
apply to blockquoted and list-indented tables instead of
being silently skipped. The MD058 blank line inside a
blockquote is the bare `>` marker, not an empty line, so the
blockquote is not broken; CRLF newline style is preserved.
Adds blockquote fixtures and brings the package to 100%
statement coverage (addresses the codecov/patch gate).
Addresses Copilot review feedback on PR #353.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* MDS060: handle escaped trailing pipe; correct MDS025 note
Edge detection treated a literal `\|` at the end of the last
cell as a trailing edge pipe, producing a false MD055/MD056
diagnostic and corrupting the cell on fix. Trailing-pipe
detection, cell counting, and edge normalization now strip a
final `|` only when it is unescaped (even backslash run).
Also correct the README guidance: no_leading_or_trailing does
not oscillate with MDS025. Once MDS060 strips the edges, MDS025
(bordered-only) stops formatting the table; the real tradeoff
is lost column alignment, not a per-pass disagreement. Backed
by a loop-stability test.
Addresses Copilot review feedback on PR #353.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* MDS060: respect backslash parity for unescaped pipes
Three call sites treated every literal `|` as a delimiter
regardless of escaping. A paragraph like `A \| B` could be
mistaken for a table header; a paragraph after a table whose
only pipe was escaped was absorbed as a body row (hiding the
MD058 "missing blank line after" diagnostic); and `splitCells`
treated `\\|` as a single literal pipe rather than an escaped
backslash followed by a real delimiter, miscounting cells for
MD056.
Introduce `containsUnescapedPipe` (used in `isHeader`,
`isSeparator`, `continuesTable`) and rewrite `splitCells` to
toggle an escape state byte by byte. Cell counts and table
boundaries now match the same backslash-parity rule as
`endsWithUnescapedPipe`.
Addresses Copilot review feedback on PR #353.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* MDS060: declare markdownlint frontmatter rules
The rule-readme schema (internal/rules/proto.md) now requires
each README to list the markdownlint rule(s) it covers, so
MDS020 can validate the coverage matrix from front matter
instead of free-form prose.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* MDS060: tighten ATX-heading guard and post-prefix indent
Two header-detection bugs surfaced by Copilot review:
- `isHeader` rejected any line whose trimmed content started with
`#`, but `#1 | Title` is a valid first cell, not an ATX heading.
Limit the guard to actual ATX shape — one to six `#` followed
by space, tab, or end of line — via a new `isATXHeading` helper.
- `parseRow` checked `HasPrefix(c, "|")` against the un-trimmed
row content, so a row like `> | a | b |` (extra indent after
the blockquote marker) had `leading` come out false even though
`logicalCells` already trimmed and treated it as a real edge.
Trim the same way before edge detection.
Addresses Copilot review feedback on PR #353.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* fold table structure into MDS025 table-format
Merge MDS060 (table-structure) into MDS025 so a single rule owns
GFM table parsing, the structure checks (MD055 pipe style, MD056
column count, MD058 blanks-around-tables), and the prettier-style
alignment pass. Users now configure one `table-format` block; a
`mdsmith fix` run is inherently single-pass with no inter-rule
oscillation window.
- Port the GFM parser and MD055/056/058 logic into
internal/rules/tableformat/structure.go.
- Extend tableformat.Rule with a `style` setting (consistent /
leading_and_trailing / no_leading_or_trailing) and chain the
structure fix before the alignment fix on the same Fix call.
- Bring the alignment pass's skip set to parity with the structure
pass via formatSkipLines (code blocks + PI blocks + generated
ranges). The helper builds a fresh map; lint.Collect*BlockLines
return a shared read-only cache.
- Migrate the structure fixtures into
internal/rules/MDS025-table-format/{good,bad,fixed}/ with
merged-rule diagnostic lists. The short-row fixture now expects
both a format diag and the MD056 structure diag; the alignment
pass pads the short cell on Fix.
- Delete the tablestructure package and MDS060 fixture dir; drop
the registrations in internal/rules/all and the integration test.
- Update the MDS025 README, the markdownlint-coverage research
doc, and the linter-comparison background page for the merged
scope.
- Refresh plan/181_table-structure.md to describe the merged
design and tasks.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* recompute GeneratedRanges after structure fix
The structure pass inserts blank lines around tables that need
MD058 fix. Those insertions shift every downstream generated
section by N lines. Fix previously copied f.GeneratedRanges onto
the reparsed buffer, so the alignment pass's skip set pointed at
pre-fix line numbers; tablefmt would then iterate past the
shifted body and rewrite a non-canonical table living inside an
`<?include?>` or `<?catalog?>` directive.
Call gensection.FindAllGeneratedRanges on the reparsed buffer
instead. The new TestFix_RecomputesGeneratedRangesAfterStructure
Insert reproduces the bug (verified by temporarily reverting to
the copy — the body table got reformatted) and gates the fix.
Also rephrase the plan: "inherently single-pass" was imprecise.
The fix engine still loops fixable rules to stability; the new
wording says one Rule.Fix call runs structure + alignment, and
that MDS025 has no second rule to oscillate against. The
package-comment "retired MDS060" is dropped in favor of naming
the markdownlint coverage directly.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* post-rebase fixups: gofmt and README include regen
After rebasing onto main, gofmt collapses the Pad field's comment
alignment and `mdsmith fix` updates the README's `<?include?>`
bodies to pick up the spaced separators main introduced in
good/default.md and good/alignment.md.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* address copilot review: sort, dedupe, CRLF, escape, skip cache
Five correctness/consistency fixes from Copilot's second pass:
1. Check sorts the combined diagnostic slice by (line, column)
after appending structure diagnostics to the format-pass output.
With multiple tables in one file the two streams interleave by
line, and fixture tests compare diagnostics in source order.
2. applyStructureFix dedupes adjacent-table MD058 insertions: two
tables with different prefixes can each schedule a blank at the
same gap (table1's blankAfter[K] + table2's blankBefore[K+1]).
Emitting both produces consecutive blank lines that then trip
MDS008 no-multiple-blanks. Test:
TestMD058_NoDoubleBlankBetweenAdjacentTables.
3. Fix re-normalises line endings to CRLF when the source used it.
tablefmt joins rewritten table lines with bare `\n`, dropping
each row's `\r`, which on CRLF documents leaves the table with
bare-LF endings while every surrounding line keeps `\r\n` — a
mixed-ending output. Test: TestFix_CRLF_RoundTrips_TableLines.
4. Structure escape semantics drop backslash parity and match
tablefmt's GFM rule directly: `\|` is the only escape, so
`\\|` reads as a literal backslash plus an escaped pipe (one
cell), not "escaped backslash + unescaped delimiter" (two
cells). The earlier parity behavior put the structure pass and
tablefmt at odds on inputs containing `\\|`. Updated tests:
TestSplitCells_EscapedPipe, TestEndsWithUnescapedPipe,
TestContainsUnescapedPipe.
5. formatSkipLines returns the cached code-block map directly
when there are no PI blocks and no generated ranges, avoiding
a per-Check allocation on the hot path. The merged map is
built only when one of the other inputs is non-empty.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* markdownlint-coverage: use legend's "partial" for MD056
The coverage matrix used a one-off "⚠️" status marker for MD056
that the file's status legend (✅ / partial / 🔲 plan N) does not
define. Switch to the existing "partial" label so the table is
self-consistent.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* docs: qualify the MD056 "alignment pads short rows" claim
Both the rule README and plan 181 said a fixed file is
structurally clean even when a cell is missing, on the assumption
that the alignment pass would pad the short row. That only holds
for *bordered* tables: tablefmt requires edge pipes on every row,
so a borderless short row survives the fix untouched and MD056
keeps firing. Qualify the claim and tell the user how to resolve
the borderless case (add edges or fill the cell).
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* docs: MD056 covers the delimiter row too
The README said only "body row" cell counts are compared, but the
implementation iterates `t.rows[1:]` so the delimiter row is also
matched against the header. Catching a delimiter row with the
wrong cell count is intentional — a malformed delimiter would
otherwise pass MD056 silently. Reword the spec line to match the
behaviour.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* structure: reject bare-pipe lines as table headers
isHeader accepted a single `|` (no logical cell) as a valid header
because it only checked for an unescaped pipe and a non-separator
shape. That produces false-positive table detection — e.g. `|`
followed by a delimiter-looking line — and diverges from tablefmt,
which requires a row to start/end with `|` and have length >= 2.
Require `countCells(c) > 0` before accepting the line as a header.
Guarded by TestBarePipeNotHeader (red without the guard).
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* perf: stream structure fix + push CRLF into tablefmt
Two perf fixes for Copilot's second-pass review:
- applyStructureFix used to materialise every line as a string,
modify the slice, then rebuild via strings.Join. On a typical
file that's N+5 allocations even when the file has no tables
to rewrite. Replace with a bytes.Buffer pre-sized to the source,
writing untouched rows directly from f.Lines as []byte and only
string-converting the rows that actually need edge-normalisation.
Split into collectStructureEdits + renderStructureFix to keep
each function under the gocognit budget.
- Rule.Fix used to scan the whole output buffer twice
(`bytes.ReplaceAll(\r\n -> \n)` then `\n -> \r\n`) so the CRLF
endings tablefmt stripped from rewritten table rows came back.
Push CRLF awareness into tablefmt.rebuildWithFormattedTables
instead: if any source line ends with `\r`, re-append it to each
formatted row. Drop the post-pass from Fix.
The existing CRLF round-trip test and adjacent-tables dedupe test
still pass.
https://claude.ai/code/session_012X1wVbY7u9DNMpGRhNurzT
* post-rebase: regen rule catalog to include MDS067
origin/main added MDS067 (callout-type) while this branch was open.
The rebase took the local catalog body during conflict resolution;
re-running `mdsmith fix` rebuilds the row.
---------
Co-authored-by: Claude <noreply@anthropic.com>
| 183 | ✅ | sonnet |[Skip DedupeDiagnostics via an audited rule.RepoScoped marker](plan/183_dedupe-diagnostics-repo-scoped-skip.md)|
113
113
| 184 | ✅ | opus |[Automate the cross-tool benchmark on merge to main and publish numbers to the assets branch](plan/184_release-benchmark-automation.md)|
0 commit comments