Skip to content

Add Plan 55 for metrics list/rank and shared abstraction - #22

Merged
jeduden merged 1 commit into
mainfrom
codex/plan-55-metrics-rank-cli
Feb 15, 2026
Merged

Add Plan 55 for metrics list/rank and shared abstraction#22
jeduden merged 1 commit into
mainfrom
codex/plan-55-metrics-rank-cli

Conversation

@jeduden

@jeduden jeduden commented Feb 15, 2026

Copy link
Copy Markdown
Owner

Summary

  • add Plan 55: DU-style metrics ranking roadmap
  • require a shared internal/metrics abstraction for all metric consumers
  • define CLI shape as mdsmith metrics list and mdsmith metrics rank
  • require rules-like metric docs in internal/metrics/-/README.md and help metrics <id|name>
  • register Plan 55 in PLAN.md

Validation

  • go run ./cmd/mdsmith check PLAN.md plan/55_du-style-metrics-ranking.md

@jeduden
jeduden force-pushed the codex/plan-55-metrics-rank-cli branch from c2d5edc to 50efd0d Compare February 15, 2026 20:09
@jeduden
jeduden merged commit bad3944 into main Feb 15, 2026
3 checks passed
jeduden pushed a commit that referenced this pull request May 8, 2026
mdsmith currently uses two schema languages in a
single schema file: CUE for front matter, a YAML
scope tree for body structure (plans 132/142/143).
This spike investigates whether to unify them, the
directions the unification could take, and the
trade-offs of each.

Inputs: the mdbase research (gaps S-1 through
S-7), plus a fresh web survey covering CUE, JSON
Schema, TypeSpec, Pkl, KCL, Dhall, RELAX NG, and
Schematron, plus a survey of every published
Markdown linter / SSG / vault tool.

Headline findings:

- No off-the-shelf schema language solves both
  halves well. Every choice trades something.
- The whole ecosystem treats Markdown body
  validation as an unsolved problem (markdownlint
  #22 has been open since 2016). mdsmith is in
  green-field territory for the body half.
- The RELAX NG + Schematron split (grammar +
  rules) maps onto mdsmith's existing MDS020 +
  rule-plugins split. A "single language for both"
  goal fights this established pattern.

Five directions sketched with the same toy schema
(runbook). Preliminary recommendation: a single
YAML DSL with a `cue:` escape hatch for the small
fraction of FM constraints that need CUE's full
power. Fallback: keep two surfaces with shared
named-type primitives (plan 134).

Plan 132 now points at the spike. The spike's
final recommendation will fold back into the plan.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ
jeduden pushed a commit that referenced this pull request May 10, 2026
mdsmith currently uses two schema languages in a
single schema file: CUE for front matter, a YAML
scope tree for body structure (plans 132/142/143).
This spike investigates whether to unify them, the
directions the unification could take, and the
trade-offs of each.

Inputs: the mdbase research (gaps S-1 through
S-7), plus a fresh web survey covering CUE, JSON
Schema, TypeSpec, Pkl, KCL, Dhall, RELAX NG, and
Schematron, plus a survey of every published
Markdown linter / SSG / vault tool.

Headline findings:

- No off-the-shelf schema language solves both
  halves well. Every choice trades something.
- The whole ecosystem treats Markdown body
  validation as an unsolved problem (markdownlint
  #22 has been open since 2016). mdsmith is in
  green-field territory for the body half.
- The RELAX NG + Schematron split (grammar +
  rules) maps onto mdsmith's existing MDS020 +
  rule-plugins split. A "single language for both"
  goal fights this established pattern.

Five directions sketched with the same toy schema
(runbook). Preliminary recommendation: a single
YAML DSL with a `cue:` escape hatch for the small
fraction of FM constraints that need CUE's full
power. Fallback: keep two surfaces with shared
named-type primitives (plan 134).

Plan 132 now points at the spike. The spike's
final recommendation will fold back into the plan.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ
jeduden added a commit that referenced this pull request May 10, 2026
…, quick-win plans, spike (#242)

* Plans 132-136: schema language ergonomics + actionable diagnostics

Schema-language work derived from the mdbase research
(docs/research/mdbase-vs-mdsmith/learn-from-mdbase.md):

- 132 inline schema in kinds (S-1)
- 133 actionable schema diagnostics for MDS020
- 134 named field-type shortcuts (S-2)
- 135 schema inheritance via extends (S-3)
- 136 field deprecation flag (S-6)

Plan 133 is the cross-cutting "errors are actionable"
work — one diagnostic per failure, with field, actual,
expected, schema reference, and best-effort hints.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Plans 137-140: dry-run, backlinks, field-presence, path-pattern

Quick-win plans from the mdbase research, S effort each:

- 137 fix --dry-run (C-6)
- 138 backlinks subcommand (L-4)
- 139 field-presence kind assignment (M-1)
- 140 per-kind path-pattern (M-3)

Plan 140 emits its diagnostic through plan 133's
actionable shape; plans 138 and 139 surface their
matches via plan 95's `mdsmith kinds` provenance.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Address Copilot review on plans 137-139

- 137: point at runFix in cmd/mdsmith/main.go (no
  cmd/mdsmith/fix file exists today)
- 138: drop the claim that backlinks JSON shape
  matches `mdsmith query --format json` (query has
  no --format flag yet); document the shape
  directly
- 139: replace the non-existent
  internal/config/kinds.go with the actual location
  (resolveEffectiveKinds in merge.go, plus
  provenance.go)

PR description updated separately to reflect that
137-140 are part of this PR.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Address Copilot follow-up review on plans 137-140

- 137: drop fixed=0 from the dry-run example (the
  text already says we omit the fixed= field);
  replace [...] with [] inside the JSON snippet so
  it parses
- 138: replace ellipsis with two concrete records
  in the JSON example
- 139: prose now references kinds: (the list key)
  instead of an explicit kind: tag
- 140: example uses kinds: [plan] in prose; the
  mock kinds show output uses the actual <name>:
  YAML key

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Address Copilot follow-up: glob syntax, package name, kinds: key

- 132: filename require uses glob syntax. Changed
  TASK-[0-9]+.md to TASK-[0-9][0-9][0-9][0-9].md so
  the example actually matches a 4-digit task ID
  (the + was reading as a literal plus).
- 134: shortcut library declares `package types` to
  match the import path tail and the `types.#date`
  identifier used downstream.
- 139: prose now consistently references the
  `kinds:` list (front matter) rather than a
  singular `kind:` field. Two more occurrences in
  the summary and Background sections updated.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Address Copilot follow-up: kinds CLI subcommand names

- 135, 140: every reference to `mdsmith kinds <name>`
  now uses the implemented `mdsmith kinds show <name>`
- 139: every reference to `mdsmith kinds <file>` now
  uses `mdsmith kinds resolve <file>`
- 135: example output reshaped to the existing
  `kinds show` YAML format (`<name>:` keying, `# from`
  provenance comments)
- 139: example output reshaped to the existing
  `kinds resolve` text format (`file:` then
  `effective kinds:`)

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Address Copilot follow-up: anchor datetime regex; fix matcher semantics

- 134: #datetime now ends with the same kind of
  end anchor as #date and #time, with an optional
  trailing Z or numeric offset to cover ISO-8601
  timezones.
- 139: drop the "short-circuits on the first match"
  claim. The matcher unions kinds across every
  matching entry, the same as resolveEffectiveKinds
  does today; the plan's surface only adds a new
  selector type, not a new precedence rule.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Address Copilot follow-up: shortcut split, RuleID/RuleName

- 134: split shortcut-resolution semantics into two
  cases. A bare YAML scalar (single identifier, no
  quotes/operators) must resolve in the registry
  or the loader errors. Anything else passes
  through as raw CUE. This reconciles the prose
  with the existing "unknown bare name -> error"
  acceptance criterion.
- 133: reframe task 6 and the matching AC around
  RuleID/RuleName (the lint diagnostic shape)
  rather than Code/Source (the LSP wire shape).
  The LSP conversion in internal/lint/diagnostic.go
  already maps RuleID -> Code and sets Source.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Address Copilot follow-up: inheritance model, struct name, glob

- 135: pick a single inheritance model (CUE
  refinement under unification). Child must
  satisfy parent. A child that narrows the parent
  is OK; a child that conflicts produces the
  "cannot unify" diagnostic. Updated the example
  so the failing case is a value outside the
  parent's disjunction (real conflict) rather
  than a type that "wins".
- 139: KindAssignment -> KindAssignmentEntry to
  match the actual struct name in
  internal/config/config.go.
- 140: example glob now requires at least one
  digit (plan/[0-9][0-9]*_*.md). The previous
  pattern would have matched plan/_slug.md.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Split plan 132 into 132/142/143 (engine, content, cross-refs)

Plan 132 grew past the file-length cap once we
folded in the rich body schema (S-7) and the
per-scope rule overrides. Splitting into three
keeps each focused and lets later plans land
incrementally:

- 132 — schema engine: two sources (inline +
  proto.md), AST-rooted scope tree, sections
  with aliases / children / sequential
  numbering, per-scope rule overrides. Existing
  rules reuse-with-no-code-change; the engine
  re-implements MDS020 with no fixture
  changes.
- 142 — section content constraints: max-words,
  forbidden-starts, required-patterns, etc.
  Each is a one-walk pass over the section
  body; diagnostics use plan 133's shape.
- 143 — cross-references, acronyms, index
  side-output. The three S-7 capabilities that
  span the document.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Reframe plan 142 as a content rule pack

Replace the schema-shape keys (max-words,
forbidden-starts, etc.) with normal mdsmith
rules:

- MDS036 grows max-words / min-words /
  max-paragraphs settings alongside its current
  line-based cap
- MDS055 forbidden-paragraph-starts (new)
- MDS056 forbidden-text (new)
- MDS057 required-text-patterns (new)
- MDS058 required-mentions (new)

All four new rules are default-disabled. They
configure document-wide today through the
existing rules: surface and per-section via
plan 132's per-scope override mechanism. The
schema language gets no new shape; the work is
in the rule set.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Plan 132: recursive sections, level inferred from depth

Heading hierarchy is the natural shape of the
section tree, so the schema models it directly:

- One recursive sections: list (no separate
  children:/fields: keys)
- Heading text only — no #-markers; the level
  is the section's depth in the tree
- repeats: + sequential: + min: / max: on a
  section whose children are a sequence rather
  than fixed
- Document H1 stays the title; sections: starts
  at H2

Plan 143 follows the same convention: scope:
matches by heading text, must-match: takes
text only.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Address Copilot follow-up: heading text, glob accuracy

- 132 / 142: drop residual ## markers from
  heading: examples to match the level-from-depth
  convention introduced in the previous commit
- 140: soften the goal text. The path-pattern
  glob can approximate "starts with digits" but
  cannot enforce "all digits, no other
  characters" — the plan now says so

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Plans: depends-on front-matter field

Adds a depends-on: '[...int] | *[]' field to
plan/proto.md so plans can declare other plans
they build on. Default empty list, so existing
plans pass unchanged.

Backfilled the obvious dependencies for the
plans introduced in this PR:

- 134, 142, 143: depend on 132 (schema engine)
- 135, 136: depend on 132 + 133
- 140: depends on 133 (actionable diagnostics)

Earlier plans without obvious in-PR dependencies
(132, 133, 137, 138, 139) keep the empty
default. Older plans (52, 61, ..., 131) carry
the empty default too; they can be backfilled
later as a chore.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Plan 132: open vs closed scopes, wildcard slot

Define how the schema handles sections the
document has but the schema does not list:

- Default is open: unlisted headings allowed
  anywhere among listed sections
- closed: true on a scope makes it strict;
  unlisted headings flag a diagnostic
- "..." entry in sections: is a positional
  escape hatch that survives closed: true and
  marks where unlisted sections are tolerated

Listed sections still appear in declared order
in either mode. Optional sections may be
skipped without breaking neighbors' order.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Plan 133: drop GitHub #L<line> anchor from example

The schema_ref example in plan 133 used a
markdown link with a #L4 anchor. MDS027 resolves
anchors against heading slugs, not
GitHub-style line anchors, so the example was a
misleading idiom. Replace with plain text;
linenumber-as-anchor is a wire-format detail
that doesn't belong in a markdown link anyway.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Schema-language unification spike

mdsmith currently uses two schema languages in a
single schema file: CUE for front matter, a YAML
scope tree for body structure (plans 132/142/143).
This spike investigates whether to unify them, the
directions the unification could take, and the
trade-offs of each.

Inputs: the mdbase research (gaps S-1 through
S-7), plus a fresh web survey covering CUE, JSON
Schema, TypeSpec, Pkl, KCL, Dhall, RELAX NG, and
Schematron, plus a survey of every published
Markdown linter / SSG / vault tool.

Headline findings:

- No off-the-shelf schema language solves both
  halves well. Every choice trades something.
- The whole ecosystem treats Markdown body
  validation as an unsolved problem (markdownlint
  #22 has been open since 2016). mdsmith is in
  green-field territory for the body half.
- The RELAX NG + Schematron split (grammar +
  rules) maps onto mdsmith's existing MDS020 +
  rule-plugins split. A "single language for both"
  goal fights this established pattern.

Five directions sketched with the same toy schema
(runbook). Preliminary recommendation: a single
YAML DSL with a `cue:` escape hatch for the small
fraction of FM constraints that need CUE's full
power. Fallback: keep two surfaces with shared
named-type primitives (plan 134).

Plan 132 now points at the spike. The spike's
final recommendation will fold back into the plan.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Plan 144 + Direction B deep-dive spike

- Plan 144: numeric sort mode for the catalog
  directive. PLAN.md sorts on `id` lexicographically
  today (52 lands after 100). The plan adds
  `sort: numeric:id` that parses each entry's
  field as int and sorts numerically when all
  parse, falling back to string compare otherwise.
  Renumbering existing plans is out of scope —
  it would break every commit/PR/review reference.

- Direction B deep dive: a full spec for the YAML
  DSL that the spike preliminarily recommends.
  Walks the surface end to end: bare-name
  shortcuts, constraint maps per type, a `cue:`
  escape valve for cross-field constraints.
  Five worked examples (ADR, runbook, plan,
  deck, deck-with-Slide-{n}). A translation
  table from each existing CUE shape to the
  YAML form. End-to-end validator trace through
  one runbook. Migration story.

The deep dive is research; nothing in this PR
commits to shipping it. Plan 132 still points
at the spike as the gating decision.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Renumber 132/133/134 → 146/147/148 to disambiguate

The rebase against main brought new plans at IDs
132 (claude-code-plugin), 133 (lsp-hover), and
134 (lsp-completion). With the new depends-on
field referencing plan IDs, duplicates make
dependencies ambiguous.

Renumber my plans to free IDs (146/147/148) so
each ID is unique:

- 132_inline-schema-in-kinds → 146
- 133_actionable-schema-diagnostics → 147
- 134_named-field-type-shortcuts → 148

depends-on lists in 135/136/140/142/143 updated
accordingly. Inline references throughout the
plan files, the spike, and the deep-dive doc
re-pointed.

Also addresses three other Copilot comments:

- Plan 144: pick one naming, "numeric:" prefix,
  consistently throughout the plan.
- Plan 137: keep failures= in the stats line so
  the dry-run output stays machine-parsable
  alongside the existing fix output. fixed=0
  literal, would-fix=N additive.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

* Revert collateral damage from renumber + fix copilot drift

The blanket renumbering script in the previous
commit accidentally also rewrote main's plans
122 and 152, where "plan 132/133/134" referred
to main's claude-code-plugin / lsp-hover /
lsp-completion plans (not mine). Revert those
two files to origin/main.

Also fix a few stragglers:

- direction-b-deep-dive.md: SchemaDiagnostic
  reference now points at plan 147 (was 133).
- 137 AC: the summary line keeps fixed= alongside
  would-fix=, matching the design section. The
  earlier "omits fixed=N" bullet contradicted it.
- 144 summary: rename "numeric-id sort mode" to
  "numeric: sort prefix" to match the rest of
  the plan.

https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ

---------

Co-authored-by: Claude <noreply@anthropic.com>
jeduden pushed a commit that referenced this pull request May 19, 2026
…ssue refs

Lines like "  #22 \"Mandatory headings\"" and "  #288.**" are GitHub
issue/PR references soft-wrapped inside list items, not malformed ATX
headings. Computing `after` before the MD023 check and returning nil
when after[0] is a digit avoids flagging these lines.

Add TestCheck_IssueReference, TestCheck_IssueReferenceAtColumn1, and
TestFix_IssueReferenceUnchanged to pin the behaviour.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz
jeduden pushed a commit that referenced this pull request May 19, 2026
The previous guard returned nil for any '#'+digit line regardless of
indentation. That silenced MD018 for genuine malformed headings like
#1Heading or ##22Title at column 1.

Scope the guard to leading>0: a '#'+digit run on an indented line is
almost certainly a soft-wrapped issue/PR reference (#22, #288) — at
column 1 it is a missing-space defect and is flagged normally.

Add TestCheck_DigitAtColumn1IsFlagged and TestFix_DigitAtColumn1 to
pin the column-1 behaviour and prevent regression.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz
jeduden added a commit that referenced this pull request May 19, 2026
* feat: add MDS059 atx-heading-whitespace rule

Implements rule MDS059 covering the markdownlint ATX-heading
whitespace family (MD018–MD021) and heading indentation (MD023).

Detects missing space, multiple spaces, closed ATX markers, and
leading indentation on raw ATX heading lines. Autofix normalizes
every defect to open ATX with a single space and no leading indent.
Skips fenced/indented code blocks and directive bodies.

Closes plan 176.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

* test(MDS059): add coverage tests for internal helpers

Three statement-coverage gaps in rule.go — the all-hash guard in
checkClosingATX, the level-out-of-range guard in normalizeLine, and
the empty-string early return in extractContent — are only reachable
by calling the unexported functions directly.  Add
rule_coverage_test.go to hit all three paths and reach 100% statement
coverage.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

* fix(MDS059): address Copilot review — CRLF, tab, C# false positive

- Add \r to TrimRight calls so CRLF line endings never produce
  spurious "missing space after # in heading" diagnostics on
  empty ATX headings (##\r\n).

- Flag a tab immediately after opening hashes as "missing space"
  and normalise it to a single space (#\tHeading → # Heading).

- Only treat a trailing # run as a closing ATX marker when
  preceded by whitespace (CommonMark rule). Previously, content
  like "# C#" was wrongly diagnosed and mangled by Fix. The
  case with no preceding space now returns nil cleanly.
  Consequence: #Heading# is fixed only for MD018 (missing
  opening space) → # Heading#; MD020 coverage downgraded to
  partial in the coverage matrix.

- Update rule README to reflect accurate behaviour.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

* fix(MDS059): address second Copilot review

- Coverage matrix: correct preamble to 41 of 52 (2 partial),
  11 remaining; remove completed plan 176 from plan list.

- normalizeLine: preserve trailing \r when the original line has
  one so CRLF files don't get mixed LF/CRLF after a partial fix.
  Test: TestFix_PreservesCRLFOnRewrittenLines.

- Add TestCheck_SkipsPIBlock and TestFix_PreservesPIBlock to pin
  the directive-body (PI block) skip behaviour for Check and Fix.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

* fix(test): drain interleaved notifications in lspPipe.shutdown

shutdown() used request(), which reads exactly one frame. After a
rename the server emits publishDiagnostics notifications that can
arrive before the shutdown response, causing the id==99 assertion
to see nil. Switch to requestPickResult(), which already loops past
interleaved server frames, matching the pattern used by every other
multi-step LSP test helper.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

* docs(plan 176): clarify CommonMark interpretation for #Heading# case

Split the acceptance criterion that covered both `# Heading #` and
`#Heading#` into separate bullets. The `#Heading#` bullet now explicitly
notes that per CommonMark a trailing `#` without preceding whitespace is
content, not a closing marker, so MD020 is partial for that case. This
matches the implementation and resolves the Copilot review comment about
the checked-off criterion contradicting the fix output.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

* fix(MDS064): rename message to "multiple spaces or tabs after # in heading"

leadingSpaces() counts tabs as well as spaces, so the check at line 71
fires for mixed whitespace like "# \tHeading" (space then tab). The old
message "multiple spaces after # in heading" was inaccurate in that case.
Rename to "multiple spaces or tabs after # in heading" and add tests for
the space+tab pattern.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

* fix(MDS064): rename package headingwhitespace → atxheadingwhitespace; register in all.go

The package name headingwhitespace did not follow the repo convention
of deriving the directory name from the rule name with hyphens removed
(blockquote-whitespace → blockquotewhitespace, list-marker-space →
listmarkerspace). Rename to atxheadingwhitespace for consistency.

Also fixes a production bug: the package was never blank-imported in
internal/rules/all/all.go, so MDS064 was not registered and would
never run in cmd/mdsmith. Add the import in alphabetical order.

Also updates plan/176 front matter and design text to replace the
provisional MDS060 ID with the shipped MDS064.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

* fix(MDS064): skip '#' followed by digit to avoid false positives on issue refs

Lines like "  #22 \"Mandatory headings\"" and "  #288.**" are GitHub
issue/PR references soft-wrapped inside list items, not malformed ATX
headings. Computing `after` before the MD023 check and returning nil
when after[0] is a digit avoids flagging these lines.

Add TestCheck_IssueReference, TestCheck_IssueReferenceAtColumn1, and
TestFix_IssueReferenceUnchanged to pin the behaviour.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

* fix(lint): sort atxheadingwhitespace import into alphabetical position

The import was left at the position where headingwhitespace used to sit
(after headingstyle) instead of its correct alphabetical slot after
ambiguousemphasis. Moves it so gofmt/goimports is satisfied.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

* docs(MDS064): clarify space requirement only applies when heading has content

The README said the opening hashes must be followed by "exactly one space"
without qualification, implying empty headings like "##" are invalid. Reword
to make clear the space check only applies when the heading has content;
empty headings are valid and produce no diagnostic.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

* fix(MDS064): narrow digit guard to indented lines only

The previous guard returned nil for any '#'+digit line regardless of
indentation. That silenced MD018 for genuine malformed headings like
#1Heading or ##22Title at column 1.

Scope the guard to leading>0: a '#'+digit run on an indented line is
almost certainly a soft-wrapped issue/PR reference (#22, #288) — at
column 1 it is a missing-space defect and is flagged normally.

Add TestCheck_DigitAtColumn1IsFlagged and TestFix_DigitAtColumn1 to
pin the column-1 behaviour and prevent regression.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant