Skip to content

Latest commit

 

History

History
345 lines (300 loc) · 31.4 KB

File metadata and controls

345 lines (300 loc) · 31.4 KB

CLAUDE.md

Project

mdsmith — a Markdown linter written in Go.

Docs

Development Workflow

  • Any change follows Red/Green TDD: failing test, then pass, then commit
  • Keep commits small and focused on one change
  • Run mdsmith check . before committing; all markdown must pass
  • Never modify .mdsmith.yml (linter configuration) without explicit user consent — this includes rule settings, overrides, ignore patterns, and file-length limits

PR Workflow

Use the /gh-pr-fixup, /gh-resolve-threads, and /merge-queue skills for PR work — they cover rebases, CI monitoring, thread resolution, and merge enqueuing. After every push, request a Copilot re-review (the skills do this automatically).

Plan Maintenance

When implementing work tracked by plan/:

  • Update the plan file as part of implementation, not a follow-up
  • Check off tasks and acceptance criteria as they're verified
  • Move front-matter status: 🔲🔳 on start, when done
  • If implementation deviates, update plan text to match
  • Run mdsmith fix PLAN.md after editing front matter

Terminal Demo (demo.tape)

demo.tape records the demo GIF. Editing notes:

  • Backtick-delimited strings for embedded quotes: Type `cmd 'status: "✅"'`. \" inside double-quoted Type strings crashes VHS
  • A hidden set +e runs at start, so don't append ; true to commands
  • demo/sample.md is in the .mdsmith.yml ignore list; hidden setup copies it to a temp dir for check/fix
  • Keep Sleep durations short (1–2 s) for fast CI renders
  • Use only fixable rules in demo/sample.md (trailing spaces, long lines, bare URLs) so the fix→check flow works

Writing Guidelines

When writing descriptions, state what specific data must satisfy what condition. Name the inputs (front matter fields, glob pattern, heading level), not just the mechanism. Avoid vague verbs (match, sync, reflect) without saying what is checked against what.

Development Reference

Build and test reference for mdsmith contributors. See also:

Build & Test Commands

Requires Go 1.25+. Dev tools (golangci-lint and gobco) build from tools/go.mod, which needs Go 1.25.8+; go.mod itself stays tool-free so go install consumers never inherit a dev tool's go floor.

  • go build ./... — build all packages
  • go test ./... — run all tests
  • go test -run TestName ./... — run a specific test
  • go run ./cmd/mdsmith check . — lint markdown
  • go run ./cmd/mdsmith fix . — auto-fix markdown
  • go tool -modfile=tools/go.mod golangci-lint run — run linter
  • go vet ./... — run go vet

Project Layout

Follows the standard Go project layout:

  • cmd/mdsmith/ — main entry point.
  • internal/ — private packages.
  • internal/rules/<rule-name>/ — rule code (e.g. paragraphstructure/).
  • internal/rules/MDS###-<rule-name>/ — rule README and good/bad fixtures (e.g. MDS024-paragraph-structure/).
  • testdata/ — shared markdown fixtures.
  • pkg/goldmark/ — vendored goldmark fork.

Code Style

  • Follow standard Go conventions (gofmt, goimports).
  • Use golangci-lint for linting.
  • Keep functions small and focused.
  • Error messages: lowercase, no trailing punctuation.
  • Prefer returning errors over panicking.

Defensive Code

Add a defensive branch only when you can drive it red/green. Write the failing test first. Then add the code that takes the branch.

Allocation Budget

A rule's Check allocates ≤ 10 times per call on representative input. Enforced by internal/integration/alloc_budget_test.go; most rules allocate 0–6.

  • Walk f.Lines / f.AST directly.
  • Prefer bytes.IndexByte / bytes.Contains over regexp for fixed searches.
  • Compile every regexp.Regexp at package scope.
  • Pre-size slices with make([]X, 0, n).
  • Reuse loop-local buffers via buf = buf[:0].
  • Return nil, not an empty slice, on no diagnostics.

Test Fixtures

Rule test fixtures live in internal/rules/MDS###-<rule-name>/ (e.g. MDS024-paragraph-structure/). Each rule has good/ and bad/ examples (or good.md / bad.md).

Good fixtures must pass all default-enabled rules plus the rule under test. Opt-in rules are skipped: a good MDS001 fixture need not also satisfy MDS043. When a good fixture uses non-default settings, override them in .mdsmith.yml so mdsmith check . also passes. Bad fixtures are excluded via the ignore: section.

When adding or changing a rule, add both:

  1. Unit tests in rule_test.go (inline markdown, fast red/green). Use require for preconditions and assert for checks; Same/NotSame for pointer identity.
  2. Fixture tests under internal/rules/MDS###-<rule-name>/ with YAML frontmatter specifying expected diagnostics. Discovered automatically by internal/integration/rules_test.go.

Config Merge Semantics

Layered config (defaults → kinds → overrides) is deep-merged rule by rule:

  • Maps merge key by key; siblings set in earlier layers survive partial overrides.
  • Scalar leaves are replaced wholesale.
  • List settings replace by default. Opt into append by implementing rule.ListMerger.SettingMergeMode(key). The placeholder vocabulary is the canonical example.
  • A bool-only layer (rule-name: false) toggles enabled without erasing inherited settings.

New list-typed settings must document the choice next to their ApplySettings handler.

Generated Sections

Content between <?directive ... ?> and <?/directive?> markers is auto-generated. Edit directive parameters or the source file, then run mdsmith fix <file> — never the body by hand. Run mdsmith merge-driver install [files...] once per clone so generated-section conflicts resolve automatically.