CtrlK
BlogDocsLog inGet started
Tessl Logo

pantheon-ai/markdown-authoring

Author high-quality Markdown documentation with deterministic structure, lint compliance, and CI integration. Use when writing README files, creating docs pages, fixing markdownlint failures, defining style rules, or wiring markdown checks into pre-commit and pipelines. Keywords: markdown, markdownlint, readme, docs, headings, lists, code fences, links, images, lint config, ci, documentation style.

Overall
score

100%

Does it follow best practices?

Validation for skill structure

Overview
Skills
Evals
Files

lint-ci.mdreferences/

Markdownlint CI/CD Integration

GitHub Actions

Basic Workflow

name: Markdown Lint

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  markdown-lint:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Lint markdown files
        run: npm run lint:md

Using markdownlint-cli2-action

name: Markdown Lint

on: [push, pull_request]

jobs:
  markdown-lint:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Lint markdown
        uses: DavidAnson/markdownlint-cli2-action@v16
        with:
          globs: |
            **/*.md
            #node_modules

With Auto-fix and Commit

name: Markdown Lint & Fix

on:
  pull_request:
    branches: [main]

jobs:
  markdown-lint-fix:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.head_ref }}
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Fix markdown issues
        run: npm run lint:md:fix
      
      - name: Commit changes
        uses: stefanzweifel/git-auto-commit-action@v5
        with:
          commit_message: 'docs: auto-fix markdown lint issues'
          file_pattern: '*.md'

Matrix Strategy (Multiple Node Versions)

name: Markdown Lint

on: [push, pull_request]

jobs:
  markdown-lint:
    runs-on: ${{ matrix.os }}
    
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20]
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      
      - run: npm ci
      - run: npm run lint:md

GitLab CI

Basic Pipeline

stages:
  - test

markdown-lint:
  stage: test
  image: node:20
  
  before_script:
    - npm ci
  
  script:
    - npm run lint:md
  
  only:
    - merge_requests
    - main
    - develop

With Caching

markdown-lint:
  stage: test
  image: node:20
  
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
  
  before_script:
    - npm ci
  
  script:
    - npm run lint:md
  
  artifacts:
    reports:
      junit: markdown-lint-report.xml
    when: always

With Auto-fix

markdown-lint-fix:
  stage: test
  image: node:20
  
  before_script:
    - npm ci
    - git config user.name "GitLab CI"
    - git config user.email "ci@gitlab.com"
  
  script:
    - npm run lint:md:fix
    - |
      if [[ -n $(git status -s) ]]; then
        git add "*.md"
        git commit -m "docs: auto-fix markdown lint issues [skip ci]"
        git push "https://oauth2:${CI_JOB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" HEAD:${CI_COMMIT_REF_NAME}
      fi
  
  only:
    - merge_requests

CircleCI

version: 2.1

executors:
  node-executor:
    docker:
      - image: cimg/node:20.0

jobs:
  markdown-lint:
    executor: node-executor
    
    steps:
      - checkout
      
      - restore_cache:
          keys:
            - node-deps-{{ checksum "package-lock.json" }}
      
      - run:
          name: Install dependencies
          command: npm ci
      
      - save_cache:
          key: node-deps-{{ checksum "package-lock.json" }}
          paths:
            - node_modules
      
      - run:
          name: Lint markdown
          command: npm run lint:md

workflows:
  version: 2
  test:
    jobs:
      - markdown-lint

Jenkins

Declarative Pipeline

pipeline {
    agent any
    
    tools {
        nodejs 'NodeJS 20'
    }
    
    stages {
        stage('Install') {
            steps {
                sh 'npm ci'
            }
        }
        
        stage('Lint Markdown') {
            steps {
                sh 'npm run lint:md'
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        failure {
            emailext(
                subject: "Markdown Lint Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: "Check console output at ${env.BUILD_URL}",
                to: "team@example.com"
            )
        }
    }
}

Scripted Pipeline

node {
    stage('Checkout') {
        checkout scm
    }
    
    stage('Install') {
        nodejs(nodeJSInstallationName: 'NodeJS 20') {
            sh 'npm ci'
        }
    }
    
    stage('Lint Markdown') {
        nodejs(nodeJSInstallationName: 'NodeJS 20') {
            sh 'npm run lint:md'
        }
    }
}

Azure Pipelines

trigger:
  - main
  - develop

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: '20.x'
    displayName: 'Install Node.js'
  
  - script: npm ci
    displayName: 'Install dependencies'
  
  - script: npm run lint:md
    displayName: 'Lint markdown files'
  
  - task: PublishTestResults@2
    condition: always()
    inputs:
      testResultsFormat: 'JUnit'
      testResultsFiles: '**/markdown-lint-report.xml'
      failTaskOnFailedTests: true

Pre-commit Hooks

Husky + lint-staged

package.json:

{
  "scripts": {
    "prepare": "husky install"
  },
  "lint-staged": {
    "*.md": [
      "markdownlint-cli2 --fix",
      "git add"
    ]
  },
  "devDependencies": {
    "husky": "^9.0.0",
    "lint-staged": "^15.0.0",
    "markdownlint-cli2": "^0.12.0"
  }
}

.husky/pre-commit:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged

Setup:

npm install -D husky lint-staged
npx husky install
npx husky add .husky/pre-commit "npx lint-staged"

pre-commit Framework

.pre-commit-config.yaml:

repos:
  - repo: https://github.com/DavidAnson/markdownlint-cli2
    rev: v0.12.1
    hooks:
      - id: markdownlint-cli2
        args: ['--fix']

Setup:

pip install pre-commit
pre-commit install

Docker Integration

Dockerfile

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

RUN npm run lint:md

docker-compose.yml

version: '3.8'

services:
  markdown-lint:
    image: node:20-alpine
    working_dir: /app
    volumes:
      - .:/app
    command: sh -c "npm ci && npm run lint:md"

Usage:

docker-compose run markdown-lint

Monorepo Integration

Turborepo

turbo.json:

{
  "pipeline": {
    "lint:md": {
      "outputs": [],
      "cache": true
    }
  }
}

package.json (root):

{
  "scripts": {
    "lint:md": "turbo run lint:md"
  }
}

packages/docs/package.json:

{
  "scripts": {
    "lint:md": "markdownlint-cli2 \"**/*.md\""
  }
}

Lerna

lerna.json:

{
  "version": "independent",
  "npmClient": "npm",
  "command": {
    "run": {
      "stream": true
    }
  }
}

package.json (root):

{
  "scripts": {
    "lint:md": "lerna run lint:md"
  }
}

Reporting and Metrics

Generate HTML Report

// scripts/markdown-lint-report.js
const { markdownlint } = require('markdownlint/promise');
const fs = require('fs').promises;

async function generateReport() {
  const result = await markdownlint({
    files: ['**/*.md'],
    config: { default: true }
  });
  
  let html = '<html><body><h1>Markdown Lint Report</h1>';
  
  Object.keys(result).forEach(file => {
    const errors = result[file];
    if (errors.length > 0) {
      html += `<h2>${file}</h2><ul>`;
      errors.forEach(err => {
        html += `<li>Line ${err.lineNumber}: ${err.ruleDescription}</li>`;
      });
      html += '</ul>';
    }
  });
  
  html += '</body></html>';
  
  await fs.writeFile('markdown-lint-report.html', html);
}

generateReport();

JSON Output for CI

package.json:

{
  "scripts": {
    "lint:md:json": "markdownlint-cli2 --config .markdownlint-cli2.jsonc \"**/*.md\""
  }
}

.markdownlint-cli2.jsonc:

{
  "outputFormatters": [
    ["markdownlint-cli2-formatter-json", { "path": "markdown-lint-report.json" }]
  ]
}

Best Practices

  1. CI Integration - Run markdown linting on every PR
  2. Pre-commit Hooks - Catch errors before commit
  3. Auto-fix in Development - Use --fix locally, not in CI
  4. Fail Fast - Exit with error code on lint failures
  5. Cache Dependencies - Speed up CI with npm/node_modules caching
  6. Ignore Generated Files - Exclude node_modules, dist, etc.
  7. Version Lock - Pin markdownlint version in package.json
  8. Separate Jobs - Keep linting separate from tests/builds
  9. Report Artifacts - Save lint reports as build artifacts
  10. Matrix Testing - Test on multiple OS/Node versions if needed

Common Pitfalls

  1. Missing Ignore Patterns - Forgetting to ignore node_modules
  2. Wrong Glob Patterns - Using incorrect file patterns
  3. Config Not Found - Config file not in expected location
  4. CI Timeout - Linting too many files without exclusions
  5. No Exit Code Check - Not failing build on lint errors
  6. Auto-fix in CI - Using --fix in CI can cause unexpected commits
  7. Cache Poisoning - Not invalidating cache when config changes
  8. Slow Builds - Not using caching or parallel jobs

Install with Tessl CLI

npx tessl i pantheon-ai/markdown-authoring@0.1.1

references

docs-api.md

docs-changelog.md

docs-organization.md

docs-readme.md

docs-writing-style.md

lint-api.md

lint-ci.md

lint-cli.md

lint-config.md

lint-rules.md

syntax-code-blocks.md

syntax-formatting.md

syntax-headings.md

syntax-links-images.md

syntax-lists.md

syntax-other-elements.md

SKILL.md

tile.json