
Codebase Onboarding
- 99 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
Codebase Onboarding is a Claude skill that analyzes a codebase and generates onboarding documentation including architecture overviews, setup guides, task runbooks, and debugging guides.
About
Codebase Onboarding analyzes a repository and generates onboarding documentation including architecture overviews with diagrams, annotated key-file maps, local setup guides, task runbooks, and debugging guides. A developer uses it when bringing on new team members, open-sourcing a project, or documenting after a major refactor. It tailors depth to junior, senior, or contractor audiences and outputs Markdown, Notion, or Confluence.
- Generates architecture overviews, key-file maps, setup guides, task runbooks and debugging guides from a codebase
- Audience-aware output tuned for junior, senior, or contractor developers
- Exports to Markdown, Notion, and Confluence
Codebase Onboarding by the numbers
- 99 all-time installs (skills.sh)
- Ranked #649 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
codebase-onboarding capabilities & compatibility
- Capabilities
- documentation · architecture mapping · setup guide
- Works with
- notion · confluence · github
- Use cases
- documentation
- Pricing
- Free
What codebase-onboarding says it does
Analyze any codebase and generate production-quality onboarding documentation tailored to the audience.
Identify the 20 most important files and explain why they matter
Supports Markdown, Notion, and Confluence output formats.
npx skills add https://github.com/borghei/claude-skills --skill codebase-onboardingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 99 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Generate audience-aware onboarding docs (architecture, setup, runbooks, debugging) from an existing codebase.
Who is it for?
Onboarding new engineers or documenting a service after a refactor
Skip if: Writing application code or fixing bugs
When should I use this skill?
Onboarding a new team member, open-sourcing a project, or documenting after a major refactor
What you get
Produces production-quality onboarding docs tailored to junior, senior, or contractor readers.
- architecture overview
- key-file map
- local setup guide
By the numbers
- Identifies the 20 most important files
- Supports 3 output formats: Markdown, Notion, Confluence
Files
Codebase Onboarding
Tier: POWERFUL Category: Engineering / Developer Experience Maintainer: Claude Skills Team
Overview
Analyze any codebase and generate production-quality onboarding documentation tailored to the audience. Produces architecture overviews with system diagrams, annotated key file maps, step-by-step local setup guides, common developer task runbooks, debugging guides with real error solutions, and contribution guidelines. Supports Markdown, Notion, and Confluence output formats.
Keywords
codebase onboarding, developer experience, documentation, architecture overview, setup guide, debugging guide, contribution guidelines, code walkthrough, new hire onboarding
Core Capabilities
1. Architecture Analysis
- Tech stack identification from manifests and lockfiles
- System boundary mapping (services, databases, external APIs)
- Data flow diagramming with Mermaid
- Dependency graph visualization
- Module ownership mapping
2. Key File Annotation
- Identify the 20 most important files and explain why they matter
- Mark entry points, configuration hubs, and shared utilities
- Flag files that are dangerous to modify without coordination
- Link files to the architectural concepts they implement
3. Setup Guide Generation
- Prerequisites with exact versions and install commands
- Step-by-step from
git cloneto running tests - Environment variable documentation with example values
- Infrastructure setup (Docker, databases, caches)
- Verification checklist (what "success" looks like)
4. Task Runbooks
- How to add a new API endpoint (full lifecycle)
- How to run and write tests
- How to create and apply database migrations
- How to deploy to staging and production
- How to add a new dependency safely
5. Debugging Guide
- Common errors with exact error messages and solutions
- Log locations by environment
- Useful SQL/CLI diagnostic queries
- How to reproduce production issues locally
When to Use
- Onboarding a new team member (junior, senior, or contractor)
- After a major refactor that made existing docs stale
- Before open-sourcing a project
- Creating a team wiki page for a service you own
- Self-documenting before a long vacation or team transition
- Preparing for a compliance audit that requires documentation
Codebase Analysis Process
Phase 1: Gather Facts
Run these analysis commands to collect data before generating any documentation.
# 1. Package manifest and scripts
cat package.json 2>/dev/null | python3 -c "
import json, sys
pkg = json.load(sys.stdin)
print(f\"Name: {pkg.get('name')}\")
print(f\"Scripts: {list(pkg.get('scripts', {}).keys())}\")
print(f\"Deps: {len(pkg.get('dependencies', {}))}\")
print(f\"DevDeps: {len(pkg.get('devDependencies', {}))}\")
" || echo "No package.json found"
# 2. Directory structure (top 3 levels, excluding noise)
find . -maxdepth 3 \
-not -path '*/node_modules/*' \
-not -path '*/.git/*' \
-not -path '*/.next/*' \
-not -path '*/__pycache__/*' \
-not -path '*/dist/*' \
-not -path '*/.venv/*' | \
sort | head -80
# 3. Largest source files (complexity indicators)
find src/ app/ lib/ -name "*.ts" -o -name "*.tsx" -o -name "*.py" -o -name "*.go" 2>/dev/null | \
xargs wc -l 2>/dev/null | sort -rn | head -20
# 4. API routes
find . -name "route.ts" -path "*/api/*" 2>/dev/null | sort # Next.js
grep -rn "router\.\(get\|post\|put\|delete\)" src/ --include="*.ts" 2>/dev/null | head -30 # Express
# 5. Database schema location
find . -name "schema.ts" -o -name "schema.prisma" -o -name "models.py" 2>/dev/null | head -10
# 6. Test infrastructure
find . -name "*.test.ts" -o -name "*.spec.ts" -o -name "test_*.py" 2>/dev/null | wc -l
# 7. Recent significant changes (last 90 days)
git log --oneline --since="90 days ago" | grep -iE "feat|refactor|breaking|migrate" | head -20
# 8. CI/CD configuration
ls .github/workflows/ 2>/dev/null || ls .gitlab-ci.yml 2>/dev/null || echo "No CI config found"
# 9. Environment variables referenced in code
grep -rh "process\.env\.\|os\.environ\.\|os\.getenv" src/ app/ lib/ --include="*.ts" --include="*.py" 2>/dev/null | \
grep -oE "[A-Z_]{3,}" | sort -u | head -30Phase 2: Identify Architecture Patterns
Based on gathered facts, classify the project:
| Signal | Architecture Pattern |
|---|---|
app/ directory with page.tsx | Next.js App Router (file-based routing) |
src/routes/ with Express imports | Express REST API |
| FastAPI decorators | Python REST/async API |
docker-compose.yml with multiple services | Microservices |
Single main.go with handlers | Go monolith |
packages/ or apps/ at root | Monorepo |
| Prisma/Drizzle schema file | ORM-managed database |
k8s/ or terraform/ directories | Infrastructure as Code |
Phase 3: Generate Documentation
Architecture Overview Template
## Architecture
### System Diagram
[Use the ASCII diagram pattern below — it renders in any markdown viewer]
Browser / Mobile App │ v [API Gateway / Load Balancer] │ ├──> [Web Server: Next.js / Express / FastAPI] │ ├── Authentication (JWT / OAuth) │ ├── Business Logic │ └── Background Jobs │ ├──> [Primary Database: PostgreSQL] │ └── Migrations managed by [ORM] │ ├──> [Cache: Redis] │ └── Sessions, rate limits, job queue │ └──> [Object Storage: S3 / R2] └── File uploads, static assets
External Integrations: ├── [Stripe] — Payments ├── [SendGrid / Resend] — Transactional email └── [Sentry] — Error tracking
### Tech Stack
| Layer | Technology | Purpose |
|-------|-----------|---------|
| Frontend | [framework] | [why chosen] |
| API | [framework] | [routing, middleware] |
| Database | [database + ORM] | [data storage, migrations] |
| Auth | [provider] | [authentication method] |
| Queue | [system] | [background processing] |
| Deployment | [platform] | [hosting, CI/CD] |
| Monitoring | [tool] | [errors, performance] |Key File Map Template
## Key Files
Priority files — read these first to understand the system:
| Priority | Path | What It Does | When to Read |
|----------|------|-------------|-------------|
| 1 | `src/db/schema.ts` | Database schema — single source of truth for data model | First day |
| 2 | `src/lib/auth.ts` | Authentication configuration and session handling | First day |
| 3 | `app/api/` | All API route handlers | First week |
| 4 | `middleware.ts` | Request middleware (auth, logging, rate limiting) | First week |
| 5 | `.env.example` | All environment variables with descriptions | Setup day |
Dangerous files — coordinate before modifying:
| Path | Risk | Coordination Required |
|------|------|----------------------|
| `src/db/schema.ts` | Schema changes affect all services | PR review from DB owner |
| `middleware.ts` | Affects every request | Load test after changes |
| `lib/stripe.ts` | Payment processing | Finance team notification |Local Setup Guide Template
## Local Setup (Target: under 10 minutes)
### Prerequisites
| Tool | Required Version | Install Command |
|------|-----------------|----------------|
| Node.js | 20+ | `nvm install 20` |
| pnpm | 9+ | `corepack enable && corepack prepare pnpm@latest` |
| Docker | 24+ | [docker.com/get-docker](https://docker.com/get-docker) |
| PostgreSQL | 16+ | Via Docker (see step 3) |
### Steps
**Step 1: Clone and install** (2 min)git clone [repo-url] cd [repo-name] pnpm install
**Step 2: Configure environment** (1 min)cp .env.example .env
Edit .env — minimum required values:
DATABASE_URL=postgresql://dev:dev@localhost:5432/myapp
APP_SECRET=$(openssl rand -base64 32)
**Step 3: Start infrastructure** (1 min)docker compose up -d
Starts: PostgreSQL, Redis
Verify: docker compose ps (all should show "running")
**Step 4: Set up database** (1 min)pnpm db:migrate pnpm db:seed # Optional: loads test data
**Step 5: Start dev server** (30 sec)pnpm dev
App runs at http://localhost:3000
### Verify Everything Works
- [ ] http://localhost:3000 loads the app
- [ ] http://localhost:3000/api/health returns `{"status": "ok"}`
- [ ] `pnpm test` passes with no failures
- [ ] You can log in with the seeded test user (see .env.example for credentials)Debugging Guide Template
## Debugging Guide
### Common Errors and Fixes
**`Error: connect ECONNREFUSED 127.0.0.1:5432`**Cause: PostgreSQL is not running Fix: docker compose up -d postgres Verify: docker compose ps postgres (should show "running")
**`Error: relation "users" does not exist`**Cause: Migrations have not been applied Fix: pnpm db:migrate Verify: pnpm db:migrate status (should show all applied)
**`TypeError: Cannot read property 'id' of null`**Cause: Session is null — usually a missing or expired auth token Fix: Check that the request includes a valid Authorization header Debug: Add console.log(session) in the route handler to inspect
### Where to Find Logs
| Environment | Location | Command |
|-------------|----------|---------|
| Local dev | Terminal running `pnpm dev` | Scroll up in terminal |
| Local DB | Docker logs | `docker compose logs postgres` |
| Staging | [Platform dashboard] | [Link to staging logs] |
| Production | [Platform dashboard] | [Link to production logs] |
### Useful Diagnostic Commands
Check database connectivity
psql $DATABASE_URL -c "SELECT 1"
View active database connections
psql $DATABASE_URL -c "SELECT count(*), state FROM pg_stat_activity GROUP BY state"
Check if a specific migration was applied
pnpm db:migrate status
Clear local caches
redis-cli FLUSHDB
Verify environment variables are loaded
node -e "console.log(process.env.DATABASE_URL ? 'Set' : 'MISSING')"
Audience-Specific Customization
Junior Developer Additions
- Explain acronyms on first use (ORM, RLS, JWT, etc.)
- Add "read this first" ordered reading list of 5 files
- Include screenshots for UI-related flows
- Link to external learning resources for key technologies
- Add a "glossary" section for domain-specific terms
Senior Engineer Additions
- Link to Architecture Decision Records (ADRs)
- Include performance benchmark baselines
- Document known technical debt and planned improvements
- Provide security model overview with threat boundaries
- Share scaling limits and planned capacity changes
Contractor Additions
- Define scope boundaries ("only modify files in src/features/your-feature/")
- Specify communication channels and response expectations
- Document access request process for required systems
- Include time logging requirements and reporting cadence
- List prohibited actions (direct push to main, schema changes, etc.)
Quality Verification
After generating onboarding docs, validate with this checklist:
1. Fresh machine test — can a new developer follow the setup guide verbatim on a clean machine? 2. 10-minute target — does local setup complete in under 10 minutes? 3. Error coverage — do the documented errors match what developers actually encounter? 4. Link validity — do all links to external resources and internal docs resolve? 5. Currency — are all version numbers, commands, and screenshots current?
Common Pitfalls
- Docs written once, never updated — add doc update checks to the PR template
- Missing "why" for architecture decisions — document why, not just what
- Untested setup instructions — test the docs on a fresh machine quarterly
- No debugging section — the debugging guide is the most valuable section for new hires
- Too much detail for the wrong audience — contractors need task-specific docs, not deep architecture
- Stale screenshots — UI screenshots go stale fast; link to running instances when possible
Best Practices
1. Keep setup under 10 minutes — if it takes longer, fix the setup process, not the docs 2. Test the docs — have a new hire follow them literally and fix every gap they hit 3. Link, do not repeat — reference ADRs, issues, and external docs instead of duplicating 4. Update docs in the same PR as code changes — documentation drift is the number one failure mode 5. Version-specific notes — call out what changed in recent versions so returning developers catch up 6. Runbooks over theory — "run this command" is more useful than "the system uses Redis for caching" 7. Key file map is mandatory — every project should have an annotated list of the 10-20 most important files
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Generated setup guide fails on fresh machine | Implicit dependencies not captured during analysis | Re-run Phase 1 gather commands on a clean environment; add every missing tool to the prerequisites table |
| Architecture diagram does not match actual data flow | Analysis relied on stale code paths or unused modules | Cross-reference with git log --since="90 days" to find active code paths; interview a senior engineer to validate |
| Key file map is too large (30+ files) | No prioritization applied; every file treated equally | Limit to 15-20 files maximum; rank by edit frequency (`git log --format='%H' -- <file> |
| Onboarding doc goes stale within weeks | No process ties doc updates to code changes | Add a "docs" checkbox to the PR template; schedule quarterly freshness reviews |
| Audience sections feel generic | Same content served to juniors, seniors, and contractors | Generate separate docs per audience or use collapsible sections; run the audience customization checklist from this skill |
| Debugging guide missing real errors | Errors were invented rather than collected from logs | Mine actual error messages from Sentry, CI logs, and Slack support channels before writing the guide |
| Environment variable list is incomplete | grep scan missed dynamically constructed variable names | Supplement grep results with a manual review of config loader files and .env.example; verify against deployment manifests |
Success Criteria
- Setup completion rate: 90%+ of new developers reach a working local environment within 10 minutes using only the generated guide (no Slack questions needed).
- First-commit time: New hires make their first meaningful commit within 2 business days of starting onboarding.
- Error coverage: The debugging guide covers at least 80% of errors reported in the team's support channel over the prior 90 days.
- Doc freshness: Onboarding documentation passes a quarterly freshness audit with fewer than 3 stale sections flagged.
- Key file accuracy: The key file map covers all files edited in more than 5 PRs during the past quarter.
- Audience satisfaction: Post-onboarding survey scores average 4.0+ out of 5.0 across junior, senior, and contractor cohorts.
- Link validity: Zero broken internal or external links when validated by an automated link checker at publish time.
Scope & Limitations
This skill covers:
- Generating architecture overviews, key file maps, setup guides, task runbooks, and debugging guides from codebase analysis
- Audience-aware documentation tailored for junior developers, senior engineers, and contractors
- Output in Markdown, Notion, and Confluence formats
- Quality verification checklists and freshness audit processes
This skill does NOT cover:
- Automated API reference generation from code annotations — see
engineering/changelog-generatorfor release-oriented docs orengineering/api-design-reviewerfor API quality - Continuous documentation pipelines or CI-triggered doc builds — see
engineering/ci-cd-pipeline-builderfor pipeline automation - Security-focused documentation such as threat models or access control matrices — see
engineering/skill-security-auditorfor security auditing - Runbook generation for incident response and production operations — see
engineering/runbook-generatorfor operational runbooks
Integration Points
| Skill | Integration | Data Flow |
|---|---|---|
engineering/runbook-generator | Onboarding task runbooks can seed operational runbooks for production incident response | Onboarding runbook templates → Runbook Generator for ops-grade expansion |
engineering/api-design-reviewer | API route analysis from Phase 1 feeds into API design quality reviews | Discovered API endpoints → API Design Reviewer for consistency checks |
engineering/database-schema-designer | Database schema files identified during key file mapping inform schema design reviews | Schema file paths and ORM type → Schema Designer for migration planning |
engineering/tech-debt-tracker | Technical debt items surfaced during architecture analysis should be logged for tracking | Architecture analysis findings → Tech Debt Tracker backlog entries |
engineering/ci-cd-pipeline-builder | CI/CD config discovered in Phase 1 can be validated and improved by the pipeline builder | CI config paths and workflow list → Pipeline Builder for optimization |
engineering/dependency-auditor | Dependency counts and lockfiles gathered in Phase 1 feed directly into security and license audits | Package manifests and lockfiles → Dependency Auditor for vulnerability scanning |
#!/usr/bin/env python3
"""Analyze project structure and generate a high-level architecture map.
Scans directories, detects architectural patterns, maps dependencies,
identifies layers, and produces a structured architecture overview
with Mermaid diagram markup.
"""
import argparse
import json
import os
import re
import sys
from collections import defaultdict
from pathlib import Path
IGNORE_DIRS = {
"node_modules", ".git", "__pycache__", ".next", "dist", "build",
".venv", "venv", "env", ".tox", ".mypy_cache", ".pytest_cache",
"vendor", "target", "bin", "obj", ".idea", ".vscode", "coverage",
".nyc_output", ".cache", ".turbo", ".svelte-kit",
}
LAYER_PATTERNS = {
"api": {"patterns": ["api", "routes", "endpoints", "controllers", "handlers", "views"],
"label": "API Layer", "description": "HTTP handlers and route definitions"},
"service": {"patterns": ["services", "service", "usecases", "use-cases", "interactors", "domain"],
"label": "Service/Business Logic", "description": "Core business rules and orchestration"},
"data": {"patterns": ["models", "entities", "schema", "db", "database", "repositories", "repo",
"dal", "prisma", "migrations", "drizzle"],
"label": "Data Layer", "description": "Database models, schemas, and data access"},
"ui": {"patterns": ["components", "pages", "views", "screens", "layouts", "templates", "ui"],
"label": "UI Layer", "description": "User interface components and page layouts"},
"config": {"patterns": ["config", "configuration", "settings", "constants"],
"label": "Configuration", "description": "Application settings and constants"},
"infrastructure": {"patterns": ["infra", "infrastructure", "deploy", "k8s", "terraform",
"docker", "ci", "scripts"],
"label": "Infrastructure", "description": "Deployment, CI/CD, and infrastructure"},
"shared": {"patterns": ["lib", "utils", "helpers", "common", "shared", "core", "pkg"],
"label": "Shared/Utilities", "description": "Reusable utilities and shared code"},
"tests": {"patterns": ["tests", "test", "__tests__", "spec", "specs", "e2e", "integration"],
"label": "Tests", "description": "Test suites and test utilities"},
"docs": {"patterns": ["docs", "documentation", "doc", "wiki"],
"label": "Documentation", "description": "Project documentation and guides"},
"assets": {"patterns": ["assets", "static", "public", "media", "images", "fonts", "styles"],
"label": "Static Assets", "description": "Images, fonts, stylesheets, and static files"},
}
ARCHITECTURE_PATTERNS = {
"monorepo": {
"signals": ["packages", "apps", "libs", "modules"],
"description": "Monorepo with multiple packages or applications",
},
"microservices": {
"signals": ["services", "microservices"],
"description": "Microservices architecture with independent services",
},
"mvc": {
"signals": ["controllers", "models", "views"],
"description": "Model-View-Controller pattern",
},
"layered": {
"signals": ["api", "services", "repositories"],
"description": "Layered architecture with clear separation of concerns",
},
"feature-based": {
"signals": ["features", "modules"],
"description": "Feature-based module organization",
},
"nextjs-app": {
"signals": ["app"],
"description": "Next.js App Router with file-based routing",
},
}
def should_ignore(name):
"""Check if a directory name should be ignored."""
return name in IGNORE_DIRS or name.startswith(".")
def scan_top_dirs(root):
"""Get top-level directories with metadata."""
root_path = Path(root).resolve()
dirs = []
for item in sorted(root_path.iterdir()):
if item.is_dir() and not should_ignore(item.name):
file_count = 0
extensions = defaultdict(int)
for dirpath, dirnames, filenames in os.walk(item):
dirnames[:] = [d for d in dirnames if not should_ignore(d)]
for f in filenames:
ext = Path(f).suffix.lower()
if ext:
extensions[ext] += 1
file_count += 1
if len(Path(dirpath).relative_to(item).parts) > 4:
dirnames.clear()
top_ext = sorted(extensions.items(), key=lambda x: -x[1])[:5]
dirs.append({
"name": item.name,
"file_count": file_count,
"top_extensions": dict(top_ext),
"subdirs": sorted([
d.name for d in item.iterdir()
if d.is_dir() and not should_ignore(d.name)
])[:10],
})
return dirs
def classify_layers(top_dirs):
"""Map top-level directories to architectural layers."""
classified = {}
unclassified = []
for d in top_dirs:
name_lower = d["name"].lower()
matched = False
for layer_id, layer_info in LAYER_PATTERNS.items():
if name_lower in layer_info["patterns"]:
classified[d["name"]] = {
"layer": layer_id,
"label": layer_info["label"],
"description": layer_info["description"],
"file_count": d["file_count"],
}
matched = True
break
if not matched:
unclassified.append(d["name"])
return classified, unclassified
def detect_architecture_pattern(top_dirs):
"""Detect the dominant architecture pattern."""
dir_names = {d["name"].lower() for d in top_dirs}
detected = []
for pattern_id, pattern_info in ARCHITECTURE_PATTERNS.items():
matches = [s for s in pattern_info["signals"] if s in dir_names]
if matches:
detected.append({
"pattern": pattern_id,
"description": pattern_info["description"],
"signals": matches,
"confidence": len(matches) / len(pattern_info["signals"]),
})
detected.sort(key=lambda x: -x["confidence"])
return detected
def parse_dependencies(root):
"""Extract dependency names from common manifest files."""
root_path = Path(root).resolve()
deps = {"runtime": [], "dev": []}
# package.json
pkg_path = root_path / "package.json"
if pkg_path.exists():
try:
data = json.loads(pkg_path.read_text(errors="ignore"))
deps["runtime"].extend(sorted(data.get("dependencies", {}).keys()))
deps["dev"].extend(sorted(data.get("devDependencies", {}).keys()))
except (ValueError, OSError):
pass
# requirements.txt
req_path = root_path / "requirements.txt"
if req_path.exists():
try:
for line in req_path.read_text(errors="ignore").splitlines():
line = line.strip()
if line and not line.startswith("#") and not line.startswith("-"):
pkg = re.split(r"[>=<!\[\];]", line)[0].strip()
if pkg:
deps["runtime"].append(pkg)
except OSError:
pass
# go.mod
gomod_path = root_path / "go.mod"
if gomod_path.exists():
try:
in_require = False
for line in gomod_path.read_text(errors="ignore").splitlines():
line = line.strip()
if line.startswith("require ("):
in_require = True
continue
if in_require and line == ")":
in_require = False
continue
if in_require and line:
parts = line.split()
if parts:
deps["runtime"].append(parts[0])
except OSError:
pass
# Cargo.toml
cargo_path = root_path / "Cargo.toml"
if cargo_path.exists():
try:
in_deps = False
for line in cargo_path.read_text(errors="ignore").splitlines():
if re.match(r"\[dependencies\]", line):
in_deps = True
continue
if re.match(r"\[dev-dependencies\]", line):
in_deps = True
continue
if line.startswith("[") and in_deps:
in_deps = False
continue
if in_deps:
match = re.match(r"^(\w[\w-]*)\s*=", line)
if match:
deps["runtime"].append(match.group(1))
except OSError:
pass
return deps
def generate_mermaid_diagram(layers, pattern):
"""Generate a Mermaid diagram of the architecture."""
lines = ["graph TD"]
layer_order = ["ui", "api", "service", "data", "shared", "config", "infrastructure", "tests"]
nodes = {}
for dir_name, info in layers.items():
layer = info["layer"]
node_id = f"{layer}_{dir_name}".replace("-", "_")
label = f"{dir_name}/ ({info['file_count']} files)"
nodes[layer] = nodes.get(layer, [])
nodes[layer].append((node_id, label))
lines.append(f" {node_id}[\"{label}\"]")
# Add edges based on typical flow
flow_edges = [
("ui", "api"), ("api", "service"), ("service", "data"),
("api", "shared"), ("service", "shared"),
]
added_edges = set()
for from_layer, to_layer in flow_edges:
if from_layer in nodes and to_layer in nodes:
f_id = nodes[from_layer][0][0]
t_id = nodes[to_layer][0][0]
edge_key = (f_id, t_id)
if edge_key not in added_edges:
lines.append(f" {f_id} --> {t_id}")
added_edges.add(edge_key)
return "\n".join(lines)
def generate_report(root):
"""Generate the full architecture analysis report."""
root_path = Path(root).resolve()
top_dirs = scan_top_dirs(root)
layers, unclassified = classify_layers(top_dirs)
patterns = detect_architecture_pattern(top_dirs)
deps = parse_dependencies(root)
mermaid = generate_mermaid_diagram(layers, patterns[0] if patterns else None)
# Count root-level files
root_files = sorted([
f.name for f in root_path.iterdir()
if f.is_file() and not f.name.startswith(".")
])
return {
"project_name": root_path.name,
"project_path": str(root_path),
"architecture_patterns": patterns,
"layers": layers,
"unclassified_directories": unclassified,
"directory_details": top_dirs,
"dependencies": {
"runtime_count": len(deps["runtime"]),
"dev_count": len(deps["dev"]),
"runtime": deps["runtime"][:30],
"dev": deps["dev"][:20],
},
"root_files": root_files,
"mermaid_diagram": mermaid,
}
def format_human(report):
"""Format report as human-readable text."""
lines = []
lines.append(f"{'=' * 60}")
lines.append(f" ARCHITECTURE MAP: {report['project_name']}")
lines.append(f"{'=' * 60}")
lines.append(f"\nProject: {report['project_path']}")
# Architecture patterns
lines.append(f"\n--- Detected Architecture Patterns ---")
if report["architecture_patterns"]:
for p in report["architecture_patterns"]:
conf = f"{p['confidence']:.0%}"
lines.append(f" {p['pattern'].upper()} ({conf} confidence)")
lines.append(f" {p['description']}")
lines.append(f" Signals: {', '.join(p['signals'])}")
else:
lines.append(" No standard architecture pattern detected.")
# Layers
lines.append(f"\n--- Architectural Layers ---")
if report["layers"]:
for dir_name, info in sorted(report["layers"].items(),
key=lambda x: x[1]["layer"]):
lines.append(f" {dir_name}/")
lines.append(f" Layer: {info['label']}")
lines.append(f" Purpose: {info['description']}")
lines.append(f" Files: {info['file_count']}")
else:
lines.append(" No directories matched known layer patterns.")
if report["unclassified_directories"]:
lines.append(f"\n Unclassified directories: {', '.join(report['unclassified_directories'])}")
# Directory details
lines.append(f"\n--- Directory Breakdown ---")
for d in report["directory_details"]:
lines.append(f"\n {d['name']}/ ({d['file_count']} files)")
if d["top_extensions"]:
ext_str = ", ".join(f"{ext}({cnt})" for ext, cnt in d["top_extensions"].items())
lines.append(f" File types: {ext_str}")
if d["subdirs"]:
lines.append(f" Subdirs: {', '.join(d['subdirs'])}")
# Dependencies
deps = report["dependencies"]
lines.append(f"\n--- Dependencies ---")
lines.append(f" Runtime: {deps['runtime_count']} | Dev: {deps['dev_count']}")
if deps["runtime"]:
lines.append(f"\n Key runtime dependencies:")
for dep in deps["runtime"][:15]:
lines.append(f" - {dep}")
if deps["dev"]:
lines.append(f"\n Key dev dependencies:")
for dep in deps["dev"][:10]:
lines.append(f" - {dep}")
# Root files
if report["root_files"]:
lines.append(f"\n--- Root-Level Files ---")
for f in report["root_files"]:
lines.append(f" {f}")
# Mermaid diagram
lines.append(f"\n--- Mermaid Architecture Diagram ---")
lines.append(" (Copy into a Mermaid-compatible renderer)\n")
lines.append("```mermaid")
lines.append(report["mermaid_diagram"])
lines.append("```")
lines.append(f"\n{'=' * 60}")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Analyze project structure and generate a high-level architecture map.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" %(prog)s /path/to/project\n"
" %(prog)s . --json\n"
" %(prog)s ~/my-app --json > architecture.json\n"
),
)
parser.add_argument(
"directory",
nargs="?",
default=".",
help="Project directory to analyze (default: current directory)",
)
parser.add_argument(
"--json",
action="store_true",
dest="json_output",
help="Output results as JSON instead of human-readable format",
)
args = parser.parse_args()
target = Path(args.directory).resolve()
if not target.is_dir():
print(f"Error: '{args.directory}' is not a valid directory.", file=sys.stderr)
sys.exit(1)
report = generate_report(str(target))
if args.json_output:
print(json.dumps(report, indent=2))
else:
print(format_human(report))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Scan a project directory and generate an onboarding guide.
Detects tech stack, key files, directory structure, entry points,
and produces a structured onboarding document for new developers.
"""
import argparse
import json
import os
import sys
from pathlib import Path
from collections import defaultdict
IGNORE_DIRS = {
"node_modules", ".git", "__pycache__", ".next", "dist", "build",
".venv", "venv", "env", ".tox", ".mypy_cache", ".pytest_cache",
".eggs", "*.egg-info", "vendor", "target", "bin", "obj",
".idea", ".vscode", ".DS_Store", "coverage", ".nyc_output",
}
STACK_INDICATORS = {
"package.json": "Node.js",
"requirements.txt": "Python",
"pyproject.toml": "Python",
"setup.py": "Python",
"Pipfile": "Python (Pipenv)",
"go.mod": "Go",
"Cargo.toml": "Rust",
"pom.xml": "Java (Maven)",
"build.gradle": "Java (Gradle)",
"Gemfile": "Ruby",
"composer.json": "PHP",
"mix.exs": "Elixir",
"Package.swift": "Swift",
"CMakeLists.txt": "C/C++ (CMake)",
"Makefile": "Make-based build",
}
FRAMEWORK_INDICATORS = {
"next.config": "Next.js",
"nuxt.config": "Nuxt.js",
"angular.json": "Angular",
"vue.config": "Vue.js",
"svelte.config": "SvelteKit",
"astro.config": "Astro",
"vite.config": "Vite",
"webpack.config": "Webpack",
"tailwind.config": "Tailwind CSS",
"tsconfig.json": "TypeScript",
"docker-compose": "Docker Compose",
"Dockerfile": "Docker",
".github/workflows": "GitHub Actions CI/CD",
".gitlab-ci.yml": "GitLab CI/CD",
"Jenkinsfile": "Jenkins CI/CD",
"terraform": "Terraform",
"prisma/schema.prisma": "Prisma ORM",
"drizzle.config": "Drizzle ORM",
"alembic.ini": "Alembic (SQLAlchemy migrations)",
}
ENTRY_POINT_PATTERNS = [
"main.py", "app.py", "manage.py", "wsgi.py", "asgi.py",
"main.go", "main.rs", "Main.java",
"index.ts", "index.js", "server.ts", "server.js",
"app.ts", "app.js", "index.tsx", "index.jsx",
"src/main.ts", "src/main.js", "src/index.ts", "src/index.js",
"cmd/main.go",
]
KEY_FILE_NAMES = [
"README.md", "README", "CONTRIBUTING.md", "CHANGELOG.md",
"LICENSE", ".env.example", ".env.sample", "Makefile",
"docker-compose.yml", "docker-compose.yaml",
"Dockerfile", ".gitignore", ".editorconfig",
]
def should_ignore(path_part):
"""Check if a path component should be ignored."""
return path_part in IGNORE_DIRS or path_part.startswith(".")
def scan_directory_tree(root, max_depth=3):
"""Walk the directory tree up to max_depth, skipping ignored dirs."""
tree = []
root_path = Path(root).resolve()
for dirpath, dirnames, filenames in os.walk(root_path):
rel = Path(dirpath).relative_to(root_path)
depth = len(rel.parts)
if depth > max_depth:
dirnames.clear()
continue
dirnames[:] = sorted(d for d in dirnames if not should_ignore(d))
for d in dirnames:
tree.append({"type": "dir", "path": str(rel / d), "depth": depth + 1})
for f in sorted(filenames):
if not f.startswith(".") or f in KEY_FILE_NAMES or f.startswith(".env"):
tree.append({"type": "file", "path": str(rel / f), "depth": depth + 1})
return tree
def detect_stack(root):
"""Identify tech stack from manifest and config files."""
root_path = Path(root).resolve()
detected = []
for indicator, stack in STACK_INDICATORS.items():
if (root_path / indicator).exists():
detected.append({"file": indicator, "stack": stack})
return detected
def detect_frameworks(root):
"""Identify frameworks from config files."""
root_path = Path(root).resolve()
detected = []
for pattern, framework in FRAMEWORK_INDICATORS.items():
matches = list(root_path.glob(pattern + "*"))
if matches or (root_path / pattern).exists():
detected.append({"pattern": pattern, "framework": framework})
return detected
def find_entry_points(root):
"""Locate likely entry point files."""
root_path = Path(root).resolve()
found = []
for pattern in ENTRY_POINT_PATTERNS:
target = root_path / pattern
if target.exists():
found.append(str(pattern))
for match in root_path.glob(f"**/{pattern}"):
rel = str(match.relative_to(root_path))
if rel not in found and not any(should_ignore(p) for p in Path(rel).parts):
found.append(rel)
return list(dict.fromkeys(found))[:15]
def find_key_files(root):
"""Find important project files."""
root_path = Path(root).resolve()
found = []
for name in KEY_FILE_NAMES:
target = root_path / name
if target.exists():
found.append(name)
return found
def count_file_types(root, max_depth=5):
"""Count files by extension."""
root_path = Path(root).resolve()
counts = defaultdict(int)
for dirpath, dirnames, filenames in os.walk(root_path):
rel = Path(dirpath).relative_to(root_path)
if len(rel.parts) > max_depth:
dirnames.clear()
continue
dirnames[:] = [d for d in dirnames if not should_ignore(d)]
for f in filenames:
ext = Path(f).suffix.lower()
if ext:
counts[ext] += 1
return dict(sorted(counts.items(), key=lambda x: -x[1])[:20])
def count_source_lines(root, extensions=None):
"""Count total lines across source files."""
if extensions is None:
extensions = {".py", ".ts", ".tsx", ".js", ".jsx", ".go", ".rs", ".java", ".rb", ".php"}
root_path = Path(root).resolve()
total = 0
file_count = 0
for dirpath, dirnames, filenames in os.walk(root_path):
dirnames[:] = [d for d in dirnames if not should_ignore(d)]
for f in filenames:
if Path(f).suffix.lower() in extensions:
try:
filepath = Path(dirpath) / f
total += sum(1 for _ in open(filepath, errors="ignore"))
file_count += 1
except (OSError, PermissionError):
pass
return {"total_lines": total, "source_files": file_count}
def generate_report(root):
"""Generate the full onboarding analysis report."""
root_path = Path(root).resolve()
project_name = root_path.name
stack = detect_stack(root)
frameworks = detect_frameworks(root)
entry_points = find_entry_points(root)
key_files = find_key_files(root)
file_types = count_file_types(root)
source_stats = count_source_lines(root)
tree = scan_directory_tree(root, max_depth=2)
top_dirs = [item["path"] for item in tree if item["type"] == "dir" and item["depth"] == 1]
return {
"project_name": project_name,
"project_path": str(root_path),
"tech_stack": stack,
"frameworks": frameworks,
"entry_points": entry_points,
"key_files": key_files,
"top_level_directories": top_dirs,
"file_type_distribution": file_types,
"source_code_stats": source_stats,
"directory_tree_sample": tree[:60],
}
def format_human(report):
"""Format report as human-readable text."""
lines = []
lines.append(f"{'=' * 60}")
lines.append(f" ONBOARDING GUIDE: {report['project_name']}")
lines.append(f"{'=' * 60}")
lines.append(f"\nProject Path: {report['project_path']}")
lines.append(f"\n--- Tech Stack ---")
if report["tech_stack"]:
for item in report["tech_stack"]:
lines.append(f" [{item['file']}] -> {item['stack']}")
else:
lines.append(" No stack indicators detected.")
lines.append(f"\n--- Frameworks & Tools ---")
if report["frameworks"]:
for item in report["frameworks"]:
lines.append(f" {item['framework']} (detected via {item['pattern']})")
else:
lines.append(" No framework configs detected.")
lines.append(f"\n--- Entry Points ---")
if report["entry_points"]:
for ep in report["entry_points"]:
lines.append(f" -> {ep}")
else:
lines.append(" No standard entry points found.")
lines.append(f"\n--- Key Project Files ---")
for kf in report["key_files"]:
lines.append(f" * {kf}")
lines.append(f"\n--- Top-Level Directories ---")
for d in report["top_level_directories"]:
lines.append(f" {d}/")
lines.append(f"\n--- File Type Distribution ---")
for ext, count in report["file_type_distribution"].items():
lines.append(f" {ext:12s} {count:5d} files")
stats = report["source_code_stats"]
lines.append(f"\n--- Source Code Stats ---")
lines.append(f" Source files: {stats['source_files']}")
lines.append(f" Total lines: {stats['total_lines']}")
lines.append(f"\n--- Directory Structure (depth 2) ---")
for item in report["directory_tree_sample"]:
indent = " " * item["depth"]
suffix = "/" if item["type"] == "dir" else ""
name = Path(item["path"]).name
lines.append(f" {indent}{name}{suffix}")
lines.append(f"\n{'=' * 60}")
lines.append(" Next steps:")
lines.append(" 1. Read the key files listed above")
lines.append(" 2. Follow the setup guide (README or CONTRIBUTING)")
lines.append(" 3. Explore entry points to understand the main flow")
lines.append(" 4. Run the test suite to verify your local setup")
lines.append(f"{'=' * 60}")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Scan a project directory and generate an onboarding guide.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" %(prog)s /path/to/project\n"
" %(prog)s /path/to/project --json\n"
" %(prog)s . --json > onboarding.json\n"
),
)
parser.add_argument(
"directory",
nargs="?",
default=".",
help="Project directory to scan (default: current directory)",
)
parser.add_argument(
"--json",
action="store_true",
dest="json_output",
help="Output results as JSON instead of human-readable format",
)
args = parser.parse_args()
target = Path(args.directory).resolve()
if not target.is_dir():
print(f"Error: '{args.directory}' is not a valid directory.", file=sys.stderr)
sys.exit(1)
report = generate_report(str(target))
if args.json_output:
print(json.dumps(report, indent=2))
else:
print(format_human(report))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Validate a project's development setup completeness.
Checks for README, .env.example, Makefile/scripts, required tools,
CI config, and other setup hygiene indicators. Produces a scored
report with pass/warn/fail verdicts and actionable recommendations.
"""
import argparse
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path
# Each check: (id, name, description, severity)
# severity: "critical" (blocks setup), "recommended", "nice-to-have"
def _file_exists(root, *candidates):
"""Return the first matching file path or None."""
for c in candidates:
if (Path(root) / c).exists():
return c
return None
def _glob_any(root, pattern):
"""Return True if any file matches the glob pattern."""
return bool(list(Path(root).glob(pattern)))
def _file_has_content(root, filename, min_lines=3):
"""Check if a file exists and has meaningful content."""
target = Path(root) / filename
if not target.exists():
return False
try:
lines = [l.strip() for l in open(target, errors="ignore") if l.strip()]
return len(lines) >= min_lines
except (OSError, PermissionError):
return False
def _detect_package_scripts(root):
"""Extract script names from package.json if present."""
pkg_path = Path(root) / "package.json"
if not pkg_path.exists():
return []
try:
import json as _json
data = _json.loads(pkg_path.read_text(errors="ignore"))
return list(data.get("scripts", {}).keys())
except (ValueError, OSError):
return []
def _check_tool_available(tool_name):
"""Check if a CLI tool is available on PATH."""
return shutil.which(tool_name) is not None
def run_checks(root):
"""Run all setup validation checks and return results."""
root = str(Path(root).resolve())
results = []
# 1. README exists and has content
readme = _file_exists(root, "README.md", "README.rst", "README.txt", "README")
results.append({
"id": "readme",
"name": "README documentation",
"severity": "critical",
"status": "pass" if readme and _file_has_content(root, readme) else (
"warn" if readme else "fail"
),
"found": readme,
"recommendation": (
None if readme and _file_has_content(root, readme)
else "README exists but is too short" if readme
else "Add a README.md with project overview, setup steps, and usage examples"
),
})
# 2. Environment variable template
env_file = _file_exists(root, ".env.example", ".env.sample", ".env.template", ".env.defaults")
results.append({
"id": "env_template",
"name": "Environment variable template",
"severity": "critical",
"status": "pass" if env_file else "fail",
"found": env_file,
"recommendation": (
None if env_file
else "Add .env.example listing all required environment variables with descriptions"
),
})
# 3. .env not committed (check .gitignore)
gitignore_path = Path(root) / ".gitignore"
env_ignored = False
if gitignore_path.exists():
try:
content = gitignore_path.read_text(errors="ignore")
env_ignored = any(
line.strip() in (".env", ".env*", ".env.*", ".env.local")
for line in content.splitlines()
)
except OSError:
pass
env_committed = (Path(root) / ".env").exists()
results.append({
"id": "env_gitignore",
"name": ".env excluded from version control",
"severity": "critical",
"status": "pass" if env_ignored and not env_committed else (
"fail" if env_committed and not env_ignored else "warn"
),
"found": ".gitignore has .env pattern" if env_ignored else None,
"recommendation": (
None if env_ignored and not env_committed
else "SECURITY: .env file is committed! Add .env to .gitignore and remove from tracking"
if env_committed and not env_ignored
else "Add .env to .gitignore to prevent accidental secret commits"
),
})
# 4. Build / task runner
makefile = _file_exists(root, "Makefile", "justfile", "Taskfile.yml")
pkg_scripts = _detect_package_scripts(root)
has_runner = makefile is not None or len(pkg_scripts) > 0
results.append({
"id": "task_runner",
"name": "Build/task runner configured",
"severity": "recommended",
"status": "pass" if has_runner else "warn",
"found": makefile or (f"package.json scripts: {', '.join(pkg_scripts[:5])}" if pkg_scripts else None),
"recommendation": (
None if has_runner
else "Add a Makefile or package.json scripts for common tasks (build, test, lint, dev)"
),
})
# 5. Lock file present
lock = _file_exists(
root, "package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb",
"Pipfile.lock", "poetry.lock", "uv.lock",
"go.sum", "Cargo.lock", "Gemfile.lock", "composer.lock",
)
results.append({
"id": "lockfile",
"name": "Dependency lock file",
"severity": "recommended",
"status": "pass" if lock else "warn",
"found": lock,
"recommendation": (
None if lock
else "Commit a dependency lock file to ensure reproducible builds"
),
})
# 6. CI/CD configuration
ci_found = None
if (Path(root) / ".github" / "workflows").is_dir():
workflows = list((Path(root) / ".github" / "workflows").glob("*.yml")) + \
list((Path(root) / ".github" / "workflows").glob("*.yaml"))
if workflows:
ci_found = f".github/workflows/ ({len(workflows)} workflow(s))"
if not ci_found:
ci_file = _file_exists(root, ".gitlab-ci.yml", "Jenkinsfile", ".circleci/config.yml",
"bitbucket-pipelines.yml", ".travis.yml")
if ci_file:
ci_found = ci_file
results.append({
"id": "ci_config",
"name": "CI/CD configuration",
"severity": "recommended",
"status": "pass" if ci_found else "warn",
"found": ci_found,
"recommendation": (
None if ci_found
else "Add CI/CD configuration to automate testing and deployment"
),
})
# 7. Linting / formatting config
lint_config = _file_exists(
root, ".eslintrc", ".eslintrc.js", ".eslintrc.json", ".eslintrc.yml",
"eslint.config.js", "eslint.config.mjs",
".prettierrc", ".prettierrc.json", "prettier.config.js",
"biome.json", "biome.jsonc",
".flake8", ".pylintrc", "pyproject.toml", "setup.cfg",
".rubocop.yml", ".golangci.yml",
)
results.append({
"id": "linter",
"name": "Linting/formatting configuration",
"severity": "recommended",
"status": "pass" if lint_config else "warn",
"found": lint_config,
"recommendation": (
None if lint_config
else "Add linter/formatter config for consistent code style"
),
})
# 8. Tests exist
has_tests = (
_glob_any(root, "**/*.test.*") or
_glob_any(root, "**/*.spec.*") or
_glob_any(root, "**/test_*.py") or
_glob_any(root, "**/tests/**/*.py") or
_glob_any(root, "tests/") or
_glob_any(root, "test/") or
_glob_any(root, "__tests__/")
)
results.append({
"id": "tests",
"name": "Test suite present",
"severity": "recommended",
"status": "pass" if has_tests else "warn",
"found": "Test files detected" if has_tests else None,
"recommendation": (
None if has_tests
else "Add tests to verify setup correctness and prevent regressions"
),
})
# 9. Contributing guide
contrib = _file_exists(root, "CONTRIBUTING.md", "CONTRIBUTING", "docs/CONTRIBUTING.md")
results.append({
"id": "contributing",
"name": "Contributing guidelines",
"severity": "nice-to-have",
"status": "pass" if contrib else "warn",
"found": contrib,
"recommendation": (
None if contrib
else "Add CONTRIBUTING.md with PR process, coding standards, and review expectations"
),
})
# 10. License
lic = _file_exists(root, "LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "COPYING")
results.append({
"id": "license",
"name": "License file",
"severity": "nice-to-have",
"status": "pass" if lic else "warn",
"found": lic,
"recommendation": (
None if lic
else "Add a LICENSE file to clarify usage and contribution terms"
),
})
# 11. Docker setup (if Dockerfile exists, check docker-compose too)
dockerfile = _file_exists(root, "Dockerfile")
compose = _file_exists(root, "docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml")
if dockerfile:
results.append({
"id": "docker_compose",
"name": "Docker Compose for local infra",
"severity": "recommended",
"status": "pass" if compose else "warn",
"found": compose,
"recommendation": (
None if compose
else "Dockerfile found but no docker-compose. Add docker-compose.yml for local infrastructure"
),
})
# 12. Editor config
editor = _file_exists(root, ".editorconfig")
results.append({
"id": "editorconfig",
"name": "EditorConfig for consistent formatting",
"severity": "nice-to-have",
"status": "pass" if editor else "warn",
"found": editor,
"recommendation": (
None if editor
else "Add .editorconfig for consistent indentation across editors"
),
})
return results
def compute_score(results):
"""Compute an overall setup health score."""
weights = {"critical": 3, "recommended": 2, "nice-to-have": 1}
total = 0
earned = 0
for r in results:
w = weights.get(r["severity"], 1)
total += w
if r["status"] == "pass":
earned += w
elif r["status"] == "warn":
earned += w * 0.5
return round((earned / total) * 100) if total > 0 else 0
def format_human(results, score, project_path):
"""Format results as human-readable text."""
lines = []
lines.append(f"{'=' * 60}")
lines.append(f" SETUP VALIDATION: {Path(project_path).name}")
lines.append(f"{'=' * 60}")
lines.append(f"\nProject: {project_path}")
lines.append(f"Score: {score}/100\n")
status_icons = {"pass": "[PASS]", "warn": "[WARN]", "fail": "[FAIL]"}
severity_order = {"critical": 0, "recommended": 1, "nice-to-have": 2}
sorted_results = sorted(results, key=lambda r: (severity_order.get(r["severity"], 9), r["status"] != "fail"))
for r in sorted_results:
icon = status_icons.get(r["status"], "[????]")
lines.append(f" {icon} {r['name']} ({r['severity']})")
if r["found"]:
lines.append(f" Found: {r['found']}")
if r["recommendation"]:
lines.append(f" -> {r['recommendation']}")
lines.append("")
pass_count = sum(1 for r in results if r["status"] == "pass")
warn_count = sum(1 for r in results if r["status"] == "warn")
fail_count = sum(1 for r in results if r["status"] == "fail")
lines.append(f"--- Summary ---")
lines.append(f" Passed: {pass_count} | Warnings: {warn_count} | Failed: {fail_count}")
lines.append(f" Health Score: {score}/100")
if score >= 80:
lines.append("\n Verdict: Setup is well-configured for onboarding.")
elif score >= 50:
lines.append("\n Verdict: Setup needs improvement. Address failed checks first.")
else:
lines.append("\n Verdict: Setup has significant gaps. New developers will struggle.")
lines.append(f"\n{'=' * 60}")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Validate a project's development setup completeness.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" %(prog)s /path/to/project\n"
" %(prog)s . --json\n"
" %(prog)s ~/my-app --json | jq '.results[] | select(.status==\"fail\")'\n"
),
)
parser.add_argument(
"directory",
nargs="?",
default=".",
help="Project directory to validate (default: current directory)",
)
parser.add_argument(
"--json",
action="store_true",
dest="json_output",
help="Output results as JSON instead of human-readable format",
)
args = parser.parse_args()
target = Path(args.directory).resolve()
if not target.is_dir():
print(f"Error: '{args.directory}' is not a valid directory.", file=sys.stderr)
sys.exit(1)
results = run_checks(str(target))
score = compute_score(results)
if args.json_output:
output = {
"project_name": target.name,
"project_path": str(target),
"score": score,
"total_checks": len(results),
"passed": sum(1 for r in results if r["status"] == "pass"),
"warnings": sum(1 for r in results if r["status"] == "warn"),
"failed": sum(1 for r in results if r["status"] == "fail"),
"results": results,
}
print(json.dumps(output, indent=2))
else:
print(format_human(results, score, str(target)))
if __name__ == "__main__":
main()
Related skills
FAQ
What output formats does it support?
Markdown, Notion, and Confluence.
Can it tailor docs to different readers?
Yes, it produces audience-aware output for junior, senior, or contractor developers.