Skip to content

Commit 5ec598a

Browse files
Copilotjeduden
andauthored
docs: add plans 124-127 for missing rules (space-in-code, space-in-links, proper-names, single-H1)
Agent-Logs-Url: https://github.com/jeduden/mdsmith/sessions/86a0ecef-882c-4f8e-b485-e94e86adbad1 Co-authored-by: jeduden <1117699+jeduden@users.noreply.github.com>
1 parent 2a871e1 commit 5ec598a

6 files changed

Lines changed: 616 additions & 12 deletions

File tree

PLAN.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ footer: |
3232
| 113 | 🔲 | sonnet | [User-defined Markdown conventions](plan/113_user-defined-profiles.md) |
3333
| 114 || sonnet | [MDS034 message clarity and flavor-vs-rule docs](plan/114_mds034-message-and-flavor-vs-rule-docs.md) |
3434
| 120 | 🔲 | sonnet | [Unify glob matcher and field naming across mdsmith](plan/120_glob-unification.md) |
35+
| 124 | 🔲 | sonnet | [No space inside code spans rule](plan/124_no-space-in-code-spans.md) |
36+
| 125 | 🔲 | sonnet | [No space inside link text rule](plan/125_no-space-in-link-text.md) |
37+
| 126 | 🔲 | sonnet | [Proper-name capitalization rule](plan/126_proper-names.md) |
38+
| 127 | 🔲 | sonnet | [Single H1 per file rule](plan/127_single-h1.md) |
3539
| 52 || | [Archetype / Template Library for Agentic Patterns](plan/52_archetype-template-library.md) |
3640
| 61 || | [Required Structure Rule Hardening](plan/61_required-structure-hardening.md) |
3741
| 65 || | [Spike WASM-Embedded Weasel Inference](plan/65_spike-wasm-embedded-inference.md) |

docs/background/markdown-linters.md

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -249,26 +249,28 @@ Weaknesses:
249249
| Emphasis style | planned ([plan 106][plan106]) | [MD049][md049], [MD050][md050] | [emphasis-marker][rl-em] |
250250
| HR style | planned ([plan 108][plan108]) | [MD035][md035] | [rule-style][rl-rs] |
251251
| Ambiguous emphasis | planned ([plan 111][plan111]) | [MD037][md037] | no |
252-
| Space in code | no | [MD038][md038] | no |
253-
| Space in links | no | [MD039][md039] | no |
254-
| Proper names | no | [MD044][md044] | no |
252+
| Space in code | planned ([plan 124][plan124]) | [MD038][md038] | no |
253+
| Space in links | planned ([plan 125][plan125]) | [MD039][md039] | no |
254+
| Proper names | planned ([plan 126][plan126]) | [MD044][md044] | no |
255255
| Required headings | [MDS020][mds020] (via schema) | [MD043][md043] | no |
256-
| Single H1 | no | [MD047][md047] | no |
256+
| Single H1 | planned ([plan 127][plan127]) | [MD047][md047] | no |
257257
| Link fragments | [MDS027][mds027] (cross-file) | [MD051][md051] | no |
258258
| Reference links | planned ([plan 107][plan107]) | [MD052][md052], [MD053][md053] | no |
259259

260260
All three cover core structural rules. markdownlint has
261-
the broadest rule set. mdsmith has plans for inline
262-
HTML ([plan 105][plan105]), UL marker style
261+
the broadest rule set. Plans cover inline HTML
262+
([plan 105][plan105]), UL marker style
263263
([plan 109][plan109]), emphasis style
264-
([plan 106][plan106]), HR style ([plan 108][plan108]),
265-
ambiguous emphasis ([plan 111][plan111]), and
266-
reference-style links ([plan 107][plan107]).
264+
([plan 106][plan106]), and HR style
265+
([plan 108][plan108]). More plans cover ambiguous
266+
emphasis ([plan 111][plan111]), reference-style links
267+
([plan 107][plan107]), no-space-in-code-spans
268+
([plan 124][plan124]), no-space-in-link-text
269+
([plan 125][plan125]), proper names
270+
([plan 126][plan126]), and single H1
271+
([plan 127][plan127]).
267272
Image alt text ([MDS032][mds032]) and OL numbering
268273
([MDS046][mds046]) are already implemented.
269-
Teams that need the remaining gaps (space in code,
270-
space in links, proper names, single H1) can pair
271-
mdsmith with markdownlint.
272274

273275
### Prose and Readability
274276

@@ -747,3 +749,7 @@ you need a stable rule set while these land.
747749
[plan111]: ../../plan/111_ambiguous-emphasis.md
748750
[plan113]: ../../plan/113_user-defined-profiles.md
749751
[plan120]: ../../plan/120_glob-unification.md
752+
[plan124]: ../../plan/124_no-space-in-code-spans.md
753+
[plan125]: ../../plan/125_no-space-in-link-text.md
754+
[plan126]: ../../plan/126_proper-names.md
755+
[plan127]: ../../plan/127_single-h1.md

plan/124_no-space-in-code-spans.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
id: 124
3+
title: No space inside code spans rule
4+
status: "🔲"
5+
summary: >-
6+
New rule MDS048 that flags inline code spans with
7+
leading or trailing whitespace inside the backticks
8+
(`` ` x ` `` instead of `` `x` ``). Closes the gap
9+
with markdownlint MD038.
10+
model: sonnet
11+
---
12+
# No space inside code spans rule
13+
14+
## Goal
15+
16+
Let users forbid stray whitespace inside inline code
17+
spans. CommonMark strips one optional space on each
18+
side of a code span when both sides have one, but any
19+
other leading or trailing whitespace renders verbatim
20+
and is almost always a typo (`` ` x` ``,
21+
`` `x ` ``, `` `x ` ``). markdownlint covers this as
22+
[MD038][md038]; mdsmith does not.
23+
24+
## Background
25+
26+
### What goldmark exposes
27+
28+
Inline code spans are `*ast.CodeSpan`. The node's
29+
text segment range covers the bytes between the
30+
backtick delimiters, *before* CommonMark's "trim one
31+
space on each side if both sides have one" rule is
32+
applied. The rule must read those raw bytes to
33+
distinguish "balanced single space" (legal) from any
34+
other whitespace pattern (flagged).
35+
36+
### Why a separate rule
37+
38+
MDS010 pins fence style and MDS011 requires a fence
39+
language. Neither inspects the *contents* of inline
40+
code spans. A dedicated rule keeps the toggle
41+
independent of fenced-block policy.
42+
43+
## Design
44+
45+
### Configuration
46+
47+
```yaml
48+
rules:
49+
no-space-in-code-spans:
50+
enabled: true
51+
```
52+
53+
Category: `whitespace`. Disabled by default (opt-in).
54+
No tunables in v1 — the only choice is whether to
55+
enforce.
56+
57+
### Detection
58+
59+
Walk `*ast.CodeSpan`. For each node:
60+
61+
1. Read the span's source bytes (between the
62+
delimiters).
63+
2. If both the first and last byte are a single
64+
ASCII space and no other whitespace is present at
65+
the boundaries, treat as the CommonMark
66+
single-space-trim case and skip.
67+
3. Otherwise, if the bytes start with whitespace,
68+
emit `code span has leading whitespace`.
69+
4. If the bytes end with whitespace, emit `code
70+
span has trailing whitespace`.
71+
72+
Tabs, newlines, and runs of two or more spaces at
73+
either boundary always emit. The single-space-trim
74+
exception only covers one ASCII space on each side.
75+
76+
### Auto-fix
77+
78+
Trim leading and trailing whitespace from the span
79+
bytes. Preserve the delimiter count (one or more
80+
backticks). When the trimmed body becomes empty, do
81+
not auto-fix — emit only the diagnostic.
82+
83+
### Error messages
84+
85+
```text
86+
code span has leading whitespace
87+
code span has trailing whitespace
88+
```
89+
90+
## Tasks
91+
92+
1. Scaffold `internal/rules/nospaceincodespans/` with
93+
`rule.go`, `rule_test.go`, and the `init()`
94+
`rule.Register` call.
95+
2. Implement `Check()` walking `*ast.CodeSpan` and
96+
reading the raw source bytes between delimiters.
97+
3. Implement `Fix()` that trims whitespace inside the
98+
delimiters while preserving backtick count.
99+
4. Implement `rule.Defaultable` returning `false`.
100+
5. Register as MDS048 in category `whitespace`.
101+
6. Add fixture tests in
102+
`internal/rules/MDS048-no-space-in-code-spans/`
103+
covering: balanced single space (legal), leading
104+
space, trailing space, both-side double space,
105+
tab, newline-inside (rare), and the empty-after-
106+
trim edge case.
107+
7. Add rule README following the MDS012 template.
108+
109+
## Acceptance Criteria
110+
111+
- [ ] `` `x` `` emits no diagnostic.
112+
- [ ] `` ` x ` `` (balanced single space) emits no
113+
diagnostic.
114+
- [ ] `` ` x` `` emits one leading-whitespace
115+
diagnostic and fixes to `` `x` ``.
116+
- [ ] `` `x ` `` emits one trailing-whitespace
117+
diagnostic and fixes to `` `x` ``.
118+
- [ ] `` ` x ` `` (double space each side) emits
119+
both diagnostics and fixes to `` `x` ``.
120+
- [ ] `` `\tx` `` (leading tab) emits a leading-
121+
whitespace diagnostic.
122+
- [ ] An empty-after-trim span (e.g. `` ` ` ``)
123+
emits diagnostics but is not auto-fixed.
124+
- [ ] Rule is disabled by default.
125+
- [ ] All tests pass: `go test ./...`
126+
- [ ] `go tool golangci-lint run` reports no issues
127+
- [ ] `mdsmith check .` passes on the repo with the
128+
rule disabled (no regression for existing
129+
docs).

plan/125_no-space-in-link-text.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
---
2+
id: 125
3+
title: No space inside link text rule
4+
status: "🔲"
5+
summary: >-
6+
New rule MDS049 that flags Markdown links and
7+
images whose visible text has leading or trailing
8+
whitespace inside the brackets. Closes the gap with
9+
markdownlint MD039.
10+
model: sonnet
11+
---
12+
# No space inside link text rule
13+
14+
## Goal
15+
16+
Let users forbid stray whitespace inside the visible
17+
text of links and images. `[ click here ](url)`
18+
renders the spaces as part of the underlined link
19+
text in most renderers, which looks broken. The same
20+
applies to image alt text. markdownlint covers this
21+
as [MD039][md039]; mdsmith does not.
22+
23+
## Background
24+
25+
### What goldmark exposes
26+
27+
Inline links are `*ast.Link`. Inline images are
28+
`*ast.Image`. The visible text is the node's child
29+
sequence, but the rule needs the *source bytes*
30+
inside the `[...]` brackets to detect whitespace
31+
flanking the text — the AST already trims to text
32+
nodes that may have lost the whitespace.
33+
34+
The rule reads `f.Source` between the opening `[` and
35+
the closing `]` for each link/image node and inspects
36+
the boundary bytes.
37+
38+
### Why a separate rule
39+
40+
MDS012 (no-bare-urls) and MDS027 (cross-file
41+
reference integrity) handle URLs and link targets.
42+
Neither looks at the *text* between the brackets. A
43+
dedicated rule keeps text-formatting policy
44+
independent of URL policy.
45+
46+
Reference-style links are out of scope here —
47+
[plan 107](107_no-reference-style.md) forbids them
48+
entirely. Until that rule is enabled, MDS049 also
49+
applies to reference-link text (`[ text ][ref]`)
50+
because the text portion is still visible.
51+
52+
## Design
53+
54+
### Configuration
55+
56+
```yaml
57+
rules:
58+
no-space-in-link-text:
59+
enabled: true
60+
check-images: true
61+
```
62+
63+
Category: `link`. Disabled by default (opt-in).
64+
`check-images` allows opting out of image alt-text
65+
checking when MDS032 (no-empty-alt-text) is doing
66+
heavier alt-text work.
67+
68+
### Detection
69+
70+
Walk `*ast.Link` and `*ast.Image`. For each node:
71+
72+
1. Locate the bytes between the opening `[` and the
73+
matching closing `]` in `f.Source`.
74+
2. If the first byte is whitespace (space, tab),
75+
emit `link text has leading whitespace` (or
76+
`image alt text has leading whitespace`).
77+
3. If the last byte is whitespace, emit the
78+
trailing-whitespace variant.
79+
4. Skip when `check-images: false` and the node is
80+
`*ast.Image`.
81+
82+
Newlines inside the brackets are *not* flagged —
83+
they often reflect intentional wrapping of long
84+
link text.
85+
86+
### Auto-fix
87+
88+
Trim leading and trailing whitespace inside the
89+
brackets. Keep the brackets and any trailing
90+
reference label or URL parenthetical untouched.
91+
92+
### Error messages
93+
94+
```text
95+
link text has leading whitespace
96+
link text has trailing whitespace
97+
image alt text has leading whitespace
98+
image alt text has trailing whitespace
99+
```
100+
101+
## Tasks
102+
103+
1. Scaffold `internal/rules/nospaceinlinktext/` with
104+
`rule.go`, `rule_test.go`, and the `init()`
105+
`rule.Register` call.
106+
2. Implement `Check()` walking `*ast.Link` and
107+
`*ast.Image`, locating the bracket span in
108+
`f.Source`.
109+
3. Implement `rule.Configurable` for `check-images`.
110+
4. Implement `Fix()` that trims whitespace inside
111+
the brackets.
112+
5. Implement `rule.Defaultable` returning `false`.
113+
6. Register as MDS049 in category `link`.
114+
7. Add fixture tests in
115+
`internal/rules/MDS049-no-space-in-link-text/`
116+
covering: clean link, leading space, trailing
117+
space, both, image alt with leading space,
118+
reference link with whitespace text, and a link
119+
whose text wraps across a newline (not flagged).
120+
8. Add rule README following the MDS012 template.
121+
122+
## Acceptance Criteria
123+
124+
- [ ] `[text](url)` emits no diagnostic.
125+
- [ ] `[ text ](url)` emits leading and trailing
126+
diagnostics and fixes to `[text](url)`.
127+
- [ ] `[text ](url)` emits one trailing diagnostic.
128+
- [ ] `![ alt ](img.png)` emits two diagnostics with
129+
`image alt text` wording and fixes to
130+
`![alt](img.png)`.
131+
- [ ] `![ alt ](img.png)` emits no diagnostic when
132+
`check-images: false`.
133+
- [ ] A link whose text spans two source lines
134+
(newline between words) emits no diagnostic.
135+
- [ ] `[ text ][ref]` emits diagnostics on the text
136+
portion only.
137+
- [ ] Rule is disabled by default.
138+
- [ ] All tests pass: `go test ./...`
139+
- [ ] `go tool golangci-lint run` reports no issues
140+
- [ ] `mdsmith check .` passes on the repo with the
141+
rule disabled (no regression for existing
142+
docs).

0 commit comments

Comments
 (0)