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-api.mdreferences/

Markdownlint Programmatic API

Installation

npm install markdownlint

Basic Usage

Callback-Based API

const markdownlint = require('markdownlint');

const options = {
  files: ['README.md', 'docs/**/*.md'],
  config: {
    default: true,
    MD013: { line_length: 120 }
  }
};

markdownlint(options, (err, result) => {
  if (err) {
    console.error(err);
    return;
  }
  
  console.log(result.toString());
  // README.md: 23: MD013/line-length Line length [Expected: 120; Actual: 145]
});

Promise-Based API

const { markdownlint } = require('markdownlint/promise');

async function lintFiles() {
  const options = {
    files: ['README.md', 'docs/**/*.md'],
    config: {
      default: true,
      MD013: { line_length: 120 }
    }
  };
  
  try {
    const result = await markdownlint(options);
    console.log(result.toString());
  } catch (err) {
    console.error(err);
  }
}

lintFiles();

Synchronous API

const { markdownlintSync } = require('markdownlint/sync');

const options = {
  files: ['README.md'],
  config: {
    default: true
  }
};

const result = markdownlintSync(options);
console.log(result.toString());

Options Object

Basic Options

const options = {
  // Files to lint (glob patterns supported)
  files: ['**/*.md'],
  
  // Or strings to lint
  strings: {
    'content1': '# Heading\n\nContent here',
    'content2': '# Another\n\nMore content'
  },
  
  // Configuration object
  config: {
    default: true,
    MD013: { line_length: 120 }
  },
  
  // Custom rules
  customRules: [
    require('./custom-rule')
  ],
  
  // Directories to search for rules
  markdownItPlugins: [
    [require('markdown-it-emoji')]
  ]
};

Advanced Options

const options = {
  files: ['README.md'],
  
  config: {
    default: true,
    MD013: false
  },
  
  // Front matter pattern (RegExp or array of RegExp)
  frontMatter: /^---[\s\S]*?---$/,
  
  // Enable/disable inline configuration
  noInlineConfig: false,
  
  // Results version for compatibility
  resultVersion: 3,
  
  // Handle results
  handleRuleFailures: true
};

Working with Results

Result Object Structure

const result = await markdownlint(options);

// result is an object with filename keys:
// {
//   'README.md': [
//     {
//       lineNumber: 23,
//       ruleNames: ['MD013', 'line-length'],
//       ruleDescription: 'Line length',
//       ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md013',
//       errorDetail: 'Expected: 120; Actual: 145',
//       errorContext: 'This is a very long line that...',
//       errorRange: [1, 145],
//       fixInfo: null
//     }
//   ]
// }

Processing Results

const result = await markdownlint(options);

// Check if any errors found
const hasErrors = Object.keys(result).some(
  file => result[file].length > 0
);

if (hasErrors) {
  console.error('Linting errors found:');
  
  Object.keys(result).forEach(file => {
    const errors = result[file];
    if (errors.length > 0) {
      console.error(`\n${file}:`);
      errors.forEach(error => {
        console.error(
          `  ${error.lineNumber}: ${error.ruleNames[0]} ` +
          `${error.ruleDescription} ${error.errorDetail || ''}`
        );
      });
    }
  });
  
  process.exit(1);
}

Counting Errors

const result = await markdownlint(options);

let totalErrors = 0;
const errorsByRule = {};

Object.values(result).forEach(fileErrors => {
  totalErrors += fileErrors.length;
  
  fileErrors.forEach(error => {
    const ruleName = error.ruleNames[0];
    errorsByRule[ruleName] = (errorsByRule[ruleName] || 0) + 1;
  });
});

console.log(`Total errors: ${totalErrors}`);
console.log('Errors by rule:', errorsByRule);

Linting Strings

Single String

const { markdownlint } = require('markdownlint/promise');

async function lintString(content) {
  const result = await markdownlint({
    strings: {
      'content': content
    },
    config: {
      default: true
    }
  });
  
  return result.content || [];
}

const errors = await lintString('# Heading\n\nSome content');

Multiple Strings

const result = await markdownlint({
  strings: {
    'file1': '# First\n\nContent',
    'file2': '# Second\n\nMore content',
    'file3': '# Third\n\nEven more'
  },
  config: {
    default: true
  }
});

// Access results by key
console.log(result.file1);
console.log(result.file2);
console.log(result.file3);

Reading Configuration

Load Config from File

const { readConfig } = require('markdownlint/sync');

// Read .markdownlint.json
const config = readConfigSync('.markdownlint.json');

const result = await markdownlint({
  files: ['README.md'],
  config: config
});

Merge Configurations

const baseConfig = readConfigSync('.markdownlint.json');
const overrides = {
  MD013: { line_length: 150 }
};

const config = { ...baseConfig, ...overrides };

const result = await markdownlint({
  files: ['README.md'],
  config: config
});

Applying Fixes

Get Fix Information

const result = await markdownlint(options);

Object.keys(result).forEach(file => {
  result[file].forEach(error => {
    if (error.fixInfo) {
      console.log(`${file}:${error.lineNumber} can be auto-fixed`);
    }
  });
});

Apply Fixes

const { markdownlint } = require('markdownlint/promise');
const { applyFixes } = require('markdownlint');
const fs = require('fs').promises;

async function lintAndFix(filePath) {
  // Read file
  const content = await fs.readFile(filePath, 'utf8');
  
  // Lint
  const result = await markdownlint({
    strings: {
      [filePath]: content
    },
    config: {
      default: true
    }
  });
  
  // Apply fixes
  const fixed = applyFixes(content, result[filePath]);
  
  // Write back
  await fs.writeFile(filePath, fixed, 'utf8');
  
  console.log(`Fixed ${filePath}`);
}

await lintAndFix('README.md');

Bulk Fix Multiple Files

const { markdownlint } = require('markdownlint/promise');
const { applyFixes } = require('markdownlint');
const fs = require('fs').promises;
const glob = require('glob');

async function fixAllFiles(pattern) {
  const files = glob.sync(pattern);
  
  for (const file of files) {
    const content = await fs.readFile(file, 'utf8');
    
    const result = await markdownlint({
      strings: { [file]: content },
      config: { default: true }
    });
    
    if (result[file] && result[file].length > 0) {
      const fixed = applyFixes(content, result[file]);
      
      if (fixed !== content) {
        await fs.writeFile(file, fixed, 'utf8');
        console.log(`Fixed ${file}`);
      }
    }
  }
}

await fixAllFiles('docs/**/*.md');

Custom Rules

Creating a Custom Rule

// custom-no-lorem.js
module.exports = {
  names: ['no-lorem'],
  description: 'Disallow lorem ipsum text',
  tags: ['test'],
  function: (params, onError) => {
    params.tokens.forEach(token => {
      if (token.type === 'inline') {
        const content = token.content.toLowerCase();
        if (content.includes('lorem ipsum')) {
          onError({
            lineNumber: token.lineNumber,
            detail: 'Found "lorem ipsum" placeholder text',
            context: token.line
          });
        }
      }
    });
  }
};

Using Custom Rules

const customRule = require('./custom-no-lorem');

const result = await markdownlint({
  files: ['README.md'],
  config: {
    default: true
  },
  customRules: [customRule]
});

Integration Examples

Express.js API Endpoint

const express = require('express');
const { markdownlint } = require('markdownlint/promise');

const app = express();
app.use(express.json());

app.post('/api/lint', async (req, res) => {
  const { content } = req.body;
  
  try {
    const result = await markdownlint({
      strings: { 'input': content },
      config: { default: true }
    });
    
    res.json({
      errors: result.input || [],
      success: (result.input || []).length === 0
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000);

Build Tool Integration

// gulpfile.js
const gulp = require('gulp');
const { markdownlint } = require('markdownlint/promise');
const through2 = require('through2');

function lintMarkdown() {
  return gulp.src('**/*.md')
    .pipe(through2.obj(async (file, enc, callback) => {
      const result = await markdownlint({
        strings: {
          [file.path]: file.contents.toString()
        },
        config: { default: true }
      });
      
      const errors = result[file.path] || [];
      if (errors.length > 0) {
        console.error(`${file.path}:`);
        errors.forEach(err => {
          console.error(`  ${err.lineNumber}: ${err.ruleDescription}`);
        });
      }
      
      callback(null, file);
    }));
}

gulp.task('lint:md', lintMarkdown);

Best Practices

  1. Use Promise API - Cleaner async/await syntax
  2. Cache Config - Load configuration once and reuse
  3. Batch Operations - Lint multiple files in one call
  4. Handle Errors - Always catch and handle errors properly
  5. Apply Fixes Carefully - Test fixes before writing to disk
  6. Custom Rules - Create project-specific validation rules
  7. Report Formatting - Format results for readability

Common Pitfalls

  1. Async/Sync Mismatch - Don't mix callback, promise, and sync APIs
  2. Missing Config - Always provide a config object
  3. Forgetting to Handle Errors - Check result object for errors
  4. Not Testing Fixes - Verify fixes don't break content
  5. Memory Issues - Don't load all files at once for large projects
  6. Config Caching - Don't reload config on every lint call

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