
Readme Generator
- 6 installs
- 2 repo stars
- Updated August 1, 2026
- vishalsachdev/claude-skills
Helps with documentation tasks.
About
readme-generator is a Claude Code skill for documentation. It helps solo builders move faster with AI-assisted development.
- readme-generator
- Documentation
- AI-coding skill
Readme Generator by the numbers
- 6 all-time installs (skills.sh)
- +1 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #1,201 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/vishalsachdev/claude-skills --skill readme-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6 |
|---|---|
| repo stars | ★ 2 |
| Last updated | August 1, 2026 |
| Repository | vishalsachdev/claude-skills ↗ |
What it does
Helps with documentation tasks.
Files
README Generator
Generate or update a comprehensive README.md file for GitHub repositories following best practices.
Purpose
This skill automates the creation of professional, well-structured README.md files for GitHub repositories. It generates all essential sections including badges for technologies used, project overview, site metrics, getting started instructions, project structure, and contact information. The skill is particularly optimized for MkDocs-based intelligent textbook projects but can be adapted for any repository type.
When to Use This Skill
Use this skill when:
- Starting a new GitHub repository that needs a README.md
- Updating an existing README.md to follow best practices
- After significant project changes that should be documented
- Before publishing or sharing a repository
- When migrating from another documentation system
- After adding new technologies or dependencies
Workflow
Step 1: Analyze Repository Context
Before generating the README, gather information about the repository:
1. Check if README.md already exists in the root directory 2. Identify the repository name from .git/config or the working directory 3. Read mkdocs.yml if it exists to extract:
- Site name
- Site description
- Site URL (for GitHub Pages link)
- Repository URL
4. Check for documentation in /docs directory 5. Identify technologies used (look for package.json, requirements.txt, mkdocs.yml, etc.)
User Dialog Triggers:
- If README.md exists: Ask "README.md already exists. Would you like to update it or create a backup first?"
- If repository URL not found: Ask "What is the GitHub repository URL? (e.g., https://github.com/username/repo-name)"
- If site URL not configured: Ask "Is this site deployed to GitHub Pages? If yes, what's the URL?"
Step 2: Generate Badges
Create badges for all relevant technologies and platforms. Use shields.io format for consistency.
Badge Order:
1. MkDocs (if mkdocs.yml exists) 2. MkDocs Material (if theme is Material) 3. GitHub Pages live badge (if site is deployed) 4. Claude Code badge 5. Claude Skills badge (if .claude/skills or skills/ directory exists) 6. License badge 7. Additional technology badges (Python, JavaScript, p5.js, etc.)
Badge Templates:
[](https://www.mkdocs.org/)
[](https://squidfunk.github.io/mkdocs-material/)
[](SITE_URL)
[](REPO_URL)
[](https://claude.ai/code)
[](https://github.com/dmccreary/claude-skills)Check for these additional badges:
- p5.js:
[](https://p5js.org/) - Python:
[](https://www.python.org/) - JavaScript:
[](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
Step 3: Add License Badge
Look for license information in:
1. LICENSE file in root 2. docs/license.md 3. mkdocs.yml (copyright field)
Default to Creative Commons BY-NC-SA 4.0 if not specified:
[](https://creativecommons.org/licenses/by-nc-sa/4.0/)Other common licenses:
- MIT:
[](https://opensource.org/licenses/MIT) - Apache 2.0:
[](https://opensource.org/licenses/Apache-2.0) - GPL-3.0:
[](https://www.gnu.org/licenses/gpl-3.0)
Step 4: Create Website Link Section
After badges, add a prominent link to the live website (if deployed):
## View the Live Site
Visit the interactive textbook at: [https://username.github.io/repo-name](https://username.github.io/repo-name)Step 5: Write Overview/Short Description
Create a compelling 1-3 paragraph overview that answers:
- What is this project?
- Who is it for?
- Why is it valuable?
- What makes it unique or special?
Guidelines:
- Keep it concise but engaging
- Use active voice
- Highlight key features or benefits
- Mention the educational framework if applicable
- For textbooks: mention target audience (grade level, prerequisites)
Example for Intelligent Textbook:
## Overview
This is an interactive, AI-generated intelligent textbook on [TOPIC] designed for [AUDIENCE]. Built using MkDocs with the Material theme, it incorporates learning graphs, concept dependencies, interactive MicroSims (p5.js simulations), and AI-assisted content generation.
The textbook follows Bloom's Taxonomy (2001 revision) for learning outcomes and uses concept dependency graphs to ensure proper prerequisite sequencing. All content is generated and curated using Claude AI skills, making it a Level 2+ intelligent textbook with interactive elements.
Whether you're a student learning [TOPIC] for the first time or an educator looking for structured course materials, this textbook provides comprehensive coverage with hands-on interactive elements that make complex concepts accessible and engaging.Step 6: Add Site Status and Metrics
Gather and display project metrics to show completeness and scope.
Run Python script to collect metrics:
Call scripts/collect-site-metrics.py (or create it if needed) to gather:
1. Learning Graph Metrics (from docs/learning-graph/):
- Number of concepts in concept graph
- Quality score
- Taxonomy distribution
2. Content Metrics:
- Number of chapters (count directories in
docs/chapters/) - Number of markdown files (
.mdfiles indocs/) - Total word count (sum of all markdown files)
- Number of code blocks
- Number of lists and tables
3. Interactive Elements:
- Number of MicroSims (directories in
docs/sims/) - Number of quizzes (files named
quiz.md) - Total quiz questions (count in quiz files)
4. Educational Resources:
- Number of glossary terms (in
docs/glossary.md) - Number of FAQ questions (in
docs/faq.md) - Number of references (in
docs/references.md)
5. Media Assets:
- Number of images (
.png,.jpg,.svgfiles) - Number of diagrams (Mermaid, vis-network)
Format as a table:
## Site Status and Metrics
| Metric | Count |
|--------|-------|
| Concepts in Learning Graph | 200 |
| Chapters | 13 |
| Markdown Files | 87 |
| Total Words | 45,230 |
| MicroSims | 12 |
| Glossary Terms | 187 |
| FAQ Questions | 42 |
| Quiz Questions | 156 |
| Images | 34 |
| References | 28 |
**Completion Status:** Approximately 85% complete (content generation phase)Book-Specific Metrics:
For specialized textbooks, add domain-specific metrics:
- Circuits textbook: Number of circuit diagrams, simulations
- History textbook: Number of timelines, maps, primary source documents
- Programming textbook: Number of code examples, exercises, projects
- Math textbook: Number of equations, proofs, worked examples
Step 7: Add Getting Started Section
Provide clear instructions for using and customizing the project.
Standard sections:
1. Prerequisites (if any) 2. Clone the Repository 3. Installation (if dependencies needed) 4. Building the Site 5. Local Development 6. Deployment
Example:
## Getting Started
### Clone the Repository
git clone https://github.com/username/repo-name.git cd repo-name
### Install Dependencies
This project uses MkDocs with the Material theme:
pip install mkdocs pip install mkdocs-material
### Build and Serve Locally
Build the site:
mkdocs build
Serve locally for development (with live reload):
mkdocs serve
Open your browser to `http://localhost:8000`
### Deploy to GitHub Pages
mkdocs gh-deploy
This will build the site and push it to the `gh-pages` branch.
### Using the Book
**Navigation:**
- Use the left sidebar to browse chapters
- Click on the search icon to search all content
- Each chapter includes quizzes and practice exercises
**Interactive MicroSims:**
- Found in the "MicroSims" section
- Each simulation runs standalone in your browser
- Adjust parameters with sliders and controls
**Customization:**
- Edit markdown files in `docs/` to modify content
- Modify `mkdocs.yml` to change site structure
- Add your own MicroSims in `docs/sims/`
- Customize theme in `docs/css/extra.css`Step 8: Document Repository Structure
Create an ASCII tree diagram showing the repository structure with explanatory comments.
Use this approach:
- Don't list every single file
- Show representative examples
- Add comments explaining each major directory
- Keep it concise (10-20 lines)
Example:
## Repository Structure
repo-name/ ├── docs/ # MkDocs documentation source │ ├── chapters/ # Chapter content │ │ ├── 01-intro/ │ │ │ ├── index.md # Chapter markdown │ │ │ └── quiz.md # Chapter quiz │ │ └── 02-concepts/ │ ├── sims/ # Interactive p5.js MicroSims │ │ ├── graph-viewer/ │ │ │ ├── main.html # Standalone simulation │ │ │ └── index.md # Documentation │ ├── learning-graph/ # Learning graph data and analysis │ │ ├── learning-graph.csv # Concept dependencies │ │ ├── learning-graph.json # vis-network format │ │ └── quality-metrics.md # Quality analysis │ ├── glossary.md # ISO 11179-compliant definitions │ ├── faq.md # Frequently asked questions │ └── references.md # Curated references ├── skills/ # Claude AI skills (if present) │ └── [skill-name]/ │ ├── SKILL.md # Skill definition │ └── *.py # Supporting scripts ├── mkdocs.yml # MkDocs configuration └── README.md # This file
Step 9: Add Issue Reporting Section
Direct users to the GitHub Issues page:
## Reporting Issues
Found a bug, typo, or have a suggestion for improvement? Please report it:
[GitHub Issues](https://github.com/username/repo-name/issues)
When reporting issues, please include:
- Description of the problem or suggestion
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- Screenshots (if applicable)
- Browser/environment details (for MicroSims)Step 10: Add License Information
Reinforce licensing terms and attribution requirements:
## License
This work is licensed under the [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/).
**You are free to:**
- Share — copy and redistribute the material
- Adapt — remix, transform, and build upon the material
**Under the following terms:**
- **Attribution** — Give appropriate credit with a link to the original
- **NonCommercial** — No commercial use without permission
- **ShareAlike** — Distribute contributions under the same license
See [LICENSE.md](docs/license.md) for full details.Step 11: Add Acknowledgements
Express gratitude to the open source community and key projects:
## Acknowledgements
This project is built on the shoulders of giants in the open source community:
- **[MkDocs](https://www.mkdocs.org/)** - Static site generator optimized for project documentation
- **[Material for MkDocs](https://squidfunk.github.io/mkdocs-material/)** - Beautiful, responsive theme
- **[p5.js](https://p5js.org/)** - Creative coding library from NYU ITP
- **[vis-network](https://visjs.org/)** - Network visualization library for learning graphs
- **[Python](https://www.python.org/)** community - Data processing and analysis tools
- **[Claude AI](https://claude.ai)** by Anthropic - AI-assisted content generation
- **[GitHub Pages](https://pages.github.com/)** - Free hosting for open source projects
Special thanks to the educators and developers who contribute to making educational resources accessible and interactive.Customize based on actual dependencies:
- Add Chart.js if using bubble charts
- Add Mermaid if using diagrams
- Add specific Python libraries if used (pandas, numpy, etc.)
- Add any other key dependencies
Step 12: Add Contact Section
Provide a way for users to reach out:
## Contact
**Dan McCreary**
- LinkedIn: [linkedin.com/in/danmccreary](https://www.linkedin.com/in/danmccreary/)
- GitHub: [@dmccreary](https://github.com/dmccreary)
Questions, suggestions, or collaboration opportunities? Feel free to connect on LinkedIn or open an issue on GitHub.Customize with actual maintainer information:
- Replace with repository owner's name
- Update LinkedIn URL
- Update GitHub username
- Add email if desired (optional)
- Add website/blog if relevant
Step 13: Add Optional Sections
Include these sections if relevant to the project:
Contributing Guidelines:
## Contributing
Contributions are welcome! To contribute:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.Citation Information:
## How to Cite
If you use this textbook in your research or teaching, please cite it as:
[Author Name]. (2024). [Textbook Title]. GitHub. https://github.com/username/repo-name
BibTeX:
@misc{repo-name-2024, author = {[Author Name]}, title = {[Textbook Title]}, year = {2024}, publisher = {GitHub}, url = {https://github.com/username/repo-name} }
Changelog:
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for a detailed history of changes.
**Recent Updates:**
- v1.0.0 (2024-11-11): Initial release with 13 chapters
- v0.9.0 (2024-11-01): Added 12 MicroSims and interactive elements
- v0.5.0 (2024-10-15): Completed learning graph and chapter structureStep 14: Validate and Format
Before finalizing the README:
1. Check all links - Verify GitHub URLs, site URLs, badge URLs 2. Validate markdown - Ensure proper formatting 3. Test locally - Render README on GitHub to check appearance 4. Spell check - Review for typos and grammar 5. Consistency - Ensure terminology matches project docs
Quality checklist:
- [ ] All badges render correctly
- [ ] Repository URL is correct
- [ ] Live site URL works (if applicable)
- [ ] Metrics are accurate and current
- [ ] Code blocks have proper syntax highlighting
- [ ] Links are not broken
- [ ] Table of contents matches sections (if auto-generated)
- [ ] License information is clear
- [ ] Contact information is current
Step 15: Write README.md
Generate the final README.md file in the repository root with all sections in order:
1. Title (H1) with repository name 2. Badges 3. Live site link (if applicable) 4. Overview 5. Site Status and Metrics 6. Getting Started 7. Repository Structure 8. Reporting Issues 9. License 10. Acknowledgements 11. Contact 12. Optional sections (Contributing, Citation, Changelog)
Formatting best practices:
- Use ATX-style headers (
#not underlines) - Include blank lines before lists
- Use code fences with language specifiers
- Keep lines under 120 characters where practical
- Use relative links for internal documentation
- Add table of contents for longer READMEs (>500 lines)
Supporting Scripts
The skill includes Python scripts for automated metrics collection:
`scripts/collect-site-metrics.py`
Scans the repository and generates a metrics report including:
- Markdown file count and word counts
- Chapter and section counts
- MicroSim count
- Glossary, FAQ, quiz statistics
- Image and diagram counts
- Learning graph statistics
Usage:
cd skills/readme-generator/scripts
python collect-site-metrics.py /path/to/repoOutput: JSON object with all metrics
`scripts/validate-readme.py`
Validates README.md for:
- Required sections present
- Working links
- Valid badge URLs
- Proper markdown formatting
Usage:
python validate-readme.py README.mdOutput Files
Required:
1. README.md - Complete README in repository root
Optional:
2. README-backup.md - Backup of previous README (if updating) 3. docs/readme-metrics.json - Metrics data in JSON format
Example Session
User: "Generate a README for this repository"
Claude (using this skill):
1. Checks if README.md exists (found, create backup) 2. Reads mkdocs.yml to extract site info 3. Identifies technologies: MkDocs, Material, p5.js, Python 4. Scans /docs for metrics (chapters, MicroSims, glossary) 5. Runs collect-site-metrics.py to gather statistics 6. Generates badges for all identified technologies 7. Writes comprehensive README.md with all 12 sections 8. Validates links and formatting 9. Reports: "Created README.md with 15 sections, 8 badges, and current site metrics (200 concepts, 13 chapters, 87 files, 12 MicroSims). Previous README backed up to README-backup.md."
Quality Standards
A high-quality README should have:
- All relevant badges displayed correctly
- Accurate, current metrics
- Clear, compelling overview (200-400 words)
- Complete getting started instructions
- Proper attribution and licensing
- Working links (100% functional)
- Professional formatting
- Contact information
References
README Generator Skill
A Claude AI skill that generates comprehensive, best-practice README.md files for GitHub repositories.
Overview
This skill automates the creation of professional README files that follow GitHub best practices. It's particularly optimized for MkDocs-based intelligent textbook projects but can be adapted for any repository type.
Features
- Automatic Badge Generation: Detects technologies and generates appropriate shields.io badges
- Site Metrics Collection: Gathers comprehensive statistics about content, structure, and resources
- GitHub Best Practices: Follows recommended README structure and formatting
- Intelligent Detection: Automatically identifies project type, dependencies, and deployment status
- Validation Tools: Includes scripts to validate README quality and completeness
What Gets Generated
The skill creates a README with these sections:
1. Badges - Technology, platform, status, and license badges 2. Live Site Link - Prominent link to deployed site (if applicable) 3. Overview - Compelling 1-3 paragraph project description 4. Site Status and Metrics - Comprehensive project statistics 5. Getting Started - Clear installation and usage instructions 6. Repository Structure - ASCII tree with explanations 7. Reporting Issues - GitHub issues link and guidelines 8. License - Clear licensing terms and attribution requirements 9. Acknowledgements - Open source community recognition 10. Contact - Maintainer information and communication channels
Usage
Invoke the skill from Claude Code:
Use the readme-generator skill to create a README.md for this repositoryThe skill will: 1. Analyze the repository structure 2. Detect technologies and dependencies 3. Collect site metrics 4. Generate a comprehensive README.md 5. Validate the output
Supporting Tools
Metrics Collection Script
scripts/collect-site-metrics.py - Gathers statistics about:
- Markdown files and word counts
- Chapters and sections
- MicroSims and interactive elements
- Glossary terms, FAQs, quizzes
- Images and diagrams
- Learning graph metrics
Usage:
python scripts/collect-site-metrics.py /path/to/repoOutput: JSON object with all metrics
Validation Script
scripts/validate-readme.py - Validates README for:
- Required sections present
- Valid links and URLs
- Proper markdown formatting
- Badge correctness
- Header structure
Usage:
python scripts/validate-readme.py README.mdOutput: Validation report with score (0-100)
Reference Documentation
Badge Reference
references/badges.md - Comprehensive guide to:
- Common technology badges
- License badges
- Status and custom badges
- Badge formatting best practices
- Color and logo options
File Structure
readme-generator/
├── SKILL.md # Skill definition and workflow
├── README.md # This file
├── scripts/
│ ├── collect-site-metrics.py # Metrics collection
│ └── validate-readme.py # README validation
└── references/
└── badges.md # Badge reference guideCustomization
The skill can be customized for different project types:
- Textbooks: Emphasizes educational metrics (concepts, chapters, quizzes)
- Software Libraries: Focuses on API documentation and examples
- Applications: Highlights features and deployment information
- Documentation Sites: Emphasizes content structure and navigation
Requirements
- Python 3.8+ (for scripts)
- Access to repository files
- Git repository (for repository URL detection)
Examples
See the generated README.md in the parent repository for a real-world example.
License
MIT License - Feel free to use and adapt for your projects.
Contributing
Improvements and suggestions welcome! Areas for enhancement:
- Additional badge templates
- Support for more project types
- Enhanced metrics collection
- Link validation (check if URLs are accessible)
- Screenshot generation
Credits
Part of the Claude Skills collection for building intelligent textbooks.
Badge Reference Guide
This document provides a comprehensive reference for badges commonly used in GitHub README files, particularly for educational and documentation projects.
Badge Services
- Shields.io: https://shields.io/ - Most popular badge service
- Badgen.net: https://badgen.net/ - Alternative badge service
Common Technology Badges
Documentation Tools
MkDocs
[](https://www.mkdocs.org/)Material for MkDocs
[](https://squidfunk.github.io/mkdocs-material/)Sphinx
[](https://www.sphinx-doc.org/)Programming Languages
Python
[](https://www.python.org/)JavaScript
[](https://developer.mozilla.org/en-US/docs/Web/JavaScript)TypeScript
[](https://www.typescriptlang.org/)Go
[](https://golang.org/)Rust
[](https://www.rust-lang.org/)Creative Coding & Visualization
p5.js
[](https://p5js.org/)D3.js
[](https://d3js.org/)Chart.js
[](https://www.chartjs.org/)vis-network
[](https://visjs.org/)Mermaid
[](https://mermaid.js.org/)AI & Machine Learning
Claude AI
[](https://claude.ai)Claude Code
[](https://claude.ai/code)Claude Skills
[](https://github.com/dmccreary/claude-skills)OpenAI
[](https://openai.com/)TensorFlow
[](https://www.tensorflow.org/)PyTorch
[](https://pytorch.org/)Hosting & Deployment
GitHub Pages
[](SITE_URL)Netlify
[](https://www.netlify.com/)Vercel
[](https://vercel.com/)Repository & Version Control
GitHub
[](REPO_URL)GitHub Stars
[](https://github.com/OWNER/REPO)GitHub Forks
[](https://github.com/OWNER/REPO)GitHub Issues
[](https://github.com/OWNER/REPO/issues)Last Commit
[](https://github.com/OWNER/REPO)License Badges
Creative Commons
CC BY 4.0
[](https://creativecommons.org/licenses/by/4.0/)CC BY-SA 4.0
[](https://creativecommons.org/licenses/by-sa/4.0/)CC BY-NC 4.0
[](https://creativecommons.org/licenses/by-nc/4.0/)CC BY-NC-SA 4.0 (Default for this project)
[](https://creativecommons.org/licenses/by-nc-sa/4.0/)CC BY-ND 4.0
[](https://creativecommons.org/licenses/by-nd/4.0/)CC BY-NC-ND 4.0
[](https://creativecommons.org/licenses/by-nc-nd/4.0/)CC0 (Public Domain)
[](https://creativecommons.org/publicdomain/zero/1.0/)Open Source Licenses
MIT
[](https://opensource.org/licenses/MIT)Apache 2.0
[](https://opensource.org/licenses/Apache-2.0)GPL-3.0
[](https://www.gnu.org/licenses/gpl-3.0)BSD 3-Clause
[](https://opensource.org/licenses/BSD-3-Clause)ISC
[](https://opensource.org/licenses/ISC)Status Badges
Build Status
[](BUILD_URL)Coverage
[](COVERAGE_URL)Version
[](RELEASES_URL)Status
[](PROJECT_URL)Custom Badges
You can create custom badges using the Shields.io format:
https://img.shields.io/badge/{LABEL}-{MESSAGE}-{COLOR}Parameters:
LABEL: Left side textMESSAGE: Right side textCOLOR: Badge color (name or hex without #)
Optional parameters:
?logo=LOGO_NAME: Add icon from Simple Icons&logoColor=COLOR: Icon color&style=STYLE: Badge style (flat, flat-square, plastic, for-the-badge, social)
Example custom badge:
[](URL)Badge Colors
Common color names supported by Shields.io:
brightgreengreenyellowgreenyelloworangeredbluelightgreysuccessimportantcriticalinformationalinactive
Hex colors: Use without the # symbol (e.g., FF6B6B)
Logo Names
Shields.io uses logos from Simple Icons. Common logo names:
pythonjavascripttypescriptgithubgitlabvisualstudiocodenpmdockerkubernetesreactvue-dot-jsangularnode-dot-jspostgresqlmongodbredis
Best Practices
1. Order badges logically: Technology → Platform → Status → License 2. Limit badge count: 5-10 badges maximum for readability 3. Keep labels concise: Short, clear text 4. Use consistent style: Stick to one badge style (flat recommended) 5. Ensure accessibility: Use sufficient color contrast 6. Link badges appropriately: Link to relevant documentation or homepage 7. Update dynamically: Use dynamic badges (version, build status) when possible
Example Badge Section
[](https://www.mkdocs.org/)
[](https://squidfunk.github.io/mkdocs-material/)
[](https://username.github.io/repo-name)
[](https://github.com/username/repo-name)
[](https://claude.ai/code)
[](https://creativecommons.org/licenses/by-nc-sa/4.0/)Resources
- Shields.io: https://shields.io/
- Simple Icons: https://simpleicons.org/
- Badge Generator: https://shields.io/badges
- Markdown Guide: https://www.markdownguide.org/
#!/usr/bin/env python3
"""
Site Metrics Collection Script
Scans a repository and collects metrics for README generation including:
- Markdown file counts and word counts
- Chapter and section counts
- MicroSim counts
- Glossary, FAQ, quiz statistics
- Image and diagram counts
- Learning graph statistics
Usage:
python collect-site-metrics.py [repo_path]
Output:
JSON object with all collected metrics
"""
import os
import json
import re
import sys
from pathlib import Path
from typing import Dict, List, Tuple
def count_words_in_markdown(file_path: str) -> int:
"""Count words in a markdown file, excluding code blocks and front matter."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Remove YAML front matter
content = re.sub(r'^---\s*\n.*?\n---\s*\n', '', content, flags=re.DOTALL)
# Remove code blocks
content = re.sub(r'```.*?```', '', content, flags=re.DOTALL)
content = re.sub(r'`[^`]+`', '', content)
# Remove HTML comments
content = re.sub(r'<!--.*?-->', '', content, flags=re.DOTALL)
# Remove markdown links but keep text
content = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', content)
# Remove images
content = re.sub(r'!\[([^\]]*)\]\([^\)]+\)', '', content)
# Count words
words = content.split()
return len(words)
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return 0
def count_list_items(file_path: str) -> int:
"""Count markdown list items in a file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Count unordered lists (-, *, +)
unordered = len(re.findall(r'^\s*[-*+]\s+', content, flags=re.MULTILINE))
# Count ordered lists (1., 2., etc.)
ordered = len(re.findall(r'^\s*\d+\.\s+', content, flags=re.MULTILINE))
return unordered + ordered
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return 0
def count_tables(file_path: str) -> int:
"""Count markdown tables in a file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Count table header separators (e.g., |---|---|)
tables = len(re.findall(r'^\|?\s*[-:]+\s*\|', content, flags=re.MULTILINE))
return tables
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return 0
def count_code_blocks(file_path: str) -> int:
"""Count code blocks in a markdown file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Count fenced code blocks
code_blocks = len(re.findall(r'```', content)) // 2
return code_blocks
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return 0
def count_equations(file_path: str) -> int:
"""Count LaTeX equations in a markdown file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Count display equations ($$...$$)
display = len(re.findall(r'\$\$.*?\$\$', content, flags=re.DOTALL))
# Count inline equations ($...$)
inline = len(re.findall(r'(?<!\$)\$(?!\$)[^$]+\$(?!\$)', content))
return display + inline
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return 0
def count_quiz_questions(file_path: str) -> int:
"""Count quiz questions in a quiz markdown file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Count question numbers (assumes format like "1.", "2.", etc. at start of line)
questions = len(re.findall(r'^\d+\.\s+', content, flags=re.MULTILINE))
# Alternative: count headers that start with numbers
if questions == 0:
questions = len(re.findall(r'^#+\s+\d+[\.)]?\s+', content, flags=re.MULTILINE))
return questions
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return 0
def count_glossary_terms(file_path: str) -> int:
"""Count glossary terms (assumes level 4 headers)."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Count level 4 headers (####)
terms = len(re.findall(r'^####\s+', content, flags=re.MULTILINE))
return terms
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return 0
def count_faq_questions(file_path: str) -> int:
"""Count FAQ questions."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Count headers that end with question marks
questions = len(re.findall(r'^#+\s+.*\?', content, flags=re.MULTILINE))
# Alternative: count specific FAQ patterns
if questions == 0:
questions = len(re.findall(r'^#+\s+', content, flags=re.MULTILINE))
return questions
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return 0
def count_references(file_path: str) -> int:
"""Count references in references file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Count numbered references or list items
refs = len(re.findall(r'^\d+\.\s+', content, flags=re.MULTILINE))
if refs == 0:
# Count list items
refs = len(re.findall(r'^\s*[-*+]\s+', content, flags=re.MULTILINE))
return refs
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return 0
def get_learning_graph_metrics(repo_path: Path) -> Dict:
"""Extract learning graph metrics from learning-graph directory."""
lg_path = repo_path / 'docs' / 'learning-graph'
metrics = {
'concepts': 0,
'quality_score': None
}
# Try to read concept list
concept_file = lg_path / 'concept-list.md'
if not concept_file.exists():
concept_file = lg_path / 'list-concepts.md'
if concept_file.exists():
try:
with open(concept_file, 'r', encoding='utf-8') as f:
content = f.read()
# Count numbered list items
concepts = re.findall(r'^\d+\.\s+', content, flags=re.MULTILINE)
metrics['concepts'] = len(concepts)
except Exception as e:
print(f"Error reading concept list: {e}", file=sys.stderr)
# Try to read quality metrics
quality_file = lg_path / 'quality-metrics.md'
if quality_file.exists():
try:
with open(quality_file, 'r', encoding='utf-8') as f:
content = f.read()
# Look for quality score
match = re.search(r'quality score:?\s*(\d+)', content, re.IGNORECASE)
if match:
metrics['quality_score'] = int(match.group(1))
except Exception as e:
print(f"Error reading quality metrics: {e}", file=sys.stderr)
return metrics
def collect_metrics(repo_path: str = '.') -> Dict:
"""Collect all site metrics from repository."""
repo = Path(repo_path).resolve()
docs_path = repo / 'docs'
metrics = {
'repository': {
'path': str(repo),
'name': repo.name
},
'content': {
'markdown_files': 0,
'total_words': 0,
'chapters': 0,
'list_items': 0,
'tables': 0,
'code_blocks': 0,
'equations': 0
},
'learning_graph': {
'concepts': 0,
'quality_score': None
},
'interactive': {
'microsims': 0,
'quizzes': 0,
'quiz_questions': 0
},
'resources': {
'glossary_terms': 0,
'faq_questions': 0,
'references': 0
},
'media': {
'images': 0,
'png': 0,
'jpg': 0,
'svg': 0
}
}
if not docs_path.exists():
print(f"Warning: docs directory not found at {docs_path}", file=sys.stderr)
return metrics
# Count markdown files and aggregate statistics
for md_file in docs_path.rglob('*.md'):
metrics['content']['markdown_files'] += 1
metrics['content']['total_words'] += count_words_in_markdown(str(md_file))
metrics['content']['list_items'] += count_list_items(str(md_file))
metrics['content']['tables'] += count_tables(str(md_file))
metrics['content']['code_blocks'] += count_code_blocks(str(md_file))
metrics['content']['equations'] += count_equations(str(md_file))
# Count chapters
chapters_path = docs_path / 'chapters'
if chapters_path.exists():
metrics['content']['chapters'] = len([d for d in chapters_path.iterdir() if d.is_dir()])
# Count MicroSims
sims_path = docs_path / 'sims'
if sims_path.exists():
metrics['interactive']['microsims'] = len([d for d in sims_path.iterdir()
if d.is_dir() and (d / 'index.md').exists()])
# Count quizzes and questions
for quiz_file in docs_path.rglob('quiz.md'):
metrics['interactive']['quizzes'] += 1
metrics['interactive']['quiz_questions'] += count_quiz_questions(str(quiz_file))
# Count glossary terms
glossary_file = docs_path / 'glossary.md'
if glossary_file.exists():
metrics['resources']['glossary_terms'] = count_glossary_terms(str(glossary_file))
# Count FAQ questions
faq_file = docs_path / 'faq.md'
if faq_file.exists():
metrics['resources']['faq_questions'] = count_faq_questions(str(faq_file))
# Count references
ref_file = docs_path / 'references.md'
if ref_file.exists():
metrics['resources']['references'] = count_references(str(ref_file))
# Count images
for ext in ['png', 'jpg', 'jpeg', 'svg', 'gif']:
image_files = list(docs_path.rglob(f'*.{ext}'))
count = len(image_files)
metrics['media']['images'] += count
if ext in ['jpg', 'jpeg']:
metrics['media']['jpg'] += count
elif ext in metrics['media']:
metrics['media'][ext] = count
# Get learning graph metrics
lg_metrics = get_learning_graph_metrics(repo)
metrics['learning_graph'] = lg_metrics
return metrics
def format_metrics_table(metrics: Dict) -> str:
"""Format metrics as a markdown table."""
table = "| Metric | Count |\n"
table += "|--------|-------|\n"
if metrics['learning_graph']['concepts'] > 0:
table += f"| Concepts in Learning Graph | {metrics['learning_graph']['concepts']} |\n"
if metrics['content']['chapters'] > 0:
table += f"| Chapters | {metrics['content']['chapters']} |\n"
table += f"| Markdown Files | {metrics['content']['markdown_files']} |\n"
table += f"| Total Words | {metrics['content']['total_words']:,} |\n"
if metrics['interactive']['microsims'] > 0:
table += f"| MicroSims | {metrics['interactive']['microsims']} |\n"
if metrics['resources']['glossary_terms'] > 0:
table += f"| Glossary Terms | {metrics['resources']['glossary_terms']} |\n"
if metrics['resources']['faq_questions'] > 0:
table += f"| FAQ Questions | {metrics['resources']['faq_questions']} |\n"
if metrics['interactive']['quiz_questions'] > 0:
table += f"| Quiz Questions | {metrics['interactive']['quiz_questions']} |\n"
if metrics['content']['equations'] > 0:
table += f"| Equations | {metrics['content']['equations']} |\n"
if metrics['media']['images'] > 0:
table += f"| Images | {metrics['media']['images']} |\n"
if metrics['resources']['references'] > 0:
table += f"| References | {metrics['resources']['references']} |\n"
return table
def main():
"""Main entry point."""
repo_path = sys.argv[1] if len(sys.argv) > 1 else '.'
print(f"Collecting metrics from: {repo_path}", file=sys.stderr)
metrics = collect_metrics(repo_path)
# Output JSON
print(json.dumps(metrics, indent=2))
# Also print formatted table to stderr for reference
print("\n--- Formatted Table ---", file=sys.stderr)
print(format_metrics_table(metrics), file=sys.stderr)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
README Validation Script
Validates README.md files for:
- Required sections present
- Working links (basic check)
- Valid badge URLs
- Proper markdown formatting
- Common issues
Usage:
python validate-readme.py [path/to/README.md]
Output:
Validation report with score and recommendations
"""
import re
import sys
from pathlib import Path
from typing import List, Tuple, Dict
from urllib.parse import urlparse
def check_required_sections(content: str) -> Tuple[List[str], List[str]]:
"""Check for required README sections."""
required = [
'overview',
'getting started',
'license',
'contact'
]
recommended = [
'installation',
'usage',
'contributing',
'acknowledgements',
'issues'
]
found_required = []
found_recommended = []
missing_required = []
missing_recommended = []
content_lower = content.lower()
for section in required:
if section in content_lower or section.replace(' ', '-') in content_lower:
found_required.append(section)
else:
missing_required.append(section)
for section in recommended:
if section in content_lower or section.replace(' ', '-') in content_lower:
found_recommended.append(section)
else:
missing_recommended.append(section)
return (found_required, missing_required, found_recommended, missing_recommended)
def extract_links(content: str) -> List[Tuple[str, str]]:
"""Extract all markdown links from content."""
# Match [text](url) format
links = re.findall(r'\[([^\]]+)\]\(([^\)]+)\)', content)
return links
def validate_url_format(url: str) -> bool:
"""Validate URL format (basic check)."""
try:
result = urlparse(url)
# Check if it's a web URL or relative path
return bool(result.scheme in ['http', 'https'] or url.startswith('/') or url.startswith('.'))
except:
return False
def extract_badges(content: str) -> List[str]:
"""Extract badge URLs from content."""
badges = []
# Find badge patterns like [](link-url)
badge_pattern = r'\[!\[([^\]]*)\]\(([^\)]+)\)\]\(([^\)]+)\)'
matches = re.findall(badge_pattern, content)
for match in matches:
badges.append(match[1]) # badge URL
return badges
def check_markdown_formatting(content: str) -> List[str]:
"""Check for common markdown formatting issues."""
issues = []
lines = content.split('\n')
# Check for lists without preceding blank line
for i, line in enumerate(lines[1:], start=1):
if re.match(r'^\s*[-*+]\s+', line) or re.match(r'^\s*\d+\.\s+', line):
if i > 0 and lines[i-1].strip() and not re.match(r'^#+\s+', lines[i-1]):
# Previous line is not blank and not a header
if not (re.match(r'^\s*[-*+]\s+', lines[i-1]) or re.match(r'^\s*\d+\.\s+', lines[i-1])):
issues.append(f"Line {i+1}: List item should have blank line before it")
# Check for code blocks without language specification
code_blocks = re.findall(r'```(\w*)\n', content)
unnamed_blocks = sum(1 for lang in code_blocks if not lang)
if unnamed_blocks > 0:
issues.append(f"Found {unnamed_blocks} code block(s) without language specification")
# Check for very long lines (> 120 chars, excluding URLs)
for i, line in enumerate(lines, start=1):
if len(line) > 120 and 'http' not in line:
issues.append(f"Line {i}: Very long line ({len(line)} chars) - consider breaking")
return issues
def check_header_structure(content: str) -> List[str]:
"""Check header structure and hierarchy."""
issues = []
lines = content.split('\n')
h1_count = 0
prev_level = 0
for i, line in enumerate(lines, start=1):
match = re.match(r'^(#+)\s+', line)
if match:
level = len(match.group(1))
if level == 1:
h1_count += 1
# Check for skipped levels
if prev_level > 0 and level > prev_level + 1:
issues.append(f"Line {i}: Skipped header level (#{prev_level} to #{level})")
prev_level = level
if h1_count == 0:
issues.append("No H1 header found (should have repository name)")
elif h1_count > 1:
issues.append(f"Multiple H1 headers found ({h1_count}) - should have only one")
return issues
def validate_readme(file_path: str) -> Dict:
"""Validate a README.md file and return detailed report."""
path = Path(file_path)
if not path.exists():
return {
'valid': False,
'error': f"File not found: {file_path}",
'score': 0
}
try:
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
except Exception as e:
return {
'valid': False,
'error': f"Error reading file: {e}",
'score': 0
}
report = {
'valid': True,
'file': str(path),
'size': len(content),
'sections': {},
'links': {},
'badges': {},
'formatting': {},
'headers': {},
'recommendations': [],
'score': 0
}
# Check sections
found_req, missing_req, found_rec, missing_rec = check_required_sections(content)
report['sections'] = {
'required_found': found_req,
'required_missing': missing_req,
'recommended_found': found_rec,
'recommended_missing': missing_rec
}
# Extract and validate links
links = extract_links(content)
invalid_links = [url for text, url in links if not validate_url_format(url)]
report['links'] = {
'total': len(links),
'invalid': invalid_links,
'invalid_count': len(invalid_links)
}
# Extract badges
badges = extract_badges(content)
report['badges'] = {
'count': len(badges),
'urls': badges
}
# Check markdown formatting
formatting_issues = check_markdown_formatting(content)
report['formatting'] = {
'issues': formatting_issues,
'issue_count': len(formatting_issues)
}
# Check header structure
header_issues = check_header_structure(content)
report['headers'] = {
'issues': header_issues,
'issue_count': len(header_issues)
}
# Generate recommendations
if missing_req:
report['recommendations'].append(f"Add missing required sections: {', '.join(missing_req)}")
if missing_rec:
report['recommendations'].append(f"Consider adding recommended sections: {', '.join(missing_rec)}")
if invalid_links:
report['recommendations'].append(f"Fix {len(invalid_links)} invalid link(s)")
if len(badges) == 0:
report['recommendations'].append("Add badges for technologies used")
if formatting_issues:
report['recommendations'].append(f"Fix {len(formatting_issues)} formatting issue(s)")
if header_issues:
report['recommendations'].append(f"Fix {len(header_issues)} header structure issue(s)")
# Calculate score (0-100)
score = 100
# Deduct for missing required sections (10 points each)
score -= len(missing_req) * 10
# Deduct for missing recommended sections (5 points each, max 20)
score -= min(len(missing_rec) * 5, 20)
# Deduct for invalid links (5 points each, max 15)
score -= min(len(invalid_links) * 5, 15)
# Deduct for no badges (10 points)
if len(badges) == 0:
score -= 10
# Deduct for formatting issues (2 points each, max 15)
score -= min(len(formatting_issues) * 2, 15)
# Deduct for header issues (3 points each, max 10)
score -= min(len(header_issues) * 3, 10)
report['score'] = max(0, score)
return report
def format_report(report: Dict) -> str:
"""Format validation report as readable text."""
if not report.get('valid', False):
return f"ERROR: {report.get('error', 'Unknown error')}"
output = []
output.append("=" * 60)
output.append("README VALIDATION REPORT")
output.append("=" * 60)
output.append(f"\nFile: {report['file']}")
output.append(f"Size: {report['size']:,} bytes")
output.append(f"\nOVERALL SCORE: {report['score']}/100")
# Score interpretation
if report['score'] >= 90:
output.append("Status: EXCELLENT ✓")
elif report['score'] >= 75:
output.append("Status: GOOD ✓")
elif report['score'] >= 60:
output.append("Status: ADEQUATE")
else:
output.append("Status: NEEDS IMPROVEMENT")
# Sections
output.append("\n" + "-" * 60)
output.append("SECTIONS")
output.append("-" * 60)
sections = report['sections']
output.append(f"Required sections: {len(sections['required_found'])}/{len(sections['required_found']) + len(sections['required_missing'])}")
if sections['required_missing']:
output.append(f" Missing: {', '.join(sections['required_missing'])}")
output.append(f"Recommended sections: {len(sections['recommended_found'])}/{len(sections['recommended_found']) + len(sections['recommended_missing'])}")
if sections['recommended_missing']:
output.append(f" Missing: {', '.join(sections['recommended_missing'])}")
# Links
output.append("\n" + "-" * 60)
output.append("LINKS")
output.append("-" * 60)
output.append(f"Total links: {report['links']['total']}")
output.append(f"Invalid links: {report['links']['invalid_count']}")
if report['links']['invalid']:
for link in report['links']['invalid'][:5]: # Show first 5
output.append(f" - {link}")
if len(report['links']['invalid']) > 5:
output.append(f" ... and {len(report['links']['invalid']) - 5} more")
# Badges
output.append("\n" + "-" * 60)
output.append("BADGES")
output.append("-" * 60)
output.append(f"Badge count: {report['badges']['count']}")
# Formatting
output.append("\n" + "-" * 60)
output.append("FORMATTING")
output.append("-" * 60)
output.append(f"Issues found: {report['formatting']['issue_count']}")
if report['formatting']['issues']:
for issue in report['formatting']['issues'][:5]: # Show first 5
output.append(f" - {issue}")
if len(report['formatting']['issues']) > 5:
output.append(f" ... and {len(report['formatting']['issues']) - 5} more")
# Headers
output.append("\n" + "-" * 60)
output.append("HEADER STRUCTURE")
output.append("-" * 60)
output.append(f"Issues found: {report['headers']['issue_count']}")
if report['headers']['issues']:
for issue in report['headers']['issues']:
output.append(f" - {issue}")
# Recommendations
if report['recommendations']:
output.append("\n" + "-" * 60)
output.append("RECOMMENDATIONS")
output.append("-" * 60)
for i, rec in enumerate(report['recommendations'], start=1):
output.append(f"{i}. {rec}")
output.append("\n" + "=" * 60)
return "\n".join(output)
def main():
"""Main entry point."""
if len(sys.argv) < 2:
print("Usage: python validate-readme.py <path/to/README.md>", file=sys.stderr)
sys.exit(1)
readme_path = sys.argv[1]
report = validate_readme(readme_path)
print(format_report(report))
# Exit with error code if score is below 60
if report['score'] < 60:
sys.exit(1)
if __name__ == '__main__':
main()