This is page 3 of 23. Use http://codebase.md/tosin2013/documcp?page={x} to view the full context.
# Directory Structure
```
├── .dockerignore
├── .eslintignore
├── .eslintrc.json
├── .github
│ ├── agents
│ │ ├── documcp-ast.md
│ │ ├── documcp-deploy.md
│ │ ├── documcp-memory.md
│ │ ├── documcp-test.md
│ │ └── documcp-tool.md
│ ├── copilot-instructions.md
│ ├── dependabot.yml
│ ├── ISSUE_TEMPLATE
│ │ ├── automated-changelog.md
│ │ ├── bug_report.md
│ │ ├── bug_report.yml
│ │ ├── documentation_issue.md
│ │ ├── feature_request.md
│ │ ├── feature_request.yml
│ │ ├── npm-publishing-fix.md
│ │ └── release_improvements.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── release-drafter.yml
│ └── workflows
│ ├── auto-merge.yml
│ ├── ci.yml
│ ├── codeql.yml
│ ├── dependency-review.yml
│ ├── deploy-docs.yml
│ ├── README.md
│ ├── release-drafter.yml
│ └── release.yml
├── .gitignore
├── .husky
│ ├── commit-msg
│ └── pre-commit
├── .linkcheck.config.json
├── .markdown-link-check.json
├── .nvmrc
├── .pre-commit-config.yaml
├── .versionrc.json
├── ARCHITECTURAL_CHANGES_SUMMARY.md
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── commitlint.config.js
├── CONTRIBUTING.md
├── docker-compose.docs.yml
├── Dockerfile.docs
├── docs
│ ├── .docusaurus
│ │ ├── docusaurus-plugin-content-docs
│ │ │ └── default
│ │ │ └── __mdx-loader-dependency.json
│ │ └── docusaurus-plugin-content-pages
│ │ └── default
│ │ └── __plugin.json
│ ├── adrs
│ │ ├── adr-0001-mcp-server-architecture.md
│ │ ├── adr-0002-repository-analysis-engine.md
│ │ ├── adr-0003-static-site-generator-recommendation-engine.md
│ │ ├── adr-0004-diataxis-framework-integration.md
│ │ ├── adr-0005-github-pages-deployment-automation.md
│ │ ├── adr-0006-mcp-tools-api-design.md
│ │ ├── adr-0007-mcp-prompts-and-resources-integration.md
│ │ ├── adr-0008-intelligent-content-population-engine.md
│ │ ├── adr-0009-content-accuracy-validation-framework.md
│ │ ├── adr-0010-mcp-resource-pattern-redesign.md
│ │ ├── adr-0011-ce-mcp-compatibility.md
│ │ ├── adr-0012-priority-scoring-system-for-documentation-drift.md
│ │ ├── adr-0013-release-pipeline-and-package-distribution.md
│ │ └── README.md
│ ├── api
│ │ ├── .nojekyll
│ │ ├── assets
│ │ │ ├── hierarchy.js
│ │ │ ├── highlight.css
│ │ │ ├── icons.js
│ │ │ ├── icons.svg
│ │ │ ├── main.js
│ │ │ ├── navigation.js
│ │ │ ├── search.js
│ │ │ └── style.css
│ │ ├── hierarchy.html
│ │ ├── index.html
│ │ ├── modules.html
│ │ └── variables
│ │ └── TOOLS.html
│ ├── assets
│ │ └── logo.svg
│ ├── CE-MCP-FINDINGS.md
│ ├── development
│ │ └── MCP_INSPECTOR_TESTING.md
│ ├── docusaurus.config.js
│ ├── explanation
│ │ ├── architecture.md
│ │ └── index.md
│ ├── guides
│ │ ├── link-validation.md
│ │ ├── playwright-integration.md
│ │ └── playwright-testing-workflow.md
│ ├── how-to
│ │ ├── analytics-setup.md
│ │ ├── change-watcher.md
│ │ ├── custom-domains.md
│ │ ├── documentation-freshness-tracking.md
│ │ ├── drift-priority-scoring.md
│ │ ├── github-pages-deployment.md
│ │ ├── index.md
│ │ ├── llm-integration.md
│ │ ├── local-testing.md
│ │ ├── performance-optimization.md
│ │ ├── prompting-guide.md
│ │ ├── repository-analysis.md
│ │ ├── seo-optimization.md
│ │ ├── site-monitoring.md
│ │ ├── troubleshooting.md
│ │ └── usage-examples.md
│ ├── index.md
│ ├── knowledge-graph.md
│ ├── package-lock.json
│ ├── package.json
│ ├── phase-2-intelligence.md
│ ├── reference
│ │ ├── api-overview.md
│ │ ├── cli.md
│ │ ├── configuration.md
│ │ ├── deploy-pages.md
│ │ ├── index.md
│ │ ├── mcp-tools.md
│ │ └── prompt-templates.md
│ ├── research
│ │ ├── cross-domain-integration
│ │ │ └── README.md
│ │ ├── domain-1-mcp-architecture
│ │ │ ├── index.md
│ │ │ └── mcp-performance-research.md
│ │ ├── domain-2-repository-analysis
│ │ │ └── README.md
│ │ ├── domain-3-ssg-recommendation
│ │ │ ├── index.md
│ │ │ └── ssg-performance-analysis.md
│ │ ├── domain-4-diataxis-integration
│ │ │ └── README.md
│ │ ├── domain-5-github-deployment
│ │ │ ├── github-pages-security-analysis.md
│ │ │ └── index.md
│ │ ├── domain-6-api-design
│ │ │ └── README.md
│ │ ├── README.md
│ │ ├── research-integration-summary-2025-01-14.md
│ │ ├── research-progress-template.md
│ │ └── research-questions-2025-01-14.md
│ ├── robots.txt
│ ├── sidebars.js
│ ├── sitemap.xml
│ ├── src
│ │ └── css
│ │ └── custom.css
│ └── tutorials
│ ├── development-setup.md
│ ├── environment-setup.md
│ ├── first-deployment.md
│ ├── getting-started.md
│ ├── index.md
│ ├── memory-workflows.md
│ └── user-onboarding.md
├── ISSUE_IMPLEMENTATION_SUMMARY.md
├── jest.config.js
├── LICENSE
├── Makefile
├── MCP_PHASE2_IMPLEMENTATION.md
├── mcp-config-example.json
├── mcp.json
├── package-lock.json
├── package.json
├── README.md
├── release.sh
├── scripts
│ └── check-package-structure.cjs
├── SECURITY.md
├── setup-precommit.sh
├── src
│ ├── benchmarks
│ │ └── performance.ts
│ ├── index.ts
│ ├── memory
│ │ ├── contextual-retrieval.ts
│ │ ├── deployment-analytics.ts
│ │ ├── enhanced-manager.ts
│ │ ├── export-import.ts
│ │ ├── freshness-kg-integration.ts
│ │ ├── index.ts
│ │ ├── integration.ts
│ │ ├── kg-code-integration.ts
│ │ ├── kg-health.ts
│ │ ├── kg-integration.ts
│ │ ├── kg-link-validator.ts
│ │ ├── kg-storage.ts
│ │ ├── knowledge-graph.ts
│ │ ├── learning.ts
│ │ ├── manager.ts
│ │ ├── multi-agent-sharing.ts
│ │ ├── pruning.ts
│ │ ├── schemas.ts
│ │ ├── storage.ts
│ │ ├── temporal-analysis.ts
│ │ ├── user-preferences.ts
│ │ └── visualization.ts
│ ├── prompts
│ │ └── technical-writer-prompts.ts
│ ├── scripts
│ │ └── benchmark.ts
│ ├── templates
│ │ └── playwright
│ │ ├── accessibility.spec.template.ts
│ │ ├── Dockerfile.template
│ │ ├── docs-e2e.workflow.template.yml
│ │ ├── link-validation.spec.template.ts
│ │ └── playwright.config.template.ts
│ ├── tools
│ │ ├── analyze-deployments.ts
│ │ ├── analyze-readme.ts
│ │ ├── analyze-repository.ts
│ │ ├── change-watcher.ts
│ │ ├── check-documentation-links.ts
│ │ ├── cleanup-agent-artifacts.ts
│ │ ├── deploy-pages.ts
│ │ ├── detect-gaps.ts
│ │ ├── evaluate-readme-health.ts
│ │ ├── generate-config.ts
│ │ ├── generate-contextual-content.ts
│ │ ├── generate-llm-context.ts
│ │ ├── generate-readme-template.ts
│ │ ├── generate-technical-writer-prompts.ts
│ │ ├── kg-health-check.ts
│ │ ├── manage-preferences.ts
│ │ ├── manage-sitemap.ts
│ │ ├── optimize-readme.ts
│ │ ├── populate-content.ts
│ │ ├── readme-best-practices.ts
│ │ ├── recommend-ssg.ts
│ │ ├── setup-playwright-tests.ts
│ │ ├── setup-structure.ts
│ │ ├── simulate-execution.ts
│ │ ├── sync-code-to-docs.ts
│ │ ├── test-local-deployment.ts
│ │ ├── track-documentation-freshness.ts
│ │ ├── update-existing-documentation.ts
│ │ ├── validate-content.ts
│ │ ├── validate-documentation-freshness.ts
│ │ ├── validate-readme-checklist.ts
│ │ └── verify-deployment.ts
│ ├── types
│ │ └── api.ts
│ ├── utils
│ │ ├── artifact-detector.ts
│ │ ├── ast-analyzer.ts
│ │ ├── change-watcher.ts
│ │ ├── code-scanner.ts
│ │ ├── content-extractor.ts
│ │ ├── drift-detector.ts
│ │ ├── execution-simulator.ts
│ │ ├── freshness-tracker.ts
│ │ ├── language-parsers-simple.ts
│ │ ├── llm-client.ts
│ │ ├── permission-checker.ts
│ │ ├── semantic-analyzer.ts
│ │ ├── sitemap-generator.ts
│ │ ├── usage-metadata.ts
│ │ └── user-feedback-integration.ts
│ └── workflows
│ └── documentation-workflow.ts
├── test-docs-local.sh
├── tests
│ ├── api
│ │ └── mcp-responses.test.ts
│ ├── benchmarks
│ │ └── performance.test.ts
│ ├── call-graph-builder.test.ts
│ ├── change-watcher-priority.integration.test.ts
│ ├── change-watcher.test.ts
│ ├── edge-cases
│ │ └── error-handling.test.ts
│ ├── execution-simulator.test.ts
│ ├── functional
│ │ └── tools.test.ts
│ ├── integration
│ │ ├── kg-documentation-workflow.test.ts
│ │ ├── knowledge-graph-workflow.test.ts
│ │ ├── mcp-readme-tools.test.ts
│ │ ├── memory-mcp-tools.test.ts
│ │ ├── readme-technical-writer.test.ts
│ │ └── workflow.test.ts
│ ├── memory
│ │ ├── contextual-retrieval.test.ts
│ │ ├── enhanced-manager.test.ts
│ │ ├── export-import.test.ts
│ │ ├── freshness-kg-integration.test.ts
│ │ ├── kg-code-integration.test.ts
│ │ ├── kg-health.test.ts
│ │ ├── kg-link-validator.test.ts
│ │ ├── kg-storage-validation.test.ts
│ │ ├── kg-storage.test.ts
│ │ ├── knowledge-graph-documentation-examples.test.ts
│ │ ├── knowledge-graph-enhanced.test.ts
│ │ ├── knowledge-graph.test.ts
│ │ ├── learning.test.ts
│ │ ├── manager-advanced.test.ts
│ │ ├── manager.test.ts
│ │ ├── mcp-resource-integration.test.ts
│ │ ├── mcp-tool-persistence.test.ts
│ │ ├── schemas-documentation-examples.test.ts
│ │ ├── schemas.test.ts
│ │ ├── storage.test.ts
│ │ ├── temporal-analysis.test.ts
│ │ └── user-preferences.test.ts
│ ├── performance
│ │ ├── memory-load-testing.test.ts
│ │ └── memory-stress-testing.test.ts
│ ├── prompts
│ │ ├── guided-workflow-prompts.test.ts
│ │ └── technical-writer-prompts.test.ts
│ ├── server.test.ts
│ ├── setup.ts
│ ├── tools
│ │ ├── all-tools.test.ts
│ │ ├── analyze-coverage.test.ts
│ │ ├── analyze-deployments.test.ts
│ │ ├── analyze-readme.test.ts
│ │ ├── analyze-repository.test.ts
│ │ ├── check-documentation-links.test.ts
│ │ ├── cleanup-agent-artifacts.test.ts
│ │ ├── deploy-pages-kg-retrieval.test.ts
│ │ ├── deploy-pages-tracking.test.ts
│ │ ├── deploy-pages.test.ts
│ │ ├── detect-gaps.test.ts
│ │ ├── evaluate-readme-health.test.ts
│ │ ├── generate-contextual-content.test.ts
│ │ ├── generate-llm-context.test.ts
│ │ ├── generate-readme-template.test.ts
│ │ ├── generate-technical-writer-prompts.test.ts
│ │ ├── kg-health-check.test.ts
│ │ ├── manage-sitemap.test.ts
│ │ ├── optimize-readme.test.ts
│ │ ├── readme-best-practices.test.ts
│ │ ├── recommend-ssg-historical.test.ts
│ │ ├── recommend-ssg-preferences.test.ts
│ │ ├── recommend-ssg.test.ts
│ │ ├── simple-coverage.test.ts
│ │ ├── sync-code-to-docs.test.ts
│ │ ├── test-local-deployment.test.ts
│ │ ├── tool-error-handling.test.ts
│ │ ├── track-documentation-freshness.test.ts
│ │ ├── validate-content.test.ts
│ │ ├── validate-documentation-freshness.test.ts
│ │ └── validate-readme-checklist.test.ts
│ ├── types
│ │ └── type-safety.test.ts
│ └── utils
│ ├── artifact-detector.test.ts
│ ├── ast-analyzer.test.ts
│ ├── content-extractor.test.ts
│ ├── drift-detector-diataxis.test.ts
│ ├── drift-detector-priority.test.ts
│ ├── drift-detector.test.ts
│ ├── freshness-tracker.test.ts
│ ├── llm-client.test.ts
│ ├── semantic-analyzer.test.ts
│ ├── sitemap-generator.test.ts
│ ├── usage-metadata.test.ts
│ └── user-feedback-integration.test.ts
├── tsconfig.json
└── typedoc.json
```
# Files
--------------------------------------------------------------------------------
/docs/how-to/documentation-freshness-tracking.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.950Z"
last_validated: "2025-12-09T19:41:38.581Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# How to Track Documentation Freshness
This guide shows you how to use DocuMCP's documentation freshness tracking system to monitor and maintain up-to-date documentation.
## Quick Start
```bash
# Initialize freshness tracking:
"validate documentation freshness for my docs directory"
# Check freshness status:
"track documentation freshness"
```
## Overview
Documentation freshness tracking helps you:
- **Identify stale documentation**: Find files that haven't been updated recently
- **Maintain quality**: Ensure documentation stays current with code changes
- **Track history**: Monitor documentation updates over time via knowledge graph integration
- **Automate maintenance**: Set up workflows for regular freshness checks
## Initial Setup
### Step 1: Initialize Freshness Metadata
Before tracking freshness, you need to initialize metadata for your documentation files:
```json
{
"docsPath": "/path/to/docs",
"projectPath": "/path/to/project",
"initializeMissing": true,
"validateAgainstGit": true
}
```
This will:
- Create freshness metadata for all documentation files
- Set initial timestamps based on file modification dates
- Link metadata to git history (if available)
### Step 2: Verify Initialization
Check that metadata was created successfully:
```bash
# Track freshness to see initialized files:
"track documentation freshness for my docs"
```
You should see all files marked as "fresh" initially.
## Regular Freshness Checks
### Basic Tracking
Run regular freshness checks to monitor documentation staleness:
```json
{
"docsPath": "/path/to/docs",
"includeFileList": true
}
```
### Using Presets
DocuMCP provides convenient presets for different update frequencies:
- **realtime**: For documentation that changes frequently (minutes/hours)
- **active**: For actively maintained docs (days)
- **recent**: For recently updated docs (weeks)
- **weekly**: For weekly review cycles
- **monthly**: For monthly maintenance (default)
- **quarterly**: For quarterly reviews
```json
{
"docsPath": "/path/to/docs",
"preset": "monthly"
}
```
### Custom Thresholds
Define your own staleness thresholds:
```json
{
"docsPath": "/path/to/docs",
"warningThreshold": {
"value": 7,
"unit": "days"
},
"staleThreshold": {
"value": 30,
"unit": "days"
},
"criticalThreshold": {
"value": 90,
"unit": "days"
}
}
```
## Understanding Freshness Levels
### Fresh ✅
Files updated within the warning threshold (default: 7 days)
### Warning 🟡
Files older than warning threshold but newer than stale threshold (7-30 days)
### Stale 🟠
Files older than stale threshold but newer than critical threshold (30-90 days)
### Critical 🔴
Files older than critical threshold (90+ days)
### Unknown ❓
Files without freshness metadata (need initialization)
## Workflow Examples
### Weekly Documentation Review
```bash
# 1. Check freshness status
"track documentation freshness with preset weekly"
# 2. Review stale files and update as needed
# (manually update documentation)
# 3. Validate freshness after updates
"validate documentation freshness and update existing metadata"
```
### After Major Code Changes
```bash
# 1. Update documentation to reflect code changes
# (manually update files)
# 2. Validate freshness against git
"validate documentation freshness with git validation"
# 3. Track updated status
"track documentation freshness"
```
### Automated CI/CD Integration
Add freshness checks to your CI/CD pipeline:
```yaml
# .github/workflows/docs-freshness.yml
- name: Check Documentation Freshness
run: |
documcp track_documentation_freshness \
--docsPath ./docs \
--preset monthly \
--failOnStale true
```
## Advanced Usage
### Knowledge Graph Integration
Freshness tracking events are automatically stored in the knowledge graph:
```json
{
"docsPath": "/path/to/docs",
"projectPath": "/path/to/project",
"storeInKG": true
}
```
This enables:
- Historical analysis of documentation updates
- Pattern recognition across projects
- Intelligent recommendations based on past behavior
### Sorting and Filtering
Customize how files are displayed:
```json
{
"docsPath": "/path/to/docs",
"sortBy": "staleness", // Options: "age", "path", "staleness"
"includeFileList": true
}
```
### Git Integration
Validate freshness against git history:
```json
{
"docsPath": "/path/to/docs",
"projectPath": "/path/to/project",
"validateAgainstGit": true
}
```
This compares file modification times with git commit history for more accurate staleness detection.
## Best Practices
### 1. Initialize Early
Set up freshness tracking when you first create documentation:
```bash
"initialize freshness tracking for my new documentation"
```
### 2. Regular Checks
Schedule regular freshness checks:
- Weekly for active projects
- Monthly for stable projects
- Quarterly for archived documentation
### 3. Update Thresholds
Adjust thresholds based on your project's update frequency:
- Active projects: 7/30/90 days
- Stable projects: 30/90/180 days
- Archived docs: 90/180/365 days
### 4. Integrate with Workflows
Combine freshness tracking with other DocuMCP tools:
```bash
# Check freshness → Update stale docs → Validate → Deploy
"track documentation freshness, then update stale files, validate, and deploy"
```
### 5. Monitor Trends
Use knowledge graph insights to identify patterns:
```bash
# Get freshness insights from knowledge graph
"get insights about documentation freshness trends"
```
## Troubleshooting
### Problem: All files show as "unknown"
**Solution**: Run `validate_documentation_freshness` with `initializeMissing: true`
### Problem: Freshness not updating after file changes
**Solution**: Run `validate_documentation_freshness` with `updateExisting: true`
### Problem: Git validation failing
**Solution**: Ensure `projectPath` points to git repository root and git is initialized
### Problem: Thresholds not working as expected
**Solution**: Check that threshold values are positive numbers and units match your needs
## Integration with Other Tools
### With Sitemap Management
```bash
# Track freshness → Generate sitemap → Deploy
"track documentation freshness, then generate sitemap and deploy"
```
### With Content Validation
```bash
# Validate freshness → Validate content → Check links
"validate documentation freshness, then validate content and check links"
```
### With Gap Detection
```bash
# Detect gaps → Track freshness → Update documentation
"detect documentation gaps, track freshness, and update stale files"
```
## Next Steps
- [Site Monitoring](site-monitoring.md) - Monitor your documentation site health
- [SEO Optimization](seo-optimization.md) - Improve search engine visibility
- [Performance Optimization](performance-optimization.md) - Optimize documentation performance
- [MCP Tools Reference](../reference/mcp-tools.md#documentation-freshness-tracking-tools) - Complete API reference for freshness tracking tools
```
--------------------------------------------------------------------------------
/docs/how-to/analytics-setup.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.949Z"
last_validated: "2025-12-09T19:41:38.580Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# How to Use DocuMCP Deployment Analytics
This guide shows you how to access and use DocuMCP's built-in deployment analytics to track your documentation deployment success and patterns.
## Quick Setup
```bash
# Analyze deployment patterns:
"analyze my deployment history and provide insights"
```
## Analytics Overview
DocuMCP provides comprehensive **deployment analytics** to help you understand and optimize your documentation deployment process:
### Analytics Types
- **Deployment Success Tracking**: Monitor deployment success/failure rates
- **SSG Performance Analytics**: Compare static site generator effectiveness
- **Build Time Metrics**: Track deployment speed and optimization opportunities
- **Project Pattern Analysis**: Understand which configurations work best
### Built-in Analytics Features
- **Deployment Health Scoring**: 0-100 health score for your deployment pipeline
- **SSG Comparison**: Compare success rates across different static site generators
- **Trend Analysis**: Track deployment patterns over time
- **Knowledge Graph Integration**: Learn from deployment history for better recommendations
## Using Deployment Analytics
### Method 1: Generate Full Analytics Report
```bash
# Get comprehensive deployment analytics:
"analyze my deployments and provide a full report"
```
This will provide:
1. Overall deployment success rates
2. SSG performance comparison
3. Build time analysis
4. Project pattern insights
5. Recommendations for optimization
### Method 2: Specific Analytics Queries
#### Get SSG Statistics
```bash
# Analyze specific SSG performance:
"show me statistics for Docusaurus deployments"
```
#### Compare SSG Performance
```bash
# Compare multiple SSGs:
"compare deployment success rates between Hugo and Jekyll"
```
#### Get Deployment Health Score
```bash
# Check deployment pipeline health:
"what is my deployment health score?"
```
#### Analyze Deployment Trends
```bash
# View deployment trends over time:
"show me deployment trends for the last 30 days"
```
## Deployment Analytics Examples
### Sample Analytics Report
```typescript
// Example deployment analytics report structure
{
"summary": {
"totalProjects": 15,
"totalDeployments": 42,
"overallSuccessRate": 0.85,
"mostUsedSSG": "docusaurus",
"mostSuccessfulSSG": "hugo"
},
"patterns": [
{
"ssg": "docusaurus",
"totalDeployments": 18,
"successfulDeployments": 16,
"failedDeployments": 2,
"successRate": 0.89,
"averageBuildTime": 45000,
"projectCount": 8
}
],
"insights": [
{
"type": "success",
"title": "High Success Rate",
"description": "Excellent! 85% of deployments succeed"
}
]
}
```
### Health Score Breakdown
```typescript
// Example health score analysis
{
"score": 78,
"factors": [
{
"name": "Overall Success Rate",
"impact": 34,
"status": "good"
},
{
"name": "Active Projects",
"impact": 20,
"status": "good"
},
{
"name": "Deployment Activity",
"impact": 15,
"status": "warning"
},
{
"name": "SSG Diversity",
"impact": 9,
"status": "warning"
}
]
}
```
### MCP Tool Integration
```typescript
// Using the analyze_deployments MCP tool directly
import { analyzeDeployments } from "./dist/tools/analyze-deployments.js";
// Get full analytics report
const report = await analyzeDeployments({
analysisType: "full_report",
});
// Get specific SSG statistics
const docusaurusStats = await analyzeDeployments({
analysisType: "ssg_stats",
ssg: "docusaurus",
});
// Compare multiple SSGs
const comparison = await analyzeDeployments({
analysisType: "compare",
ssgs: ["hugo", "jekyll", "docusaurus"],
});
// Get deployment health score
const health = await analyzeDeployments({
analysisType: "health",
});
```
## Advanced Deployment Analytics
### Deployment Pattern Analysis
```bash
# Analyze deployment patterns by technology:
"show me deployment success patterns for TypeScript projects"
# Analyze by project size:
"compare deployment success rates for small vs large projects"
# Analyze by team size:
"show deployment patterns for different team sizes"
```
### Knowledge Graph Insights
```bash
# Get insights from deployment history:
"what SSG works best for React projects based on deployment history?"
# Learn from similar projects:
"recommend deployment strategy based on similar successful projects"
# Analyze failure patterns:
"what are the common causes of deployment failures?"
```
### Trend Analysis
```bash
# Analyze deployment trends:
"show me deployment success trends over the last 6 months"
# Compare time periods:
"compare deployment performance between Q3 and Q4"
# Identify improvement opportunities:
"what deployment metrics have improved recently?"
```
## Troubleshooting
### Common Issues
**Problem**: No deployment data available
**Solution**: Deploy at least one project to start collecting analytics data
**Problem**: Analytics tool returns empty results
**Solution**: Ensure knowledge graph storage directory exists and has proper permissions
**Problem**: Health score seems low
**Solution**: Review deployment failures and optimize SSG configurations
**Problem**: Missing deployment history
**Solution**: Check that deployment tracking is enabled in knowledge graph
### Analytics Debugging
```bash
# Debug deployment analytics issues:
"check my deployment analytics configuration and data availability"
```
## Best Practices
### Deployment Analytics Guidelines
1. **Regular Deployments**: Deploy frequently to build meaningful analytics data
2. **Track Failures**: Learn from deployment failures to improve success rates
3. **Monitor Trends**: Review analytics weekly to identify patterns
4. **Compare SSGs**: Use analytics to choose the best SSG for each project type
5. **Health Monitoring**: Keep deployment health score above 70
### Data Quality
1. **Consistent Tracking**: Ensure all deployments are tracked in knowledge graph
2. **Clean Data**: Review and clean up failed deployment records periodically
3. **Regular Analysis**: Run analytics reports monthly to identify trends
4. **Documentation**: Document deployment patterns and insights
5. **Team Sharing**: Share analytics insights with your development team
## Deployment Analytics Tools
### Built-in DocuMCP Analytics
- **Deployment success tracking**: Monitor success/failure rates
- **SSG performance analysis**: Compare static site generator effectiveness
- **Build time metrics**: Track deployment speed and optimization opportunities
- **Knowledge graph insights**: Learn from deployment history patterns
### MCP Tools Available
- `analyze_deployments`: Generate comprehensive deployment analytics
- `deploy_pages`: Track deployment attempts and outcomes
- `recommend_ssg`: Get SSG recommendations based on analytics
## Next Steps
- [Deploy Pages](../reference/mcp-tools.md#deploy_pages)
- [SSG Recommendations](../reference/mcp-tools.md#recommend_ssg)
- [Knowledge Graph](../knowledge-graph.md)
- [Troubleshooting](troubleshooting.md)
```
--------------------------------------------------------------------------------
/src/tools/verify-deployment.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
import { promises as fs } from "fs";
import path from "path";
import { MCPToolResponse, formatMCPResponse } from "../types/api.js";
const inputSchema = z.object({
repository: z.string(),
url: z.string().optional(),
});
interface DeploymentCheck {
check: string;
status: "pass" | "fail" | "warning";
message: string;
recommendation?: string;
}
export async function verifyDeployment(
args: unknown,
): Promise<{ content: any[] }> {
const startTime = Date.now();
const { repository, url } = inputSchema.parse(args);
try {
const checks: DeploymentCheck[] = [];
// Determine repository path
const repoPath = repository.startsWith("http") ? "." : repository;
// Check 1: GitHub Actions workflow exists
const workflowPath = path.join(repoPath, ".github", "workflows");
try {
const workflows = await fs.readdir(workflowPath);
const deployWorkflow = workflows.find(
(f) =>
f.includes("deploy") || f.includes("pages") || f.includes("docs"),
);
if (deployWorkflow) {
checks.push({
check: "GitHub Actions Workflow",
status: "pass",
message: `Found deployment workflow: ${deployWorkflow}`,
});
} else {
checks.push({
check: "GitHub Actions Workflow",
status: "fail",
message: "No deployment workflow found",
recommendation: "Run deploy_pages tool to create a workflow",
});
}
} catch {
checks.push({
check: "GitHub Actions Workflow",
status: "fail",
message: "No .github/workflows directory found",
recommendation: "Run deploy_pages tool to set up GitHub Actions",
});
}
// Check 2: Documentation source files exist
const docsPaths = ["docs", "documentation", "site", "content"];
let docsFound = false;
for (const docsPath of docsPaths) {
try {
const fullPath = path.join(repoPath, docsPath);
const stats = await fs.stat(fullPath);
if (stats.isDirectory()) {
const files = await fs.readdir(fullPath);
const mdFiles = files.filter(
(f) => f.endsWith(".md") || f.endsWith(".mdx"),
);
if (mdFiles.length > 0) {
docsFound = true;
checks.push({
check: "Documentation Source Files",
status: "pass",
message: `Found ${mdFiles.length} documentation files in ${docsPath}/`,
});
break;
}
}
} catch {
// Directory doesn't exist, continue checking
}
}
if (!docsFound) {
checks.push({
check: "Documentation Source Files",
status: "warning",
message: "No documentation files found in standard locations",
recommendation:
"Run setup_structure tool to create documentation structure",
});
}
// Check 3: Configuration files
const configPatterns = [
"docusaurus.config.js",
"mkdocs.yml",
"hugo.toml",
"hugo.yaml",
"_config.yml",
".eleventy.js",
];
let configFound = false;
for (const config of configPatterns) {
try {
await fs.access(path.join(repoPath, config));
configFound = true;
checks.push({
check: "SSG Configuration",
status: "pass",
message: `Found configuration file: ${config}`,
});
break;
} catch {
// File doesn't exist, continue
}
}
if (!configFound) {
checks.push({
check: "SSG Configuration",
status: "fail",
message: "No static site generator configuration found",
recommendation: "Run generate_config tool to create SSG configuration",
});
}
// Check 4: Build output directory
const buildDirs = ["_site", "build", "dist", "public", "out"];
let buildFound = false;
for (const buildDir of buildDirs) {
try {
const buildPath = path.join(repoPath, buildDir);
const stats = await fs.stat(buildPath);
if (stats.isDirectory()) {
buildFound = true;
checks.push({
check: "Build Output",
status: "pass",
message: `Found build output directory: ${buildDir}/`,
});
break;
}
} catch {
// Directory doesn't exist
}
}
if (!buildFound) {
checks.push({
check: "Build Output",
status: "warning",
message: "No build output directory found",
recommendation: "Run your SSG build command to generate the site",
});
}
// Check 5: GitHub Pages settings (if URL provided)
if (url) {
checks.push({
check: "Deployment URL",
status: "warning",
message: `Expected URL: ${url}`,
recommendation: "Verify GitHub Pages is enabled in repository settings",
});
}
// Generate summary
const passCount = checks.filter((c) => c.status === "pass").length;
const failCount = checks.filter((c) => c.status === "fail").length;
const warningCount = checks.filter((c) => c.status === "warning").length;
let overallStatus = "Ready for deployment";
if (failCount > 0) {
overallStatus = "Configuration required";
} else if (warningCount > 0) {
overallStatus = "Minor issues detected";
}
const verificationResult = {
repository,
url,
overallStatus,
checks,
summary: {
passed: passCount,
warnings: warningCount,
failed: failCount,
total: checks.length,
},
};
const response: MCPToolResponse<typeof verificationResult> = {
success: true,
data: verificationResult,
metadata: {
toolVersion: "1.0.0",
executionTime: Date.now() - startTime,
timestamp: new Date().toISOString(),
},
recommendations: [
{
type:
failCount > 0 ? "critical" : warningCount > 0 ? "warning" : "info",
title: "Deployment Verification Complete",
description: `${overallStatus}. ${passCount} checks passed, ${warningCount} warnings, ${failCount} failures.`,
},
],
nextSteps: checks
.filter((check) => check.recommendation)
.map((check) => ({
action: check.recommendation!,
toolRequired: check.recommendation!.includes("deploy_pages")
? "deploy_pages"
: check.recommendation!.includes("setup_structure")
? "setup_structure"
: check.recommendation!.includes("generate_config")
? "generate_config"
: "manual",
description: check.message,
priority: check.status === "fail" ? "high" : ("medium" as const),
})),
};
return formatMCPResponse(response);
} catch (error) {
const errorResponse: MCPToolResponse = {
success: false,
error: {
code: "VERIFICATION_FAILED",
message: `Failed to verify deployment: ${error}`,
resolution: "Ensure repository path is accessible",
},
metadata: {
toolVersion: "1.0.0",
executionTime: Date.now() - startTime,
timestamp: new Date().toISOString(),
},
};
return formatMCPResponse(errorResponse);
}
}
// Removed unused getStatusEmoji function - status indicators now handled in formatMCPResponse
```
--------------------------------------------------------------------------------
/.github/copilot-instructions.md:
--------------------------------------------------------------------------------
```markdown
# DocuMCP AI Coding Agent Instructions
DocuMCP is an intelligent MCP server for GitHub Pages documentation deployment with 45+ tools, Knowledge Graph memory system, and AST-based code analysis.
## Architecture Essentials
### MCP Server Design (src/index.ts)
- **Stateless operation**: Each tool analyzes current state; no persistent session data
- **Resource storage**: Temporary session context in `resourceStore` Map with URIs like `documcp://analysis/{id}`
- **Response format**: ALL tools MUST use `formatMCPResponse()` from `src/types/api.ts` returning `MCPToolResponse`
- **Memory integration**: Tools auto-store results in JSONL (`.documcp/memory/`) and query historical data
### Tool Implementation Pattern
Every tool follows this structure:
1. Zod schema for input validation (e.g., `inputSchema.parse()`)
2. Business logic with error handling
3. Return via `formatMCPResponse({ success, data, metadata, recommendations?, nextSteps? })`
4. Store result as resource: `storeResourceFromToolResult(result, 'analysis', analysis.id)`
**Example** (see `src/tools/analyze-repository.ts`):
```typescript
const inputSchema = z.object({
path: z.string(),
depth: z.enum(["quick", "standard", "deep"]).optional(),
});
export async function analyzeRepository(
args: unknown,
): Promise<MCPToolResponse> {
const input = inputSchema.parse(args);
// ... logic ...
return formatMCPResponse({
success: true,
data: analysis,
metadata: { toolVersion, executionTime, timestamp },
});
}
```
### Knowledge Graph Memory (src/memory/)
- **Entities**: Project, User, Configuration, Technology, CodeFile, DocumentationSection (see `src/memory/schemas.ts`)
- **Relationships**: `project_uses_technology`, `project_deployed_with`, `similar_to`, `documents`, etc.
- **Storage**: `.documcp/memory/knowledge-graph-entities.jsonl` and `knowledge-graph-relationships.jsonl`
- **Integration**: Use `createOrUpdateProject()`, `getProjectContext()`, `trackDeployment()` from `src/memory/kg-integration.ts`
**analyze_repository enhancement**: Retrieves project history before analysis, shows previous analysis count, similar projects
## Development Workflows
### Essential Commands
```bash
npm run build # Compile TypeScript → dist/
npm run dev # Development watch mode with tsx
npm test # Run Jest test suite
npm run test:coverage # Coverage report (80% threshold)
npm run build:inspect # Launch MCP Inspector for interactive testing
npm run ci # Full CI pipeline: typecheck + lint + test + build
make qa # Quality assurance: lint + types + test + coverage
```
### Testing Strategy
- **Location**: `tests/` mirroring `src/` structure
- **Coverage**: 80% global (branches/functions/lines), 60% for `recommend-ssg.ts` (complex logic)
- **Excluded**: Experimental memory features, `src/index.ts` entry point
- **Pattern**: Use `formatMCPResponse()` for consistent response validation
- **Integration**: Multi-tool workflow tests in `tests/integration/`
### MCP Inspector Workflow
1. `npm run build:inspect` opens browser at `http://localhost:5173`
2. Click "Connect" to attach to server
3. Test tools with custom parameters interactively
4. Verify resources/prompts without full integration
## Project-Specific Conventions
### ESM Module Requirements
- **ALL imports** must end with `.js` (even for `.ts` files): `import { foo } from './utils.js'`
- Use `import type` for type-only imports: `import type { MyType } from './types.js'`
- File URLs: `fileURLToPath(import.meta.url)` and `dirname()`
### Path & Permission Handling
- **Security**: Use `isPathAllowed(path, allowedRoots)` from `src/utils/permission-checker.ts` before file operations
- **Async FS**: Always use `fs.promises` API, never sync methods
- **Cross-platform**: Use `path` module for joining/resolving
### Git Integration
- Use `simple-git` library for repository operations
- Handle missing `.git` directories gracefully (check `hasGit` flag)
- Always validate repo state before analysis
### Diataxis Framework
Documentation structured as:
- **Tutorials**: Learning-oriented (getting started)
- **How-To Guides**: Problem-solving (specific tasks)
- **Reference**: Information-oriented (API docs)
- **Explanation**: Understanding-oriented (architecture, concepts)
Tools like `setup-structure` and `populate-content` enforce this structure.
## Critical Implementation Details
### Phase 3: AST-Based Code Analysis
- **AST Parser** (`src/utils/ast-analyzer.ts`): Multi-language support via Tree-sitter (TypeScript, Python, Go, Rust, Java, Ruby, Bash)
- **Drift Detection** (`src/utils/drift-detector.ts`): Snapshot-based comparison, categorizes changes (breaking/major/minor/patch)
- **Sync Tool** (`src/tools/sync-code-to-docs.ts`): Modes: detect/preview/apply/auto, confidence threshold (default 0.8)
- **Content Generator** (`src/tools/generate-contextual-content.ts`): Creates Diataxis-compliant docs from actual code structure
**Example drift detection**:
```typescript
// Detects function signature changes, new/removed classes, breaking changes
const drift = await detectDrift({ projectPath, docsPath, snapshotDir });
drift.affectedFiles.forEach((f) => console.log(f.driftType, f.severity));
```
### Error Handling Pattern
```typescript
try {
// tool logic
return formatMCPResponse({ success: true, data: result, metadata });
} catch (error) {
return formatMCPResponse({
success: false,
error: {
code: "TOOL_ERROR",
message: error.message,
resolution: "Check inputs and try again",
},
metadata,
});
}
```
### Adding New Tools Checklist
1. Create `src/tools/my-tool.ts` with Zod schema and logic
2. Export tool function returning `MCPToolResponse`
3. Add to `TOOLS` array in `src/index.ts` with name, description, inputSchema
4. Add handler in `CallToolRequestSchema` switch case
5. Store result: `storeResourceFromToolResult(result, 'type', id)`
6. Create tests: `tests/tools/my-tool.test.ts`
7. Run `npm run ci` to validate
## Key Integration Points
### Memory Query Patterns
```typescript
// Query similar projects by analysis results
const similar = await getSimilarProjects(analysisResult, limit);
// Get project insights with historical context
const insights = await getProjectInsights(projectPath);
// Export/import memory for backup
await exportMemories({ outputPath: "./backup.json" });
await importMemories({ inputPath: "./backup.json" });
```
### Resource Storage Pattern
```typescript
// Tools create resources for cross-tool reference
resourceStore.set(`documcp://analysis/${id}`, {
uri: `documcp://analysis/${id}`,
name: `Repository Analysis ${id}`,
mimeType: "application/json",
text: JSON.stringify(result, null, 2),
});
```
### ADR References
- **ADR-001**: TypeScript MCP SDK chosen over Python/Go for ecosystem maturity
- **ADR-002**: Multi-layered repository analysis (structure + dependencies + documentation)
- **ADR-003**: Algorithmic SSG recommendation with confidence scoring
- **ADR-006**: Consistent tool API design with Zod validation
- **ADR-010**: Resource pattern redesign for session-based storage
Full ADRs: `docs/adrs/`
## Common Pitfalls
- ❌ Forgetting `.js` extension in imports → Module resolution fails
- ❌ Using `formatMCPResponse()` incorrectly → Response validation errors
- ❌ Missing Zod schema validation → Runtime type errors
- ❌ Synchronous file operations → Blocking operations
- ❌ Not checking path permissions → Security vulnerabilities
- ❌ Returning raw objects instead of `MCPToolResponse` → Protocol violations
## Quick Reference
**Node.js**: ≥20.0.0 | **Module**: ESM | **Test Framework**: Jest (ts-jest) | **Storage**: `.documcp/memory/` (JSONL)
**MCP Inspector**: `npm run build:inspect` | **Full test suite**: `npm run ci` | **Docs**: `docs/` (Docusaurus)
```
--------------------------------------------------------------------------------
/docs/guides/link-validation.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.947Z"
last_validated: "2025-12-09T19:41:38.578Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# Link Validation in Knowledge Graph
## Overview
DocuMCP now includes automatic link validation for documentation content, integrated directly into the Knowledge Graph memory system. This feature validates external links, tracks their status over time, and surfaces broken links during repository analysis.
## Architecture
### Components
1. **kg-link-validator.ts** - Core link validation module
2. **kg-code-integration.ts** - Automatic validation during doc analysis
3. **Knowledge Graph** - Stores validation results as entities
### Entity Type: `link_validation`
```typescript
{
totalLinks: number; // Total links found
validLinks: number; // Links that returned HTTP 200
brokenLinks: number; // Links that failed (404, timeout, etc.)
warningLinks: number; // Links that were skipped
unknownLinks: number; // Links that couldn't be validated
healthScore: number; // 0-100 score based on valid/total
lastValidated: string; // ISO 8601 timestamp
brokenLinksList: string[]; // Array of broken link URLs
}
```
### Relationships
1. **has_link_validation**: `documentation_section` → `link_validation`
- Connects docs to their validation results
2. **requires_fix**: `link_validation` → `documentation_section`
- Created when broken links are detected
- Properties:
- `severity`: "high" (>5 broken) or "medium" (1-5 broken)
- `brokenLinkCount`: Number of broken links
- `detectedAt`: ISO timestamp
## How It Works
### 1. Automatic Validation During Analysis
When `analyze_repository` runs, it:
1. Extracts documentation content
2. Creates documentation entities in KG
3. **Automatically validates external links** (async, non-blocking)
4. Stores validation results in KG
```typescript
// In kg-code-integration.ts
for (const doc of extractedContent.existingDocs) {
const docNode = createDocSectionEntity(
projectId,
doc.path,
doc.title,
doc.content,
);
kg.addNode(docNode);
// Validate links in background
validateAndStoreDocumentationLinks(docNode.id, doc.content).catch((error) =>
console.warn(`Failed to validate links: ${error.message}`),
);
}
```
### 2. Link Extraction
Supports both Markdown and HTML formats:
```markdown
<!-- Markdown links -->
[GitHub](https://github.com)
<!-- HTML links -->
<a href="https://example.com">Link</a>
```
### 3. Validation Strategy
Uses native Node.js `fetch` API with:
- **HTTP HEAD requests** (faster than GET)
- **5-second timeout** (configurable)
- **Retry logic** (2 retries by default)
- **Concurrent checking** (up to 10 simultaneous)
```typescript
const result = await validateExternalLinks(urls, {
timeout: 5000, // 5 seconds
retryCount: 2, // Retry failed links
concurrency: 10, // Check 10 links at once
});
```
### 4. Storage in Knowledge Graph
Results are stored as entities and can be queried:
```typescript
// Get validation history for a doc section
const history = await getLinkValidationHistory(docSectionId);
// Latest validation
const latest = history[0];
console.log(`Health Score: ${latest.properties.healthScore}%`);
console.log(`Broken Links: ${latest.properties.brokenLinks}`);
```
## Usage Examples
### Query Broken Links
```typescript
import { getKnowledgeGraph } from "./memory/kg-integration.js";
const kg = await getKnowledgeGraph();
// Find all link validation entities with broken links
const allNodes = await kg.getAllNodes();
const validations = allNodes.filter(
(n) => n.type === "link_validation" && n.properties.brokenLinks > 0,
);
validations.forEach((v) => {
console.log(`Found ${v.properties.brokenLinks} broken links:`);
v.properties.brokenLinksList.forEach((url) => console.log(` - ${url}`));
});
```
### Get Documentation Health Report
```typescript
import { getKnowledgeGraph } from "./memory/kg-integration.js";
const kg = await getKnowledgeGraph();
// Find all documentation sections
const docSections = (await kg.getAllNodes()).filter(
(n) => n.type === "documentation_section",
);
for (const doc of docSections) {
// Get validation results
const edges = await kg.findEdges({
source: doc.id,
type: "has_link_validation",
});
if (edges.length > 0) {
const validationId = edges[0].target;
const validation = (await kg.getAllNodes()).find(
(n) => n.id === validationId,
);
if (validation) {
console.log(`\n${doc.properties.filePath}:`);
console.log(` Health: ${validation.properties.healthScore}%`);
console.log(` Valid: ${validation.properties.validLinks}`);
console.log(` Broken: ${validation.properties.brokenLinks}`);
}
}
}
```
### Manual Validation
```typescript
import {
validateExternalLinks,
storeLinkValidationInKG,
} from "./memory/kg-link-validator.js";
// Validate specific URLs
const result = await validateExternalLinks([
"https://github.com",
"https://example.com/404",
]);
console.log(result);
// {
// totalLinks: 2,
// validLinks: 1,
// brokenLinks: 1,
// results: [...]
// }
// Store in KG
await storeLinkValidationInKG(docSectionId, result);
```
## Integration with analyze_repository
The `analyze_repository` tool now includes link validation data:
```json
{
"success": true,
"data": {
"intelligentAnalysis": {
"documentationHealth": {
"outdatedCount": 2,
"coveragePercent": 85,
"totalCodeFiles": 20,
"documentedFiles": 17,
"linkHealth": {
"totalLinks": 45,
"brokenLinks": 3,
"healthScore": 93
}
}
}
}
}
```
## Configuration
### Environment Variables
```bash
# Link validation timeout (milliseconds)
DOCUMCP_LINK_TIMEOUT=5000
# Maximum retries for failed links
DOCUMCP_LINK_RETRIES=2
# Concurrent link checks
DOCUMCP_LINK_CONCURRENCY=10
```
### Skip Link Validation
Link validation is non-blocking and runs in the background. If it fails, it logs a warning but doesn't stop the analysis.
## Performance Considerations
1. **Non-blocking**: Validation runs asynchronously after doc entities are created
2. **Cached Results**: Results stored in KG, no re-validation on subsequent reads
3. **Concurrent Checking**: Validates up to 10 links simultaneously
4. **Smart Timeouts**: 5-second timeout prevents hanging on slow servers
## Troubleshooting
### Links Not Being Validated
**Issue**: Documentation sections have no validation results
**Check**:
1. Are there external links in the content?
2. Check console for warnings: `Failed to validate links in...`
3. Verify network connectivity
### False Positives
**Issue**: Valid links marked as broken
**Solutions**:
1. Increase timeout: Some servers respond slowly
2. Check if server blocks HEAD requests (rare)
3. Verify URL is publicly accessible (not behind auth)
### Memory Storage Not Updated
**Issue**: KG doesn't show validation results
**Check**:
```typescript
import { saveKnowledgeGraph } from "./memory/kg-integration.js";
// Manually save KG
await saveKnowledgeGraph();
```
## Future Enhancements
1. **AST-based Internal Link Validation**
- Verify internal file references exist
- Check anchor links (`#section-id`)
2. **Link Health Trends**
- Track link health over time
- Alert on degrading link quality
3. **Batch Re-validation**
- MCP tool to re-check all links
- Scheduled validation for long-lived projects
4. **Link Recommendation**
- Suggest fixing broken links
- Recommend archive.org alternatives for dead links
## Dependencies
- **linkinator** (v6.1.4) - Link validation library (installed)
- **native fetch** - Node.js 20+ built-in HTTP client
## Related Documentation
- [Architecture Decision Records](../adrs/)
- [Phase 2: Intelligence & Learning System](../phase-2-intelligence.md)
- [Memory Workflows Tutorial](../tutorials/memory-workflows.md)
```
--------------------------------------------------------------------------------
/src/memory/kg-link-validator.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Knowledge Graph Link Validator
* Validates external and internal links in documentation and stores results in KG
*/
import crypto from "crypto";
import { getKnowledgeGraph } from "./kg-integration.js";
import { GraphNode } from "./knowledge-graph.js";
export interface LinkValidationResult {
url: string;
status: "valid" | "broken" | "warning" | "unknown";
statusCode?: number;
errorMessage?: string;
lastChecked: string;
responseTime?: number;
}
export interface LinkValidationSummary {
totalLinks: number;
validLinks: number;
brokenLinks: number;
warningLinks: number;
unknownLinks: number;
results: LinkValidationResult[];
}
/**
* Validate external links in documentation content
*/
export async function validateExternalLinks(
urls: string[],
options?: {
timeout?: number;
retryCount?: number;
concurrency?: number;
},
): Promise<LinkValidationSummary> {
const _timeout = options?.timeout || 5000;
// const _retryCount = options?.retryCount || 2;
// const _concurrency = options?.concurrency || 10;
const results: LinkValidationResult[] = [];
const summary: LinkValidationSummary = {
totalLinks: urls.length,
validLinks: 0,
brokenLinks: 0,
warningLinks: 0,
unknownLinks: 0,
results: [],
};
// Create a temporary HTML file with all links to validate
// const _tempHtml = `<html><body>${urls
// .map((url) => `<a href="${url}">${url}</a>`)
// .join("\n")}</body></html>`;
try {
// Use individual validation for now (linkinator API is complex)
// TODO: Optimize with linkinator's batch checking in future
for (const url of urls) {
try {
const result = await validateSingleLink(url, _timeout);
results.push(result);
if (result.status === "valid") summary.validLinks++;
else if (result.status === "broken") summary.brokenLinks++;
else if (result.status === "warning") summary.warningLinks++;
else summary.unknownLinks++;
} catch {
results.push({
url,
status: "unknown",
errorMessage: "Validation failed",
lastChecked: new Date().toISOString(),
});
summary.unknownLinks++;
}
}
} catch (error) {
console.warn("Link validation error:", error);
}
summary.results = results;
return summary;
}
/**
* Validate a single link with HTTP HEAD request
*/
async function validateSingleLink(
url: string,
timeout: number,
): Promise<LinkValidationResult> {
const startTime = Date.now();
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await fetch(url, {
method: "HEAD",
signal: controller.signal,
redirect: "follow",
});
clearTimeout(timeoutId);
const responseTime = Date.now() - startTime;
if (response.ok) {
return {
url,
status: "valid",
statusCode: response.status,
lastChecked: new Date().toISOString(),
responseTime,
};
} else {
return {
url,
status: "broken",
statusCode: response.status,
errorMessage: `HTTP ${response.status}: ${response.statusText}`,
lastChecked: new Date().toISOString(),
responseTime,
};
}
} catch (error: any) {
return {
url,
status: "broken",
errorMessage: error.message || "Network error",
lastChecked: new Date().toISOString(),
};
}
}
/**
* Store link validation results in Knowledge Graph
*/
export async function storeLinkValidationInKG(
docSectionId: string,
validationSummary: LinkValidationSummary,
): Promise<void> {
const kg = await getKnowledgeGraph();
// Create link validation entity
const validationId = `link_validation:${crypto
.randomBytes(8)
.toString("hex")}`;
const validationNode: GraphNode = {
id: validationId,
type: "link_validation",
label: "Link Validation Result",
properties: {
totalLinks: validationSummary.totalLinks,
validLinks: validationSummary.validLinks,
brokenLinks: validationSummary.brokenLinks,
warningLinks: validationSummary.warningLinks,
unknownLinks: validationSummary.unknownLinks,
healthScore:
validationSummary.totalLinks > 0
? (validationSummary.validLinks / validationSummary.totalLinks) * 100
: 100,
lastValidated: new Date().toISOString(),
brokenLinksList: validationSummary.results
.filter((r) => r.status === "broken")
.map((r) => r.url),
},
weight: 1.0,
lastUpdated: new Date().toISOString(),
};
kg.addNode(validationNode);
// Create relationship: documentation_section -> link_validation
kg.addEdge({
source: docSectionId,
target: validationId,
type: "has_link_validation",
weight: 1.0,
confidence: 1.0,
properties: {
validatedAt: new Date().toISOString(),
hasBrokenLinks: validationSummary.brokenLinks > 0,
needsAttention: validationSummary.brokenLinks > 0,
},
});
// If there are broken links, create "requires_fix" edges
if (validationSummary.brokenLinks > 0) {
kg.addEdge({
source: validationId,
target: docSectionId,
type: "requires_fix",
weight: validationSummary.brokenLinks / validationSummary.totalLinks,
confidence: 1.0,
properties: {
severity: validationSummary.brokenLinks > 5 ? "high" : "medium",
brokenLinkCount: validationSummary.brokenLinks,
detectedAt: new Date().toISOString(),
},
});
}
}
/**
* Get link validation history from Knowledge Graph
*/
export async function getLinkValidationHistory(
docSectionId: string,
): Promise<GraphNode[]> {
const kg = await getKnowledgeGraph();
const edges = await kg.findEdges({
source: docSectionId,
type: "has_link_validation",
});
const validationNodes: GraphNode[] = [];
const allNodes = await kg.getAllNodes();
for (const edge of edges) {
const validationNode = allNodes.find((n) => n.id === edge.target);
if (validationNode) {
validationNodes.push(validationNode);
}
}
// Sort by lastValidated (newest first)
return validationNodes.sort(
(a, b) =>
new Date(b.properties.lastValidated).getTime() -
new Date(a.properties.lastValidated).getTime(),
);
}
/**
* Extract links from documentation content
*/
export function extractLinksFromContent(content: string): {
externalLinks: string[];
internalLinks: string[];
} {
const externalLinks: string[] = [];
const internalLinks: string[] = [];
// Markdown links: [text](url)
const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
let match;
while ((match = markdownLinkRegex.exec(content)) !== null) {
const url = match[2];
if (url.startsWith("http://") || url.startsWith("https://")) {
externalLinks.push(url);
} else {
internalLinks.push(url);
}
}
// HTML links: <a href="url">
const htmlLinkRegex = /<a\s+(?:[^>]*?\s+)?href=(["'])(.*?)\1/gi;
while ((match = htmlLinkRegex.exec(content)) !== null) {
const url = match[2];
if (url.startsWith("http://") || url.startsWith("https://")) {
externalLinks.push(url);
} else {
internalLinks.push(url);
}
}
return {
externalLinks: [...new Set(externalLinks)], // Remove duplicates
internalLinks: [...new Set(internalLinks)],
};
}
/**
* Validate all links in a documentation section and store in KG
*/
export async function validateAndStoreDocumentationLinks(
docSectionId: string,
content: string,
): Promise<LinkValidationSummary> {
const { externalLinks } = extractLinksFromContent(content);
if (externalLinks.length === 0) {
return {
totalLinks: 0,
validLinks: 0,
brokenLinks: 0,
warningLinks: 0,
unknownLinks: 0,
results: [],
};
}
const validationSummary = await validateExternalLinks(externalLinks);
await storeLinkValidationInKG(docSectionId, validationSummary);
return validationSummary;
}
```
--------------------------------------------------------------------------------
/docs/reference/cli.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.960Z"
last_validated: "2025-12-09T19:41:38.590Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# Command Line Interface
DocuMCP primarily operates as an MCP server integrated with AI assistants, but it also provides command-line utilities for direct usage and debugging.
## MCP Server Usage
The primary way to use DocuMCP is through MCP-compatible clients:
### Starting the MCP Server
```bash
# Using npx (recommended)
npx documcp
# Using global installation
documcp
# Using Node.js directly
node dist/index.js
```
### Server Information
```bash
# Check version
documcp --version
# Show help
documcp --help
# Debug mode
DEBUG=* documcp
```
## MCP Client Integration
### Claude Desktop Configuration
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"documcp": {
"command": "npx",
"args": ["documcp"],
"env": {
"DOCUMCP_STORAGE_DIR": "/path/to/storage"
}
}
}
}
```
### Environment Variables
| Variable | Description | Default |
| --------------------- | ------------------------ | ----------------- |
| `DOCUMCP_STORAGE_DIR` | Memory storage directory | `.documcp/memory` |
| `DEBUG` | Enable debug logging | `false` |
| `NODE_ENV` | Node.js environment | `development` |
## Development Commands
For development and testing:
### Build Commands
```bash
# Build TypeScript
npm run build
# Build in watch mode
npm run dev
# Type checking
npm run typecheck
```
### Testing Commands
```bash
# Run all tests
npm test
# Run tests with coverage
npm run test:coverage
# Run performance benchmarks
npm run test:performance
# CI test run
npm run test:ci
```
### Code Quality Commands
```bash
# Lint code
npm run lint
# Fix linting issues
npm run lint:fix
# Format code
npm run format
# Check formatting
npm run format:check
# Full validation
npm run validate:rules
```
### Documentation Commands
```bash
# Check documentation links
npm run docs:check-links
# Check external links
npm run docs:check-links:external
# Check internal links only
npm run docs:check-links:internal
# Validate documentation structure
npm run docs:validate
# Complete documentation test
npm run docs:test
```
### Security Commands
```bash
# Check for vulnerabilities
npm run security:check
# Audit dependencies
npm audit
# Fix security issues
npm audit fix
```
### Benchmark Commands
```bash
# Run performance benchmarks
npm run benchmark:run
# Show current performance metrics
npm run benchmark:current
# Create benchmark configuration
npm run benchmark:create-config
# Show benchmark help
npm run benchmark:help
```
## Tool Invocation via CLI
While DocuMCP is designed for MCP integration, you can test tools via Node.js:
### Direct Tool Testing
```javascript
// test-tool.js
import { analyzeRepository } from "./dist/tools/analyze-repository.js";
async function test() {
const result = await analyzeRepository({
path: process.cwd(),
depth: "standard",
});
console.log(JSON.stringify(result, null, 2));
}
test().catch(console.error);
```
```bash
# Run test
node test-tool.js
```
### Tool-Specific Examples
**Repository Analysis:**
```javascript
import { analyzeRepository } from "./dist/tools/analyze-repository.js";
const analysis = await analyzeRepository({
path: "/path/to/repository",
depth: "deep",
});
```
**SSG Recommendation:**
```javascript
import { recommendSSG } from "./dist/tools/recommend-ssg.js";
const recommendation = await recommendSSG({
analysisId: "analysis_12345",
preferences: {
ecosystem: "javascript",
priority: "features",
},
});
```
**Configuration Generation:**
```javascript
import { generateConfig } from "./dist/tools/generate-config.js";
const config = await generateConfig({
ssg: "docusaurus",
projectName: "My Project",
outputPath: "./docs",
});
```
## Debugging
### Debug Modes
Enable detailed logging:
```bash
# All debug info
DEBUG=* documcp
# Specific modules
DEBUG=documcp:* documcp
DEBUG=documcp:analysis documcp
DEBUG=documcp:memory documcp
```
### Log Levels
DocuMCP supports different log levels:
```bash
# Error only
NODE_ENV=production documcp
# Development (verbose)
NODE_ENV=development documcp
# Custom logging
DEBUG=documcp:error,documcp:warn documcp
```
### Performance Debugging
```bash
# Enable performance tracking
DEBUG=documcp:perf documcp
# Memory usage tracking
DEBUG=documcp:memory documcp
# Network requests
DEBUG=documcp:http documcp
```
## Configuration Files
### Project-level Configuration
Create `.documcprc.json` in your project:
```json
{
"storage": {
"directory": ".documcp/memory",
"maxEntries": 1000,
"cleanupDays": 30
},
"analysis": {
"defaultDepth": "standard",
"excludePatterns": ["node_modules", ".git", "dist"]
},
"deployment": {
"defaultBranch": "gh-pages",
"verifyDeployment": true
}
}
```
### Global Configuration
Create `~/.documcp/config.json`:
```json
{
"defaultPreferences": {
"ecosystem": "any",
"priority": "simplicity"
},
"github": {
"defaultOrg": "your-username"
},
"memory": {
"enableLearning": true,
"shareAnonymousData": false
}
}
```
## Exit Codes
DocuMCP uses standard exit codes:
| Code | Meaning |
| ---- | -------------------- |
| 0 | Success |
| 1 | General error |
| 2 | Invalid arguments |
| 3 | File system error |
| 4 | Network error |
| 5 | Configuration error |
| 6 | Tool execution error |
## Scripting and Automation
### Batch Operations
Create scripts for common workflows:
```bash
#!/bin/bash
# deploy-docs.sh
set -e
echo "Starting documentation deployment..."
# Test locally first
echo "Testing local build..."
npm run docs:validate
# Deploy via DocuMCP
echo "Analyzing repository..."
# Trigger MCP analysis through your client
echo "Deployment complete!"
```
### CI/CD Integration
DocuMCP can be used in CI/CD pipelines:
```yaml
# .github/workflows/docs.yml
name: Documentation
on:
push:
branches: [main]
paths: ["docs/**", "*.md"]
jobs:
deploy-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install DocuMCP
run: npm install -g documcp
- name: Validate documentation
run: |
# Use DocuMCP validation tools
npm run docs:validate
```
### Programmatic Usage
For advanced integration:
```javascript
// integration.js
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { analyzeRepository } from "./dist/tools/analyze-repository.js";
import { recommendSSG } from "./dist/tools/recommend-ssg.js";
import { deployPages } from "./dist/tools/deploy-pages.js";
class DocuMCPIntegration {
async deployDocumentation(repoPath) {
// Analyze
const analysis = await analyzeRepository({
path: repoPath,
depth: "standard",
});
// Get recommendation
const recommendation = await recommendSSG({
analysisId: analysis.id,
});
// Deploy
const deployment = await deployPages({
repository: repoPath,
ssg: recommendation.recommended,
});
return { analysis, recommendation, deployment };
}
}
```
## Troubleshooting CLI Issues
### Common Problems
**Command not found:**
```bash
# Check installation
which documcp
npm list -g documcp
# Reinstall if needed
npm uninstall -g documcp
npm install -g documcp
```
**Permission errors:**
```bash
# Check permissions
ls -la $(which documcp)
# Fix permissions
chmod +x $(which documcp)
```
**Module resolution errors:**
```bash
# Clear npm cache
npm cache clean --force
# Rebuild
npm run build
```
### Getting Help
```bash
# Show help
documcp --help
# Show version
documcp --version
# Contact support
echo "Report issues: https://github.com/tosin2013/documcp/issues"
```
For more detailed troubleshooting, see the [Troubleshooting Guide](../how-to/troubleshooting.md).
```
--------------------------------------------------------------------------------
/docs/tutorials/first-deployment.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.971Z"
last_validated: "2025-12-09T19:41:38.603Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# Your First Documentation Deployment
This tutorial walks you through deploying your first documentation site using DocuMCP, from analysis to live GitHub Pages deployment.
## What You'll Build
By the end of this tutorial, you'll have:
- A live documentation site on GitHub Pages
- Automated deployment workflow
- Professional Diataxis-structured content
- Understanding of DocuMCP's deployment process
## Prerequisites
- Completed the [Getting Started](getting-started.md) tutorial
- GitHub repository with your code
- Write access to the repository
- GitHub Pages enabled in repository settings
## Step-by-Step Deployment
### Step 1: Complete Repository Analysis
If you haven't already, analyze your repository:
```bash
# Prompt to DocuMCP:
"analyze my repository for documentation deployment"
```
Expected output includes analysis ID (e.g., `analysis_xyz789`) that we'll use throughout the deployment.
### Step 2: Get Deployment-Optimized Recommendations
Request recommendations specifically for deployment:
```bash
# Prompt:
"recommend the best static site generator for GitHub Pages deployment based on analysis_xyz789"
```
DocuMCP will consider:
- **GitHub Pages compatibility** (native Jekyll support vs. Actions required)
- **Build time** (Hugo's speed vs. Docusaurus features)
- **Maintenance overhead** (MkDocs simplicity vs. Eleventy flexibility)
### Step 3: Generate Deployment Configuration
Create production-ready configuration:
```bash
# For example, if Docusaurus was recommended:
"generate Docusaurus configuration for production deployment to GitHub Pages"
```
This creates:
- **docusaurus.config.js**: Optimized for GitHub Pages
- **package.json updates**: Required dependencies
- **Build scripts**: Production build configuration
### Step 4: Set Up Documentation Structure
Create comprehensive documentation structure:
```bash
# Prompt:
"set up Diataxis documentation structure for Docusaurus deployment"
```
Creates organized folders:
```
docs/
├── tutorials/ # Learning-oriented
├── how-to-guides/ # Problem-solving
├── reference/ # Information-oriented
├── explanation/ # Understanding-oriented
└── index.md # Landing page
```
### Step 5: Populate with Initial Content
Generate starter content based on your project:
```bash
# Prompt:
"populate the documentation structure with content based on my project analysis"
```
DocuMCP creates:
- **Project-specific tutorials** based on your codebase
- **API documentation** extracted from your code
- **Installation guides** tailored to your tech stack
- **Configuration examples** using your actual project structure
### Step 6: Deploy to GitHub Pages
Set up automated deployment:
```bash
# Prompt:
"deploy my Docusaurus documentation to GitHub Pages with automated workflow"
```
This generates:
- **.github/workflows/deploy.yml**: GitHub Actions workflow
- **Optimized build process**: Cached dependencies, parallel builds
- **Security configuration**: OIDC tokens, minimal permissions
### Step 7: Verify Deployment
Check that everything is working:
```bash
# Prompt:
"verify my GitHub Pages deployment is working correctly"
```
DocuMCP checks:
- **Workflow status**: Build and deployment success
- **Site accessibility**: Homepage loads correctly
- **Navigation**: All sections are reachable
- **Asset loading**: CSS, JS, images work properly
## Example: Complete TypeScript Library Deployment
Here's a real example for a TypeScript library:
### 1. Analysis Results
```json
{
"id": "analysis_ts_lib_001",
"structure": {
"totalFiles": 47,
"languages": { ".ts": 32, ".md": 5, ".json": 3 },
"hasTests": true,
"hasCI": true
},
"recommendations": {
"primaryLanguage": "typescript",
"projectType": "library"
}
}
```
### 2. Recommendation
```json
{
"recommended": "docusaurus",
"confidence": 0.88,
"reasoning": [
"TypeScript ecosystem alignment",
"Excellent API documentation support",
"React component integration for examples"
]
}
```
### 3. Generated Configuration
**docusaurus.config.js**:
```javascript
const config = {
title: "TypeScript Library Docs",
tagline: "Comprehensive API documentation",
url: "https://yourusername.github.io",
baseUrl: "/your-repo-name/",
// GitHub Pages deployment config
organizationName: "yourusername",
projectName: "your-repo-name",
deploymentBranch: "gh-pages",
trailingSlash: false,
presets: [
[
"classic",
{
docs: {
routeBasePath: "/",
sidebarPath: require.resolve("./sidebars.js"),
},
theme: {
customCss: require.resolve("./src/css/custom.css"),
},
},
],
],
};
```
### 4. GitHub Actions Workflow
**.github/workflows/deploy.yml**:
```yaml
name: Deploy Documentation
on:
push:
branches: [main]
paths: ["docs/**", "docusaurus.config.js"]
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build documentation
run: npm run build
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
path: "./build"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
```
## Verification Checklist
After deployment, verify:
- [ ] **Site is live** at `https://yourusername.github.io/repository-name`
- [ ] **All sections load** (Tutorials, How-to, Reference, Explanation)
- [ ] **Search works** (if enabled)
- [ ] **Mobile responsive** design
- [ ] **Fast loading** (check Core Web Vitals)
- [ ] **SEO optimized** (meta tags, sitemap)
## Common Deployment Issues
### Build Failures
**Problem**: Workflow fails during build
**Solution**:
- Check Node.js version compatibility
- Verify all dependencies are in package.json
- Review build logs in Actions tab
### Page Not Found (404)
**Problem**: Site shows 404 error
**Solution**:
- Verify `baseUrl` in config matches repository name
- Check GitHub Pages source is set to GitHub Actions
- Confirm deployment branch exists
### Assets Not Loading
**Problem**: CSS/JS files return 404
**Solution**:
- Ensure `publicPath` is configured correctly
- Check trailing slash configuration
- Verify asset paths are relative
## Performance Optimization
### Build Speed
- **Caching**: Enable npm cache in GitHub Actions
- **Parallel builds**: Use appropriate number of workers
- **Incremental builds**: Only rebuild changed files
### Site Performance
- **Image optimization**: Compress and use modern formats
- **Code splitting**: Load only necessary JavaScript
- **CDN integration**: Use GitHub's CDN for assets
## Next Steps
Now that you have a deployed documentation site:
1. **[Set up development workflow](development-setup.md)** for ongoing maintenance
2. **[Configure custom domain](../how-to/custom-domains.md)** (optional)
3. **[Set up monitoring](../how-to/site-monitoring.md)** for uptime tracking
4. **[Optimize for search](../how-to/seo-optimization.md)** engines
## Summary
You've successfully:
✅ Analyzed your repository for deployment
✅ Generated production-ready configuration
✅ Set up professional documentation structure
✅ Deployed to GitHub Pages with automation
✅ Verified your live documentation site
Your documentation is now live and will automatically update with each commit!
## Troubleshooting
If you encounter issues:
1. Check the [troubleshooting guide](../how-to/troubleshooting.md)
2. Review GitHub Actions logs
3. Verify repository permissions
4. Confirm GitHub Pages settings
Need help? Open an issue on the [DocuMCP repository](https://github.com/tosin2013/documcp/issues).
```
--------------------------------------------------------------------------------
/src/tools/setup-playwright-tests.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Setup Playwright E2E Tests Tool
* Generates Playwright test configuration and files for user's documentation site
*/
import { promises as fs } from "fs";
import path from "path";
import { z } from "zod";
// Return type matches MCP tool response format
type ToolResponse = {
content: Array<{ type: "text"; text: string }>;
isError?: boolean;
};
import { fileURLToPath } from "url";
import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const inputSchema = z.object({
repositoryPath: z.string().describe("Path to the documentation repository"),
ssg: z.enum(["jekyll", "hugo", "docusaurus", "mkdocs", "eleventy"]),
projectName: z.string().describe("Project name for tests"),
mainBranch: z.string().optional().default("main"),
includeAccessibilityTests: z.boolean().optional().default(true),
includeDockerfile: z.boolean().optional().default(true),
includeGitHubActions: z.boolean().optional().default(true),
});
interface SSGConfig {
buildCommand: string;
buildDir: string;
port: number;
packageDeps: Record<string, string>;
}
const SSG_CONFIGS: Record<string, SSGConfig> = {
jekyll: {
buildCommand: "bundle exec jekyll build",
buildDir: "_site",
port: 4000,
packageDeps: {},
},
hugo: {
buildCommand: "hugo",
buildDir: "public",
port: 1313,
packageDeps: {},
},
docusaurus: {
buildCommand: "npm run build",
buildDir: "build",
port: 3000,
packageDeps: {
"@docusaurus/core": "^3.0.0",
"@docusaurus/preset-classic": "^3.0.0",
},
},
mkdocs: {
buildCommand: "mkdocs build",
buildDir: "site",
port: 8000,
packageDeps: {},
},
eleventy: {
buildCommand: "npx @11ty/eleventy",
buildDir: "_site",
port: 8080,
packageDeps: {
"@11ty/eleventy": "^2.0.0",
},
},
};
export async function setupPlaywrightTests(
args: unknown,
): Promise<ToolResponse> {
const {
repositoryPath,
ssg,
projectName,
mainBranch,
includeAccessibilityTests,
includeDockerfile,
includeGitHubActions,
} = inputSchema.parse(args);
try {
const config = SSG_CONFIGS[ssg];
const templatesDir = path.join(__dirname, "../templates/playwright");
// Create directories
const testsDir = path.join(repositoryPath, "tests/e2e");
await fs.mkdir(testsDir, { recursive: true });
if (includeGitHubActions) {
const workflowsDir = path.join(repositoryPath, ".github/workflows");
await fs.mkdir(workflowsDir, { recursive: true });
}
// Read and process templates
const filesCreated: string[] = [];
// 1. Playwright config
const configTemplate = await fs.readFile(
path.join(templatesDir, "playwright.config.template.ts"),
"utf-8",
);
const playwrightConfig = configTemplate.replace(
/{{port}}/g,
config.port.toString(),
);
await fs.writeFile(
path.join(repositoryPath, "playwright.config.ts"),
playwrightConfig,
);
filesCreated.push("playwright.config.ts");
// 2. Link validation tests
const linkTestTemplate = await fs.readFile(
path.join(templatesDir, "link-validation.spec.template.ts"),
"utf-8",
);
const linkTest = linkTestTemplate.replace(/{{projectName}}/g, projectName);
await fs.writeFile(
path.join(testsDir, "link-validation.spec.ts"),
linkTest,
);
filesCreated.push("tests/e2e/link-validation.spec.ts");
// 3. Accessibility tests (if enabled)
if (includeAccessibilityTests) {
const a11yTemplate = await fs.readFile(
path.join(templatesDir, "accessibility.spec.template.ts"),
"utf-8",
);
await fs.writeFile(
path.join(testsDir, "accessibility.spec.ts"),
a11yTemplate,
);
filesCreated.push("tests/e2e/accessibility.spec.ts");
}
// 4. Dockerfile (if enabled)
if (includeDockerfile) {
const dockerTemplate = await fs.readFile(
path.join(templatesDir, "Dockerfile.template"),
"utf-8",
);
const dockerfile = dockerTemplate
.replace(/{{ssg}}/g, ssg)
.replace(/{{buildCommand}}/g, config.buildCommand)
.replace(/{{buildDir}}/g, config.buildDir);
await fs.writeFile(
path.join(repositoryPath, "Dockerfile.playwright"),
dockerfile,
);
filesCreated.push("Dockerfile.playwright");
}
// 5. GitHub Actions workflow (if enabled)
if (includeGitHubActions) {
const workflowTemplate = await fs.readFile(
path.join(templatesDir, "docs-e2e.workflow.template.yml"),
"utf-8",
);
const workflow = workflowTemplate
.replace(/{{mainBranch}}/g, mainBranch)
.replace(/{{buildCommand}}/g, config.buildCommand)
.replace(/{{buildDir}}/g, config.buildDir)
.replace(/{{port}}/g, config.port.toString());
await fs.writeFile(
path.join(repositoryPath, ".github/workflows/docs-e2e-tests.yml"),
workflow,
);
filesCreated.push(".github/workflows/docs-e2e-tests.yml");
}
// 6. Update package.json
const packageJsonPath = path.join(repositoryPath, "package.json");
let packageJson: any = {};
try {
const existing = await fs.readFile(packageJsonPath, "utf-8");
packageJson = JSON.parse(existing);
} catch {
// Create new package.json
packageJson = {
name: projectName.toLowerCase().replace(/\s+/g, "-"),
version: "1.0.0",
private: true,
scripts: {},
dependencies: {},
devDependencies: {},
};
}
// Add Playwright dependencies
packageJson.devDependencies = {
...packageJson.devDependencies,
"@playwright/test": "^1.55.1",
...(includeAccessibilityTests
? { "@axe-core/playwright": "^4.10.2" }
: {}),
};
// Add test scripts
packageJson.scripts = {
...packageJson.scripts,
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:report": "playwright show-report",
"test:e2e:docker":
"docker build -t docs-test -f Dockerfile.playwright . && docker run --rm docs-test",
};
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
filesCreated.push("package.json (updated)");
// 7. Create .gitignore entries
const gitignorePath = path.join(repositoryPath, ".gitignore");
const gitignoreEntries = [
"test-results/",
"playwright-report/",
"playwright-results.json",
"playwright/.cache/",
].join("\n");
try {
const existing = await fs.readFile(gitignorePath, "utf-8");
if (!existing.includes("test-results/")) {
await fs.writeFile(
gitignorePath,
`${existing}\n\n# Playwright\n${gitignoreEntries}\n`,
);
filesCreated.push(".gitignore (updated)");
}
} catch {
await fs.writeFile(gitignorePath, `# Playwright\n${gitignoreEntries}\n`);
filesCreated.push(".gitignore");
}
return {
content: [
{
type: "text" as const,
text: JSON.stringify(
{
success: true,
filesCreated,
nextSteps: [
"Run `npm install` to install Playwright dependencies",
"Run `npx playwright install` to download browser binaries",
"Test locally: `npm run test:e2e`",
includeDockerfile
? "Build container: `docker build -t docs-test -f Dockerfile.playwright .`"
: "",
includeGitHubActions
? "Push to trigger GitHub Actions workflow"
: "",
].filter(Boolean),
configuration: {
ssg,
buildCommand: config.buildCommand,
buildDir: config.buildDir,
port: config.port,
testsIncluded: {
linkValidation: true,
accessibility: includeAccessibilityTests,
},
integrations: {
docker: includeDockerfile,
githubActions: includeGitHubActions,
},
},
},
null,
2,
),
},
],
};
} catch (error: any) {
return {
content: [
{
type: "text" as const,
text: JSON.stringify(
{
success: false,
error: error.message,
},
null,
2,
),
},
],
isError: true,
};
}
}
```
--------------------------------------------------------------------------------
/src/tools/kg-health-check.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Knowledge Graph Health Check Tool
* MCP tool for checking knowledge graph health and getting recommendations
*/
import { z } from "zod";
import { MCPToolResponse, formatMCPResponse } from "../types/api.js";
import { getKnowledgeGraph, getKGStorage } from "../memory/kg-integration.js";
import { KGHealthMonitor, KGHealthMetrics } from "../memory/kg-health.js";
const inputSchema = z.object({
includeHistory: z.boolean().optional().default(false),
generateReport: z.boolean().optional().default(true),
days: z.number().min(1).max(90).optional().default(7),
});
/**
* Check the health of the knowledge graph
*
* Performs comprehensive health analysis including data quality, structure health,
* performance metrics, issue detection, and trend analysis.
*
* @param args - The input arguments
* @param args.includeHistory - Include historical health trend data
* @param args.generateReport - Generate a formatted health report
* @param args.days - Number of days of history to include (1-90)
*
* @returns Health metrics with recommendations
*
* @example
* ```typescript
* const result = await checkKGHealth({
* includeHistory: true,
* generateReport: true,
* days: 7
* });
* ```
*/
export async function checkKGHealth(
args: unknown,
): Promise<{ content: any[]; isError?: boolean }> {
const startTime = Date.now();
try {
const { includeHistory, generateReport } = inputSchema.parse(args);
// Get KG instances
const kg = await getKnowledgeGraph();
const storage = await getKGStorage();
// Create health monitor
const monitor = new KGHealthMonitor();
// Calculate health
const health = await monitor.calculateHealth(kg, storage);
// Generate report if requested
let report = "";
if (generateReport) {
report = generateHealthReport(health, includeHistory);
}
const response: MCPToolResponse<KGHealthMetrics> = {
success: true,
data: health,
metadata: {
toolVersion: "1.0.0",
executionTime: Date.now() - startTime,
timestamp: new Date().toISOString(),
},
recommendations: health.recommendations.map((rec) => ({
type: rec.priority === "high" ? "warning" : "info",
title: rec.action,
description: `Expected impact: +${rec.expectedImpact} health score | Effort: ${rec.effort}`,
})),
nextSteps: [
{
action: "Apply Recommendations",
toolRequired: "manual",
description:
"Implement high-priority recommendations to improve health",
priority: "high",
},
...(health.issues.filter((i) => i.severity === "critical").length > 0
? [
{
action: "Fix Critical Issues",
toolRequired: "manual" as const,
description: "Address critical issues immediately",
priority: "high" as const,
},
]
: []),
],
};
if (generateReport) {
// Add report as additional content
return {
content: [
...formatMCPResponse(response).content,
{
type: "text",
text: report,
},
],
};
}
return formatMCPResponse(response);
} catch (error) {
const errorResponse: MCPToolResponse = {
success: false,
error: {
code: "HEALTH_CHECK_FAILED",
message: `Failed to check KG health: ${error}`,
resolution: "Ensure the knowledge graph is properly initialized",
},
metadata: {
toolVersion: "1.0.0",
executionTime: Date.now() - startTime,
timestamp: new Date().toISOString(),
},
};
return formatMCPResponse(errorResponse);
}
}
/**
* Generate a human-readable health report
*/
function generateHealthReport(
health: KGHealthMetrics,
includeHistory: boolean,
): string {
const lines: string[] = [];
// Header
lines.push("═══════════════════════════════════════════════════════");
lines.push(" KNOWLEDGE GRAPH HEALTH REPORT");
lines.push("═══════════════════════════════════════════════════════");
lines.push("");
// Overall Health
lines.push(
`📊 OVERALL HEALTH: ${health.overallHealth}/100 ${getHealthEmoji(
health.overallHealth,
)}`,
);
lines.push(
` Trend: ${health.trends.healthTrend.toUpperCase()} ${getTrendEmoji(
health.trends.healthTrend,
)}`,
);
lines.push("");
// Component Scores
lines.push("Component Scores:");
lines.push(
` • Data Quality: ${health.dataQuality.score}/100 ${getHealthEmoji(
health.dataQuality.score,
)}`,
);
lines.push(
` • Structure Health: ${health.structureHealth.score}/100 ${getHealthEmoji(
health.structureHealth.score,
)}`,
);
lines.push(
` • Performance: ${health.performance.score}/100 ${getHealthEmoji(
health.performance.score,
)}`,
);
lines.push("");
// Graph Statistics
lines.push("Graph Statistics:");
lines.push(` • Total Nodes: ${health.dataQuality.totalNodes}`);
lines.push(` • Total Edges: ${health.dataQuality.totalEdges}`);
lines.push(
` • Avg Connectivity: ${health.structureHealth.densityScore.toFixed(3)}`,
);
lines.push(
` • Storage Size: ${formatBytes(health.performance.storageSize)}`,
);
lines.push("");
// Data Quality Details
if (health.dataQuality.score < 90) {
lines.push("⚠️ Data Quality Issues:");
if (health.dataQuality.staleNodeCount > 0) {
lines.push(
` • ${health.dataQuality.staleNodeCount} stale nodes (>30 days old)`,
);
}
if (health.dataQuality.orphanedEdgeCount > 0) {
lines.push(` • ${health.dataQuality.orphanedEdgeCount} orphaned edges`);
}
if (health.dataQuality.duplicateCount > 0) {
lines.push(` • ${health.dataQuality.duplicateCount} duplicate entities`);
}
if (health.dataQuality.completenessScore < 0.8) {
lines.push(
` • Completeness: ${Math.round(
health.dataQuality.completenessScore * 100,
)}%`,
);
}
lines.push("");
}
// Critical Issues
const criticalIssues = health.issues.filter((i) => i.severity === "critical");
const highIssues = health.issues.filter((i) => i.severity === "high");
if (criticalIssues.length > 0 || highIssues.length > 0) {
lines.push("🚨 CRITICAL & HIGH PRIORITY ISSUES:");
for (const issue of [...criticalIssues, ...highIssues].slice(0, 5)) {
lines.push(` [${issue.severity.toUpperCase()}] ${issue.description}`);
lines.push(` → ${issue.remediation}`);
}
lines.push("");
}
// Top Recommendations
if (health.recommendations.length > 0) {
lines.push("💡 TOP RECOMMENDATIONS:");
for (const rec of health.recommendations.slice(0, 5)) {
lines.push(` ${getPriorityIcon(rec.priority)} ${rec.action}`);
lines.push(` Impact: +${rec.expectedImpact} | Effort: ${rec.effort}`);
}
lines.push("");
}
// Trends
if (includeHistory) {
lines.push("📈 TRENDS (Last 7 Days):");
lines.push(
` • Health: ${health.trends.healthTrend} ${getTrendEmoji(
health.trends.healthTrend,
)}`,
);
lines.push(
` • Quality: ${health.trends.qualityTrend} ${getTrendEmoji(
health.trends.qualityTrend,
)}`,
);
lines.push(
` • Node Growth: ${health.trends.nodeGrowthRate.toFixed(1)} nodes/day`,
);
lines.push(
` • Edge Growth: ${health.trends.edgeGrowthRate.toFixed(1)} edges/day`,
);
lines.push("");
}
// Footer
lines.push("═══════════════════════════════════════════════════════");
lines.push(
`Report generated: ${new Date(health.timestamp).toLocaleString()}`,
);
lines.push("═══════════════════════════════════════════════════════");
return lines.join("\n");
}
// Helper functions
function getHealthEmoji(score: number): string {
if (score >= 90) return "🟢 Excellent";
if (score >= 75) return "🟡 Good";
if (score >= 60) return "🟠 Fair";
return "🔴 Poor";
}
function getTrendEmoji(trend: string): string {
if (trend === "improving") return "📈";
if (trend === "degrading") return "📉";
return "➡️";
}
function getPriorityIcon(priority: string): string {
if (priority === "high") return "🔴";
if (priority === "medium") return "🟡";
return "🟢";
}
function formatBytes(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024)
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
```
--------------------------------------------------------------------------------
/tests/tools/kg-health-check.test.ts:
--------------------------------------------------------------------------------
```typescript
import { promises as fs } from "fs";
import path from "path";
import os from "os";
import { checkKGHealth } from "../../src/tools/kg-health-check";
import {
getKnowledgeGraph,
getKGStorage,
createOrUpdateProject,
} from "../../src/memory/kg-integration";
describe("KG Health Check Tool", () => {
let tempDir: string;
const originalCwd = process.cwd();
beforeEach(async () => {
tempDir = path.join(os.tmpdir(), `kg-health-${Date.now()}`);
await fs.mkdir(tempDir, { recursive: true });
process.chdir(tempDir);
});
afterEach(async () => {
process.chdir(originalCwd);
try {
await fs.rm(tempDir, { recursive: true, force: true });
} catch {
// Ignore cleanup errors
}
});
it("should perform basic health check", async () => {
const result = await checkKGHealth({
includeHistory: false,
generateReport: false,
});
expect(result.content).toBeDefined();
expect(result.content.length).toBeGreaterThan(0);
// Should contain health metrics
const text = result.content.map((c) => c.text).join(" ");
expect(text).toContain("Health");
});
it("should include historical data when requested", async () => {
const result = await checkKGHealth({
includeHistory: true,
generateReport: false,
days: 7,
});
expect(result.content).toBeDefined();
const text = result.content.map((c) => c.text).join(" ");
expect(text).toContain("Health");
});
it("should generate detailed report", async () => {
const result = await checkKGHealth({
includeHistory: false,
generateReport: true,
});
expect(result.content).toBeDefined();
expect(result.content.length).toBeGreaterThan(0);
// Report should contain detailed metrics
const text = result.content.map((c) => c.text).join(" ");
expect(text).toContain("Health");
});
it("should generate report with history included", async () => {
const result = await checkKGHealth({
includeHistory: true,
generateReport: true,
days: 14,
});
expect(result.content).toBeDefined();
expect(result.content.length).toBeGreaterThan(1); // Should have formatted response + report
const text = result.content.map((c) => c.text).join(" ");
expect(text).toContain("Health");
expect(text).toContain("TRENDS");
});
it("should handle errors gracefully", async () => {
// Test with invalid parameters
const result = await checkKGHealth({
includeHistory: true,
generateReport: true,
days: -1, // Invalid
});
// Should either handle gracefully or return error
expect(result.content).toBeDefined();
});
it("should calculate health score", async () => {
const result = await checkKGHealth({
includeHistory: false,
generateReport: true,
});
expect(result.content).toBeDefined();
const text = result.content.map((c) => c.text).join(" ");
// Should contain some health indicator
expect(text.length).toBeGreaterThan(0);
});
it("should include critical issues in next steps", async () => {
// Create a project with some data to trigger health calculation
const kg = await getKnowledgeGraph();
// Add some nodes and edges to test health calculation
kg.addNode({
id: "test-node-1",
type: "project",
label: "Test Project",
properties: { name: "test" },
weight: 1.0,
});
const result = await checkKGHealth({
includeHistory: false,
generateReport: true,
});
expect(result.content).toBeDefined();
// Check that the response structure is correct
const text = result.content.map((c) => c.text).join(" ");
expect(text).toBeTruthy();
});
it("should handle graph with high data quality score", async () => {
const result = await checkKGHealth({
includeHistory: false,
generateReport: true,
});
expect(result.content).toBeDefined();
const text = result.content.map((c) => c.text).join(" ");
// Should complete without errors
expect(text.length).toBeGreaterThan(0);
});
it("should use default values when parameters not provided", async () => {
const result = await checkKGHealth({});
expect(result.content).toBeDefined();
expect(result.content.length).toBeGreaterThan(0);
});
it("should handle various health score ranges in report", async () => {
// Test the helper functions indirectly through the report
const result = await checkKGHealth({
includeHistory: false,
generateReport: true,
});
expect(result.content).toBeDefined();
const text = result.content.map((c) => c.text).join(" ");
// Should contain health indicators (emojis or text)
expect(text.length).toBeGreaterThan(0);
});
it("should handle different trend directions in report", async () => {
const result = await checkKGHealth({
includeHistory: true,
generateReport: true,
});
expect(result.content).toBeDefined();
const text = result.content.map((c) => c.text).join(" ");
// Report should include trend information
expect(text).toContain("TRENDS");
});
it("should handle different priority levels in recommendations", async () => {
const result = await checkKGHealth({
includeHistory: false,
generateReport: true,
});
expect(result.content).toBeDefined();
const text = result.content.map((c) => c.text).join(" ");
// Should complete without errors
expect(text.length).toBeGreaterThan(0);
});
it("should handle different byte sizes in formatBytes", async () => {
const result = await checkKGHealth({
includeHistory: false,
generateReport: true,
});
expect(result.content).toBeDefined();
const text = result.content.map((c) => c.text).join(" ");
// Report should include storage size
expect(text.length).toBeGreaterThan(0);
});
it("should handle validation errors", async () => {
const result = await checkKGHealth({
days: 150, // Exceeds max of 90
});
expect(result.content).toBeDefined();
// Should return error response
const text = result.content.map((c) => c.text).join(" ");
expect(text).toBeTruthy();
});
it("should handle recommendations with different priorities", async () => {
const result = await checkKGHealth({
includeHistory: false,
generateReport: true,
});
expect(result.content).toBeDefined();
// Check response structure
const text = result.content.map((c) => c.text).join(" ");
expect(text.length).toBeGreaterThan(0);
});
it("should detect and report data quality issues", async () => {
const kg = await getKnowledgeGraph();
// Create nodes
kg.addNode({
id: "test-project-1",
type: "project",
label: "Test Project 1",
properties: { name: "test-project-1" },
weight: 1.0,
});
kg.addNode({
id: "test-tech-1",
type: "technology",
label: "TypeScript",
properties: { name: "typescript" },
weight: 1.0,
});
// Create an orphaned edge (edge pointing to non-existent node)
kg.addEdge({
source: "test-tech-1",
target: "non-existent-node-id",
type: "uses",
weight: 1.0,
confidence: 0.9,
properties: {},
});
const result = await checkKGHealth({
includeHistory: false,
generateReport: true,
});
expect(result.content).toBeDefined();
const text = result.content.map((c) => c.text).join(" ");
// Should report data quality issues with score < 90
expect(text).toContain("Health");
// The report should show details about stale nodes, orphaned edges, etc.
expect(text.length).toBeGreaterThan(100); // Detailed report
});
it("should test all priority icon levels", async () => {
// This test indirectly tests getPriorityIcon for "high", "medium", and "low"
const result = await checkKGHealth({
includeHistory: false,
generateReport: true,
});
expect(result.content).toBeDefined();
const text = result.content.map((c) => c.text).join(" ");
// The report should include priority indicators (emojis)
expect(text.length).toBeGreaterThan(0);
});
it("should test formatBytes for different size ranges", async () => {
// The tool will calculate storage size which triggers formatBytes
// This covers: bytes, KB, MB ranges
const result = await checkKGHealth({
includeHistory: false,
generateReport: true,
});
expect(result.content).toBeDefined();
const text = result.content.map((c) => c.text).join(" ");
// Storage size should be included in the report
expect(text.length).toBeGreaterThan(0);
});
});
```
--------------------------------------------------------------------------------
/src/utils/usage-metadata.ts:
--------------------------------------------------------------------------------
```typescript
import {
DriftSnapshot,
UsageMetadata,
DocumentationSection,
} from "./drift-detector.js";
import { ASTAnalyzer, CallGraph } from "./ast-analyzer.js";
/**
* Builds UsageMetadata from snapshot artifacts so priority scoring can
* incorporate real import/reference counts and call graph analysis.
*
* Enhanced implementation (ADR-012 Phase 2):
* - Builds call graphs for exported functions to count actual usage
* - Extracts class instantiations from AST analysis
* - Counts imports with better accuracy
* - Incorporates documentation references
*/
export class UsageMetadataCollector {
private analyzer: ASTAnalyzer;
constructor(analyzer?: ASTAnalyzer) {
this.analyzer = analyzer || new ASTAnalyzer();
}
/**
* Collect usage metadata from snapshot using AST call graph analysis
*/
async collect(snapshot: DriftSnapshot): Promise<UsageMetadata> {
const functionCalls = new Map<string, number>();
const classInstantiations = new Map<string, number>();
const imports = new Map<string, number>();
const exportedSymbols = new Set<string>();
const functionSymbols = new Map<string, { file: string; signature: any }>();
const classSymbols = new Map<string, { file: string; info: any }>();
// Initialize analyzer if needed
await this.analyzer.initialize();
// Phase 1: Collect all exported symbols and their locations
for (const [filePath, fileAnalysis] of snapshot.files.entries()) {
// Track exports
for (const symbol of fileAnalysis.exports ?? []) {
exportedSymbols.add(symbol);
}
// Track functions with their file locations
for (const fn of fileAnalysis.functions ?? []) {
if (fn.name) {
functionSymbols.set(fn.name, { file: filePath, signature: fn });
}
}
// Track classes with their file locations
for (const cls of fileAnalysis.classes ?? []) {
if (cls.name) {
classSymbols.set(cls.name, { file: filePath, info: cls });
}
}
}
// Phase 2: Build call graphs for exported functions to count actual usage
const callGraphPromises: Promise<CallGraph | null>[] = [];
const exportedFunctions = Array.from(exportedSymbols).filter((name) =>
functionSymbols.has(name),
);
for (const funcName of exportedFunctions.slice(0, 50)) {
// Limit to top 50 exported functions to avoid performance issues
const funcInfo = functionSymbols.get(funcName);
if (funcInfo) {
callGraphPromises.push(
this.analyzer
.buildCallGraph(funcName, funcInfo.file, {
maxDepth: 2, // Limit depth for performance
resolveImports: true,
extractConditionals: false, // Skip for performance
trackExceptions: false,
})
.catch(() => null), // Gracefully handle failures
);
}
}
const callGraphs = await Promise.all(callGraphPromises);
// Count function calls from call graphs
for (const graph of callGraphs) {
if (!graph) continue;
// Count calls recursively in the call graph
const countCalls = (node: any): void => {
if (!node.function?.name) return;
const funcName = node.function.name;
functionCalls.set(funcName, (functionCalls.get(funcName) ?? 0) + 1);
// Recursively count child calls
for (const childCall of node.calls ?? []) {
countCalls(childCall);
}
};
countCalls(graph.root);
}
// Phase 3: Count imports and infer usage
for (const fileAnalysis of snapshot.files.values()) {
for (const imp of fileAnalysis.imports ?? []) {
for (const imported of imp.imports ?? []) {
const name = imported.alias || imported.name;
if (name) {
imports.set(name, (imports.get(name) ?? 0) + 1);
// If imported symbol is an exported function/class, count as usage
const isFunction = functionSymbols.has(name);
const isClass = classSymbols.has(name);
if (exportedSymbols.has(name)) {
if (isClass) {
classInstantiations.set(
name,
(classInstantiations.get(name) ?? 0) + 1,
);
} else if (isFunction) {
// Only count if not already counted from call graph
if (!functionCalls.has(name)) {
functionCalls.set(name, (functionCalls.get(name) ?? 0) + 1);
}
}
}
}
}
}
}
// Phase 4: Extract class instantiations from AST
// Look for "new ClassName()" patterns in function bodies
for (const fileAnalysis of snapshot.files.values()) {
for (const fn of fileAnalysis.functions ?? []) {
// Check function dependencies for class instantiations
for (const dep of fn.dependencies ?? []) {
if (classSymbols.has(dep)) {
classInstantiations.set(
dep,
(classInstantiations.get(dep) ?? 0) + 1,
);
}
}
}
}
// Phase 5: Count references from documentation sections
const bumpRefs = (
sections: DocumentationSection[],
target: "fn" | "cls",
) => {
for (const section of sections) {
const refs =
target === "fn"
? section.referencedFunctions ?? []
: section.referencedClasses ?? [];
for (const ref of refs) {
if (target === "fn") {
functionCalls.set(ref, (functionCalls.get(ref) ?? 0) + 1);
} else {
classInstantiations.set(
ref,
(classInstantiations.get(ref) ?? 0) + 1,
);
}
}
}
};
for (const doc of snapshot.documentation.values()) {
bumpRefs(doc.sections ?? [], "fn");
bumpRefs(doc.sections ?? [], "cls");
}
return {
filePath: snapshot.projectPath,
functionCalls,
classInstantiations,
imports,
};
}
/**
* Synchronous collection using heuristics (fallback when analyzer unavailable)
* This maintains backward compatibility with existing code
*/
collectSync(snapshot: DriftSnapshot): UsageMetadata {
const functionCalls = new Map<string, number>();
const classInstantiations = new Map<string, number>();
const imports = new Map<string, number>();
const exportedSymbols = new Set<string>();
const functionSymbols = new Set<string>();
const classSymbols = new Set<string>();
// Collect exported symbols and discovered functions/classes
for (const file of snapshot.files.values()) {
for (const symbol of file.exports ?? []) {
exportedSymbols.add(symbol);
}
for (const fn of file.functions ?? []) {
if (fn.name) functionSymbols.add(fn.name);
}
for (const cls of file.classes ?? []) {
if (cls.name) classSymbols.add(cls.name);
}
}
// Count imports from source files
for (const file of snapshot.files.values()) {
for (const imp of file.imports ?? []) {
for (const imported of imp.imports ?? []) {
const name = imported.alias || imported.name;
if (name) {
imports.set(name, (imports.get(name) ?? 0) + 1);
const isFunction = functionSymbols.has(name);
const isClass = classSymbols.has(name);
if (exportedSymbols.has(name) || isFunction || isClass) {
if (isClass) {
classInstantiations.set(
name,
(classInstantiations.get(name) ?? 0) + 1,
);
} else {
functionCalls.set(name, (functionCalls.get(name) ?? 0) + 1);
}
}
}
}
}
}
// Count references from documentation sections
const bumpRefs = (
sections: DocumentationSection[],
target: "fn" | "cls",
) => {
for (const section of sections) {
const refs =
target === "fn"
? section.referencedFunctions ?? []
: section.referencedClasses ?? [];
for (const ref of refs) {
if (target === "fn") {
functionCalls.set(ref, (functionCalls.get(ref) ?? 0) + 1);
} else {
classInstantiations.set(
ref,
(classInstantiations.get(ref) ?? 0) + 1,
);
}
}
}
};
for (const doc of snapshot.documentation.values()) {
bumpRefs(doc.sections ?? [], "fn");
bumpRefs(doc.sections ?? [], "cls");
}
return {
filePath: snapshot.projectPath,
functionCalls,
classInstantiations,
imports,
};
}
}
```
--------------------------------------------------------------------------------
/docs/development/MCP_INSPECTOR_TESTING.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.945Z"
last_validated: "2025-12-09T19:41:38.576Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# MCP Inspector Testing Guide
The MCP Inspector is an in-browser debugging tool for testing MCP servers without connecting to actual applications. This guide explains how to use it for DocuMCP development.
## Prerequisites
- Node.js 20+ installed
- DocuMCP repository cloned
- Dependencies installed (`npm install`)
## Quick Start
### Option 1: Build and Launch Inspector
```bash
npm run build:inspect
```
This command:
1. Compiles TypeScript to `dist/`
2. Launches MCP Inspector
3. Opens browser at `http://localhost:5173` (or similar)
### Option 2: Launch Inspector with Existing Build
```bash
npm run build # First build (if needed)
npm run dev:inspect # Then launch inspector
```
## Using the Inspector
### 1. Connect to Server
1. Open the browser URL provided by the inspector
2. Click the "Connect" button in the left sidebar
3. Wait for connection confirmation
### 2. Test Tools
The Tools section lists all available MCP tools:
**Example: Testing `analyze_repository`**
1. Click "Tools" in the top navigation
2. Select "analyze_repository" from the list
3. In the right panel, enter parameters:
```json
{
"path": "./",
"depth": "standard"
}
```
4. Click "Run Tool"
5. Verify the output includes:
- File counts
- Language detection
- Dependency analysis
- Memory insights
**Example: Testing `recommend_ssg`**
1. First run `analyze_repository` (as above) to get an `analysisId`
2. Select "recommend_ssg"
3. Enter parameters:
```json
{
"analysisId": "<id-from-previous-analysis>",
"userId": "test-user",
"preferences": {
"priority": "simplicity",
"ecosystem": "javascript"
}
}
```
4. Click "Run Tool"
5. Verify recommendation includes:
- Recommended SSG
- Confidence score
- Reasoning
- Alternative options
### 3. Test Resources
Resources provide static data for application UIs:
**Example: Testing SSG List**
1. Click "Resources" in the top navigation
2. Select "documcp://ssgs/available"
3. Verify output shows all 5 SSGs:
- Jekyll
- Hugo
- Docusaurus
- MkDocs
- Eleventy
4. Check each SSG includes:
- ID, name, description
- Language, complexity, build speed
- Best use cases
**Example: Testing Configuration Templates**
1. Select "documcp://templates/jekyll-config"
2. Verify YAML template is returned
3. Test other templates:
- `documcp://templates/hugo-config`
- `documcp://templates/docusaurus-config`
- `documcp://templates/mkdocs-config`
- `documcp://templates/eleventy-config`
- `documcp://templates/diataxis-structure`
### 4. Test Prompts
Prompts provide pre-written instructions for specialized tasks:
**Example: Testing `tutorial-writer`**
1. Click "Prompts" in the top navigation
2. Select "tutorial-writer"
3. Provide arguments:
```json
{
"project_path": "./",
"target_audience": "beginners",
"learning_goal": "deploy first documentation site"
}
```
4. Click "Get Prompt"
5. Verify prompt messages include:
- Project context (languages, frameworks)
- Diataxis tutorial requirements
- Step-by-step structure guidance
**Example: Testing `analyze-and-recommend` workflow**
1. Select "analyze-and-recommend"
2. Provide arguments:
```json
{
"project_path": "./",
"analysis_depth": "standard",
"preferences": "good community support"
}
```
3. Verify workflow prompt includes:
- Complete analysis workflow
- SSG recommendation guidance
- Implementation steps
## Common Test Cases
### Tool Testing Checklist
- [ ] **analyze_repository**
- [ ] Test with current directory (`./`)
- [ ] Test with different depth levels
- [ ] Verify memory integration works
- [ ] Check similar projects are found
- [ ] **recommend_ssg**
- [ ] Test with valid analysisId
- [ ] Test different preference combinations
- [ ] Verify confidence scores
- [ ] Check historical data integration
- [ ] **generate_config**
- [ ] Test each SSG type
- [ ] Verify output format
- [ ] Check template variables
- [ ] **setup_structure**
- [ ] Test Diataxis structure creation
- [ ] Verify all categories included
- [ ] Check example content
- [ ] **deploy_pages**
- [ ] Test workflow generation
- [ ] Verify GitHub Actions YAML
- [ ] Check custom domain support
- [ ] **validate_content**
- [ ] Test with documentation path
- [ ] Verify link checking
- [ ] Check code block validation
### Resource Testing Checklist
- [ ] **documcp://ssgs/available**
- [ ] All 5 SSGs listed
- [ ] Complete metadata for each
- [ ] **Templates**
- [ ] Jekyll config valid YAML
- [ ] Hugo config valid YAML
- [ ] Docusaurus config valid JS
- [ ] MkDocs config valid YAML
- [ ] Eleventy config valid JS
- [ ] Diataxis structure valid JSON
- [ ] **Workflows**
- [ ] All workflows listed
- [ ] Quick setup available
- [ ] Full setup available
- [ ] Guidance provided
### Prompt Testing Checklist
- [ ] **Technical Writer Prompts**
- [ ] tutorial-writer
- [ ] howto-guide-writer
- [ ] reference-writer
- [ ] explanation-writer
- [ ] diataxis-organizer
- [ ] readme-optimizer
- [ ] **Workflow Prompts**
- [ ] analyze-and-recommend
- [ ] setup-documentation
- [ ] troubleshoot-deployment
## Troubleshooting
### Inspector Won't Connect
**Problem:** Connection fails or times out
**Solutions:**
1. Ensure server is built: `npm run build`
2. Check no other process is using the port
3. Try restarting: `Ctrl+C` and re-run `npm run dev:inspect`
### Tool Returns Error
**Problem:** Tool execution fails with error message
**Solutions:**
1. Check parameter format (must be valid JSON)
2. Verify required parameters are provided
3. Ensure file paths exist (for file-based tools)
4. Check server logs for detailed error messages
### Resource Not Found
**Problem:** Resource URI returns "Resource not found" error
**Solutions:**
1. Verify URI spelling matches exactly (case-sensitive)
2. Check resource list for available URIs
3. Ensure server version matches documentation
### Prompt Arguments Missing
**Problem:** Prompt doesn't use provided arguments
**Solutions:**
1. Check argument names match prompt definition
2. Verify JSON format is correct
3. Required arguments must be provided
## Best Practices
### During Development
1. **Keep Inspector Open:** Launch inspector at start of development session
2. **Test After Changes:** Run tool tests after modifying tool implementation
3. **Verify All Paths:** Test both success and error paths
4. **Check Edge Cases:** Test with unusual inputs, empty values, etc.
### Before Committing
1. **Full Tool Test:** Test at least one example from each tool
2. **Resource Validation:** Verify all resources return valid data
3. **Prompt Verification:** Check prompts generate correct messages
4. **Error Handling:** Test with invalid inputs to verify error messages
### For Bug Fixing
1. **Reproduce in Inspector:** Use inspector to reproduce bug consistently
2. **Test Fix:** Verify fix works in inspector before integration testing
3. **Regression Test:** Test related tools to ensure no regressions
4. **Document:** Add test case to this guide if bug was subtle
## Integration with Development Workflow
### Daily Development
```bash
# Morning startup
npm run build:inspect
# Keep inspector tab open
# Make code changes in editor
# Test changes in inspector
# Iterate until working
# Before lunch/end of day
npm run build && npm test
```
### Pre-Commit Workflow
```bash
# Run full validation
npm run ci
# Test in inspector
npm run build:inspect
# Manual spot checks on key tools
# Commit when all checks pass
```
### CI/CD Integration
While MCP Inspector is primarily for local development, you can add automated checks:
```bash
# In CI pipeline (future enhancement)
npm run build
npx @modelcontextprotocol/inspector dist/index.js --test automated-tests.json
```
## Additional Resources
- **MCP Inspector GitHub:** https://github.com/modelcontextprotocol/inspector
- **MCP Specification:** https://modelcontextprotocol.io/docs
- **MCP TypeScript SDK:** https://github.com/modelcontextprotocol/typescript-sdk
- **DocuMCP Architecture:** See `docs/adrs/` for detailed architectural decisions
## Feedback
If you encounter issues with MCP Inspector or this guide:
1. Check for known issues: https://github.com/modelcontextprotocol/inspector/issues
2. Report DocuMCP-specific issues: https://github.com/anthropics/documcp/issues
3. Suggest improvements to this guide via pull request
---
**Last Updated:** 2025-10-09
**Version:** 1.0.0
```
--------------------------------------------------------------------------------
/tests/prompts/technical-writer-prompts.test.ts:
--------------------------------------------------------------------------------
```typescript
import {
generateTechnicalWriterPrompts,
analyzeProjectContext,
} from "../../src/prompts/technical-writer-prompts.js";
import { promises as fs } from "fs";
import { join } from "path";
import { tmpdir } from "os";
describe("Technical Writer Diataxis Prompts", () => {
let testProjectPath: string;
beforeEach(async () => {
// Create a temporary test project
testProjectPath = join(tmpdir(), `test-project-${Date.now()}`);
await fs.mkdir(testProjectPath, { recursive: true });
// Create a basic package.json
const packageJson = {
name: "test-project",
version: "1.0.0",
dependencies: {
react: "^18.0.0",
typescript: "^5.0.0",
},
scripts: {
test: "jest",
},
};
await fs.writeFile(
join(testProjectPath, "package.json"),
JSON.stringify(packageJson, null, 2),
);
// Create a basic README.md
await fs.writeFile(
join(testProjectPath, "README.md"),
"# Test Project\n\nA test project for testing.",
);
// Create a test directory
await fs.mkdir(join(testProjectPath, "tests"), { recursive: true });
await fs.writeFile(
join(testProjectPath, "tests", "example.test.js"),
'test("example", () => { expect(true).toBe(true); });',
);
// Create a CI file
await fs.mkdir(join(testProjectPath, ".github", "workflows"), {
recursive: true,
});
await fs.writeFile(
join(testProjectPath, ".github", "workflows", "ci.yml"),
"name: CI\non: [push, pull_request]",
);
});
afterEach(async () => {
// Clean up test project
try {
await fs.rm(testProjectPath, { recursive: true, force: true });
} catch (error) {
// Ignore cleanup errors
}
});
describe("generateTechnicalWriterPrompts", () => {
it("should generate tutorial writer prompts", async () => {
const prompts = await generateTechnicalWriterPrompts(
"tutorial-writer",
testProjectPath,
);
expect(prompts.length).toBeGreaterThan(0);
expect(prompts[0]).toHaveProperty("role");
expect(prompts[0]).toHaveProperty("content");
expect(prompts[0].content).toHaveProperty("type", "text");
expect(prompts[0].content).toHaveProperty("text");
expect(prompts[0].content.text).toContain("tutorial");
expect(prompts[0].content.text).toContain("Diataxis");
});
it("should generate how-to guide writer prompts", async () => {
const prompts = await generateTechnicalWriterPrompts(
"howto-guide-writer",
testProjectPath,
);
expect(prompts.length).toBeGreaterThan(0);
expect(prompts[0].content.text).toContain("how-to guide");
expect(prompts[0].content.text).toContain("Problem-oriented");
});
it("should generate reference writer prompts", async () => {
const prompts = await generateTechnicalWriterPrompts(
"reference-writer",
testProjectPath,
);
expect(prompts.length).toBeGreaterThan(0);
expect(prompts[0].content.text).toContain("reference documentation");
expect(prompts[0].content.text).toContain("Information-oriented");
});
it("should generate explanation writer prompts", async () => {
const prompts = await generateTechnicalWriterPrompts(
"explanation-writer",
testProjectPath,
);
expect(prompts.length).toBeGreaterThan(0);
expect(prompts[0].content.text).toContain("explanation documentation");
expect(prompts[0].content.text).toContain("Understanding-oriented");
});
it("should generate diataxis organizer prompts", async () => {
const prompts = await generateTechnicalWriterPrompts(
"diataxis-organizer",
testProjectPath,
);
expect(prompts.length).toBeGreaterThan(0);
expect(prompts[0].content.text).toContain("Diataxis framework");
expect(prompts[0].content.text).toContain("organize");
});
it("should generate readme optimizer prompts", async () => {
const prompts = await generateTechnicalWriterPrompts(
"readme-optimizer",
testProjectPath,
);
expect(prompts.length).toBeGreaterThan(0);
expect(prompts[0].content.text).toContain("README");
expect(prompts[0].content.text).toContain("Diataxis-aware");
});
it("should generate analyze-and-recommend prompts", async () => {
const prompts = await generateTechnicalWriterPrompts(
"analyze-and-recommend",
testProjectPath,
);
expect(prompts.length).toBeGreaterThan(0);
expect(prompts[0].content.text.toLowerCase()).toContain("analyz");
expect(prompts[0].content.text.toLowerCase()).toContain("recommend");
});
it("should generate setup-documentation prompts", async () => {
const prompts = await generateTechnicalWriterPrompts(
"setup-documentation",
testProjectPath,
);
expect(prompts.length).toBeGreaterThan(0);
expect(prompts[0].content.text).toContain("documentation");
});
it("should generate troubleshoot-deployment prompts", async () => {
const prompts = await generateTechnicalWriterPrompts(
"troubleshoot-deployment",
testProjectPath,
);
expect(prompts.length).toBeGreaterThan(0);
expect(prompts[0].content.text).toContain("troubleshoot");
expect(prompts[0].content.text).toContain("deployment");
});
it("should generate maintain-documentation-freshness prompts", async () => {
const prompts = await generateTechnicalWriterPrompts(
"maintain-documentation-freshness",
testProjectPath,
{ action: "track", preset: "monthly" },
);
expect(prompts.length).toBeGreaterThan(0);
expect(prompts[0].content.text).toContain("freshness");
expect(prompts[0].content.text).toContain("track");
});
it("should throw error for unknown prompt type", async () => {
await expect(
generateTechnicalWriterPrompts("unknown-type", testProjectPath),
).rejects.toThrow("Unknown prompt type: unknown-type");
});
it("should include project context in prompts", async () => {
const prompts = await generateTechnicalWriterPrompts(
"tutorial-writer",
testProjectPath,
);
const promptText = prompts[0].content.text;
expect(promptText).toContain("React"); // Should detect React from package.json
expect(promptText).toContain("TypeScript"); // Should detect TypeScript
});
});
describe("analyzeProjectContext", () => {
it("should analyze project context correctly", async () => {
const context = await analyzeProjectContext(testProjectPath);
expect(context).toHaveProperty("projectType");
expect(context).toHaveProperty("languages");
expect(context).toHaveProperty("frameworks");
expect(context).toHaveProperty("hasTests");
expect(context).toHaveProperty("hasCI");
expect(context).toHaveProperty("readmeExists");
expect(context).toHaveProperty("documentationGaps");
// Check specific values based on our test setup
expect(context.projectType).toBe("node_application");
expect(context.languages).toContain("TypeScript");
expect(context.frameworks).toContain("React");
expect(context.hasTests).toBe(true);
expect(context.hasCI).toBe(true);
expect(context.readmeExists).toBe(true);
expect(context.packageManager).toBe("npm");
});
it("should detect documentation gaps", async () => {
const context = await analyzeProjectContext(testProjectPath);
expect(Array.isArray(context.documentationGaps)).toBe(true);
// Should detect missing documentation since we only have a basic README
expect(context.documentationGaps.length).toBeGreaterThan(0);
});
it("should handle projects without package.json", async () => {
// Create a project without package.json
const simpleProjectPath = join(tmpdir(), `simple-project-${Date.now()}`);
await fs.mkdir(simpleProjectPath, { recursive: true });
try {
const context = await analyzeProjectContext(simpleProjectPath);
expect(context.projectType).toBe("unknown");
expect(context.languages).toEqual([]);
expect(context.frameworks).toEqual([]);
expect(context.readmeExists).toBe(false);
} finally {
await fs.rm(simpleProjectPath, { recursive: true, force: true });
}
});
it("should detect yarn package manager", async () => {
// Create yarn.lock to simulate yarn project
await fs.writeFile(join(testProjectPath, "yarn.lock"), "# Yarn lockfile");
const context = await analyzeProjectContext(testProjectPath);
expect(context.packageManager).toBe("yarn");
});
it("should detect pnpm package manager", async () => {
// Create pnpm-lock.yaml to simulate pnpm project
await fs.writeFile(
join(testProjectPath, "pnpm-lock.yaml"),
"lockfileVersion: 5.4",
);
const context = await analyzeProjectContext(testProjectPath);
expect(context.packageManager).toBe("pnpm");
});
});
});
```
--------------------------------------------------------------------------------
/src/utils/freshness-tracker.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Documentation Freshness Tracking Utilities
*
* Tracks when documentation files were last updated and validated,
* supporting both short-term (minutes/hours) and long-term (days) staleness detection.
*/
import fs from "fs/promises";
import path from "path";
import matter from "gray-matter";
/**
* Time unit for staleness threshold
*/
export type TimeUnit = "minutes" | "hours" | "days";
/**
* Staleness threshold configuration
*/
export interface StalenessThreshold {
value: number;
unit: TimeUnit;
}
/**
* Predefined staleness levels
*/
export const STALENESS_PRESETS = {
realtime: { value: 30, unit: "minutes" as TimeUnit },
active: { value: 1, unit: "hours" as TimeUnit },
recent: { value: 24, unit: "hours" as TimeUnit },
weekly: { value: 7, unit: "days" as TimeUnit },
monthly: { value: 30, unit: "days" as TimeUnit },
quarterly: { value: 90, unit: "days" as TimeUnit },
} as const;
/**
* Documentation metadata tracked in frontmatter
*/
export interface DocFreshnessMetadata {
last_updated?: string; // ISO 8601 timestamp
last_validated?: string; // ISO 8601 timestamp
validated_against_commit?: string;
auto_updated?: boolean;
staleness_threshold?: StalenessThreshold;
update_frequency?: keyof typeof STALENESS_PRESETS;
}
/**
* Full frontmatter structure
*/
export interface DocFrontmatter {
title?: string;
description?: string;
documcp?: DocFreshnessMetadata;
[key: string]: unknown;
}
/**
* File freshness status
*/
export interface FileFreshnessStatus {
filePath: string;
relativePath: string;
hasMetadata: boolean;
metadata?: DocFreshnessMetadata;
lastUpdated?: Date;
lastValidated?: Date;
ageInMs?: number;
ageFormatted?: string;
isStale: boolean;
stalenessLevel: "fresh" | "warning" | "stale" | "critical" | "unknown";
staleDays?: number;
}
/**
* Freshness scan report
*/
export interface FreshnessScanReport {
scannedAt: string;
docsPath: string;
totalFiles: number;
filesWithMetadata: number;
filesWithoutMetadata: number;
freshFiles: number;
warningFiles: number;
staleFiles: number;
criticalFiles: number;
files: FileFreshnessStatus[];
thresholds: {
warning: StalenessThreshold;
stale: StalenessThreshold;
critical: StalenessThreshold;
};
}
/**
* Convert time threshold to milliseconds
*/
export function thresholdToMs(threshold: StalenessThreshold): number {
const { value, unit } = threshold;
switch (unit) {
case "minutes":
return value * 60 * 1000;
case "hours":
return value * 60 * 60 * 1000;
case "days":
return value * 24 * 60 * 60 * 1000;
default:
throw new Error(`Unknown time unit: ${unit}`);
}
}
/**
* Format age in human-readable format
*/
export function formatAge(ageMs: number): string {
const seconds = Math.floor(ageMs / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) {
return `${days} day${days !== 1 ? "s" : ""}`;
} else if (hours > 0) {
return `${hours} hour${hours !== 1 ? "s" : ""}`;
} else if (minutes > 0) {
return `${minutes} minute${minutes !== 1 ? "s" : ""}`;
} else {
return `${seconds} second${seconds !== 1 ? "s" : ""}`;
}
}
/**
* Parse frontmatter from markdown file
*/
export async function parseDocFrontmatter(
filePath: string,
): Promise<DocFrontmatter> {
try {
const content = await fs.readFile(filePath, "utf-8");
const { data } = matter(content);
return data as DocFrontmatter;
} catch (error) {
return {};
}
}
/**
* Update frontmatter in markdown file
*/
export async function updateDocFrontmatter(
filePath: string,
metadata: Partial<DocFreshnessMetadata>,
): Promise<void> {
const content = await fs.readFile(filePath, "utf-8");
const { data, content: body } = matter(content);
const existingDocuMCP = (data.documcp as DocFreshnessMetadata) || {};
const updatedData = {
...data,
documcp: {
...existingDocuMCP,
...metadata,
},
};
const newContent = matter.stringify(body, updatedData);
await fs.writeFile(filePath, newContent, "utf-8");
}
/**
* Calculate file freshness status
*/
export function calculateFreshnessStatus(
filePath: string,
relativePath: string,
frontmatter: DocFrontmatter,
thresholds: {
warning: StalenessThreshold;
stale: StalenessThreshold;
critical: StalenessThreshold;
},
): FileFreshnessStatus {
const metadata = frontmatter.documcp;
const hasMetadata = !!metadata?.last_updated;
if (!hasMetadata) {
return {
filePath,
relativePath,
hasMetadata: false,
isStale: true,
stalenessLevel: "unknown",
};
}
const lastUpdated = new Date(metadata.last_updated!);
const lastValidated = metadata.last_validated
? new Date(metadata.last_validated)
: undefined;
const now = new Date();
const ageInMs = now.getTime() - lastUpdated.getTime();
const ageFormatted = formatAge(ageInMs);
const staleDays = Math.floor(ageInMs / (24 * 60 * 60 * 1000));
// Determine staleness level
let stalenessLevel: FileFreshnessStatus["stalenessLevel"];
let isStale: boolean;
const warningMs = thresholdToMs(thresholds.warning);
const staleMs = thresholdToMs(thresholds.stale);
const criticalMs = thresholdToMs(thresholds.critical);
if (ageInMs >= criticalMs) {
stalenessLevel = "critical";
isStale = true;
} else if (ageInMs >= staleMs) {
stalenessLevel = "stale";
isStale = true;
} else if (ageInMs >= warningMs) {
stalenessLevel = "warning";
isStale = false;
} else {
stalenessLevel = "fresh";
isStale = false;
}
return {
filePath,
relativePath,
hasMetadata: true,
metadata,
lastUpdated,
lastValidated,
ageInMs,
ageFormatted,
isStale,
stalenessLevel,
staleDays,
};
}
/**
* Find all markdown files in directory recursively
*/
export async function findMarkdownFiles(dir: string): Promise<string[]> {
const files: string[] = [];
async function scan(currentDir: string): Promise<void> {
const entries = await fs.readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
// Skip common directories
if (entry.isDirectory()) {
if (
!["node_modules", ".git", "dist", "build", ".documcp"].includes(
entry.name,
)
) {
await scan(fullPath);
}
continue;
}
// Include markdown files
if (entry.isFile() && /\.(md|mdx)$/i.test(entry.name)) {
files.push(fullPath);
}
}
}
await scan(dir);
return files;
}
/**
* Scan directory for documentation freshness
*/
export async function scanDocumentationFreshness(
docsPath: string,
thresholds: {
warning?: StalenessThreshold;
stale?: StalenessThreshold;
critical?: StalenessThreshold;
} = {},
): Promise<FreshnessScanReport> {
// Default thresholds
const finalThresholds = {
warning: thresholds.warning || STALENESS_PRESETS.weekly,
stale: thresholds.stale || STALENESS_PRESETS.monthly,
critical: thresholds.critical || STALENESS_PRESETS.quarterly,
};
// Find all markdown files
const markdownFiles = await findMarkdownFiles(docsPath);
// Analyze each file
const files: FileFreshnessStatus[] = [];
for (const filePath of markdownFiles) {
const relativePath = path.relative(docsPath, filePath);
const frontmatter = await parseDocFrontmatter(filePath);
const status = calculateFreshnessStatus(
filePath,
relativePath,
frontmatter,
finalThresholds,
);
files.push(status);
}
// Calculate summary statistics
const totalFiles = files.length;
const filesWithMetadata = files.filter((f) => f.hasMetadata).length;
const filesWithoutMetadata = totalFiles - filesWithMetadata;
const freshFiles = files.filter((f) => f.stalenessLevel === "fresh").length;
const warningFiles = files.filter(
(f) => f.stalenessLevel === "warning",
).length;
const staleFiles = files.filter((f) => f.stalenessLevel === "stale").length;
const criticalFiles = files.filter(
(f) => f.stalenessLevel === "critical",
).length;
return {
scannedAt: new Date().toISOString(),
docsPath,
totalFiles,
filesWithMetadata,
filesWithoutMetadata,
freshFiles,
warningFiles,
staleFiles,
criticalFiles,
files,
thresholds: finalThresholds,
};
}
/**
* Initialize frontmatter for files without metadata
*/
export async function initializeFreshnessMetadata(
filePath: string,
options: {
updateFrequency?: keyof typeof STALENESS_PRESETS;
autoUpdated?: boolean;
} = {},
): Promise<void> {
const frontmatter = await parseDocFrontmatter(filePath);
if (!frontmatter.documcp?.last_updated) {
const metadata: DocFreshnessMetadata = {
last_updated: new Date().toISOString(),
last_validated: new Date().toISOString(),
auto_updated: options.autoUpdated ?? false,
update_frequency: options.updateFrequency || "monthly",
};
if (options.updateFrequency) {
metadata.staleness_threshold = STALENESS_PRESETS[options.updateFrequency];
}
await updateDocFrontmatter(filePath, metadata);
}
}
```
--------------------------------------------------------------------------------
/tests/tools/readme-best-practices.test.ts:
--------------------------------------------------------------------------------
```typescript
import { readmeBestPractices } from "../../src/tools/readme-best-practices.js";
import { formatMCPResponse } from "../../src/types/api.js";
import { writeFile, mkdir, rm } from "fs/promises";
import { join } from "path";
describe("readmeBestPractices", () => {
const testDir = join(process.cwd(), "test-readme-best-practices-temp");
beforeEach(async () => {
// Create test directory
await mkdir(testDir, { recursive: true });
});
afterEach(async () => {
// Clean up test directory
try {
await rm(testDir, { recursive: true, force: true });
} catch (error) {
// Ignore cleanup errors
}
});
describe("Basic Functionality", () => {
test("should analyze README best practices with default parameters", async () => {
const readmePath = join(testDir, "README.md");
await writeFile(
readmePath,
`# Test Library
## Description
This is a test library for analyzing best practices.
## Installation
\`\`\`bash
npm install test-library
\`\`\`
## Usage
\`\`\`javascript
const lib = require('test-library');
\`\`\`
## API Reference
Function documentation here.
## Contributing
Please read CONTRIBUTING.md.
## License
MIT License
`,
);
const result = await readmeBestPractices({
readme_path: readmePath,
});
expect(result.success).toBe(true);
expect(result.data).toBeDefined();
expect(result.data!.bestPracticesReport).toBeDefined();
expect(result.metadata).toBeDefined();
});
test("should handle different project types", async () => {
const readmePath = join(testDir, "README.md");
await writeFile(readmePath, "# Application\n\nA web application.");
const result = await readmeBestPractices({
readme_path: readmePath,
project_type: "application",
});
expect(result.success).toBe(true);
expect(result.data).toBeDefined();
});
test("should generate templates when requested", async () => {
const outputDir = join(testDir, "output");
await mkdir(outputDir, { recursive: true });
const result = await readmeBestPractices({
readme_path: join(testDir, "nonexistent.md"),
generate_template: true,
output_directory: outputDir,
project_type: "library",
});
expect(result.success).toBe(true);
expect(result.data).toBeDefined();
});
test("should handle different target audiences", async () => {
const readmePath = join(testDir, "README.md");
await writeFile(readmePath, "# Advanced Tool\n\nFor expert users.");
const result = await readmeBestPractices({
readme_path: readmePath,
target_audience: "advanced",
});
expect(result.success).toBe(true);
expect(result.data).toBeDefined();
});
});
describe("Error Handling", () => {
test("should handle missing README file without template generation", async () => {
const result = await readmeBestPractices({
readme_path: join(testDir, "nonexistent.md"),
generate_template: false,
});
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
expect(result.error!.code).toBe("README_NOT_FOUND");
});
test("should handle invalid project type", async () => {
const readmePath = join(testDir, "README.md");
await writeFile(readmePath, "# Test");
const result = await readmeBestPractices({
readme_path: readmePath,
project_type: "invalid_type" as any,
});
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
test("should handle invalid target audience", async () => {
const readmePath = join(testDir, "README.md");
await writeFile(readmePath, "# Test");
const result = await readmeBestPractices({
readme_path: readmePath,
target_audience: "invalid_audience" as any,
});
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
});
describe("Best Practices Analysis", () => {
test("should evaluate checklist items", async () => {
const readmePath = join(testDir, "README.md");
await writeFile(
readmePath,
`# Complete Library
## Table of Contents
- [Installation](#installation)
- [Usage](#usage)
## Description
Detailed description of the library.
## Installation
Installation instructions here.
## Usage
Usage examples here.
## API Reference
API documentation.
## Examples
Code examples.
## Contributing
Contributing guidelines.
## License
MIT License
## Support
Support information.
`,
);
const result = await readmeBestPractices({
readme_path: readmePath,
project_type: "library",
});
expect(result.success).toBe(true);
expect(result.data!.bestPracticesReport.checklist).toBeDefined();
expect(Array.isArray(result.data!.bestPracticesReport.checklist)).toBe(
true,
);
expect(result.data!.bestPracticesReport.checklist.length).toBeGreaterThan(
0,
);
});
test("should calculate overall score and grade", async () => {
const readmePath = join(testDir, "README.md");
await writeFile(readmePath, "# Basic Project\n\nMinimal content.");
const result = await readmeBestPractices({
readme_path: readmePath,
});
expect(result.success).toBe(true);
expect(
result.data!.bestPracticesReport.overallScore,
).toBeGreaterThanOrEqual(0);
expect(result.data!.bestPracticesReport.overallScore).toBeLessThanOrEqual(
100,
);
expect(result.data!.bestPracticesReport.grade).toBeDefined();
});
test("should provide recommendations", async () => {
const readmePath = join(testDir, "README.md");
await writeFile(readmePath, "# Incomplete Project");
const result = await readmeBestPractices({
readme_path: readmePath,
});
expect(result.success).toBe(true);
expect(result.data!.recommendations).toBeDefined();
expect(Array.isArray(result.data!.recommendations)).toBe(true);
expect(result.data!.nextSteps).toBeDefined();
expect(Array.isArray(result.data!.nextSteps)).toBe(true);
});
test("should provide summary metrics", async () => {
const readmePath = join(testDir, "README.md");
await writeFile(
readmePath,
`# Project
## Description
Basic description.
## Installation
Install steps.
`,
);
const result = await readmeBestPractices({
readme_path: readmePath,
});
expect(result.success).toBe(true);
expect(result.data!.bestPracticesReport.summary).toBeDefined();
expect(
result.data!.bestPracticesReport.summary.criticalIssues,
).toBeGreaterThanOrEqual(0);
expect(
result.data!.bestPracticesReport.summary.importantIssues,
).toBeGreaterThanOrEqual(0);
expect(
result.data!.bestPracticesReport.summary.sectionsPresent,
).toBeGreaterThanOrEqual(0);
expect(
result.data!.bestPracticesReport.summary.totalSections,
).toBeGreaterThan(0);
});
});
describe("Template Generation", () => {
test("should generate README template when file is missing", async () => {
const outputDir = join(testDir, "template-output");
await mkdir(outputDir, { recursive: true });
const result = await readmeBestPractices({
readme_path: join(testDir, "missing.md"),
generate_template: true,
output_directory: outputDir,
project_type: "tool",
include_community_files: true,
});
expect(result.success).toBe(true);
expect(result.data).toBeDefined();
});
test("should handle template generation without community files", async () => {
const outputDir = join(testDir, "no-community-output");
await mkdir(outputDir, { recursive: true });
const result = await readmeBestPractices({
readme_path: join(testDir, "missing.md"),
generate_template: true,
output_directory: outputDir,
include_community_files: false,
});
expect(result.success).toBe(true);
expect(result.data).toBeDefined();
});
});
describe("Response Format", () => {
test("should return MCPToolResponse structure", async () => {
const readmePath = join(testDir, "README.md");
await writeFile(readmePath, "# Test Project");
const result = await readmeBestPractices({
readme_path: readmePath,
});
expect(result.success).toBeDefined();
expect(result.metadata).toBeDefined();
expect(result.metadata.toolVersion).toBe("1.0.0");
expect(result.metadata.executionTime).toBeGreaterThanOrEqual(0);
expect(result.metadata.timestamp).toBeDefined();
expect(result.metadata.analysisId).toBeDefined();
});
test("should format properly with formatMCPResponse", async () => {
const readmePath = join(testDir, "README.md");
await writeFile(readmePath, "# Test Project");
const result = await readmeBestPractices({
readme_path: readmePath,
});
// Test that the result can be formatted without errors
const formatted = formatMCPResponse(result);
expect(formatted.content).toBeDefined();
expect(Array.isArray(formatted.content)).toBe(true);
expect(formatted.content.length).toBeGreaterThan(0);
expect(formatted.isError).toBe(false);
});
});
});
```
--------------------------------------------------------------------------------
/docs/adrs/adr-0002-repository-analysis-engine.md:
--------------------------------------------------------------------------------
```markdown
---
id: adr-2-repository-analysis-engine
title: "ADR-002: Repository Analysis Engine Design"
sidebar_label: "ADR-002: Repository Analysis Engine Design"
sidebar_position: 2
documcp:
last_updated: "2025-01-14T00:00:00.000Z"
last_validated: "2025-01-14T00:00:00.000Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 577a312
---
# ADR-002: Multi-Layered Repository Analysis Engine Design
## Status
Accepted
## Context
DocuMCP needs to understand repository characteristics to make intelligent recommendations about static site generators and documentation structure. The analysis must go beyond simple file counting to provide deep insights into project complexity, language ecosystems, existing documentation patterns, and development practices.
Key requirements:
- Comprehensive project characterization
- Language ecosystem detection
- Documentation quality assessment
- Project complexity evaluation
- Performance optimization for large repositories
- Extensible architecture for new analysis types
## Decision
We will implement a multi-layered repository analysis engine that examines repositories from multiple perspectives to build comprehensive project profiles.
### Analysis Layers:
#### 1. File System Analysis Layer
- **Recursive directory traversal** with intelligent filtering
- **File categorization** by extension and content patterns
- **Metrics calculation**: file counts, lines of code, directory depth, size distributions
- **Ignore pattern handling**: .gitignore, common build artifacts, node_modules
#### 2. Language Ecosystem Analysis Layer
- **Package manager detection**: package.json, requirements.txt, Cargo.toml, go.mod, etc.
- **Dependency analysis**: direct and transitive dependencies
- **Build tool identification**: webpack, vite, gradle, maven, cargo, etc.
- **Version constraint analysis**: compatibility requirements
#### 3. Content Analysis Layer
- **Documentation quality assessment**: README analysis, existing docs
- **Code comment analysis**: inline documentation patterns
- **API surface detection**: public interfaces, exported functions
- **Content gap identification**: missing documentation areas
#### 4. Project Metadata Analysis Layer
- **Git history patterns**: commit frequency, contributor activity
- **Release management**: tagging patterns, version schemes
- **Issue tracking**: GitHub issues, project management indicators
- **Community engagement**: contributor count, activity patterns
#### 5. Complexity Assessment Layer
- **Architectural complexity**: microservices, modular design patterns
- **Technical complexity**: multi-language projects, advanced configurations
- **Maintenance indicators**: test coverage, CI/CD presence, code quality metrics
- **Documentation sophistication needs**: API complexity, user journey complexity
## Alternatives Considered
### Single-Pass Analysis
- **Pros**: Simpler implementation, faster for small repositories
- **Cons**: Limited depth, cannot build sophisticated project profiles
- **Decision**: Rejected due to insufficient intelligence for quality recommendations
### External Tool Integration (e.g., GitHub API, CodeClimate)
- **Pros**: Rich metadata, established metrics
- **Cons**: External dependencies, rate limiting, requires authentication
- **Decision**: Rejected for core analysis; may integrate as optional enhancement
### Machine Learning-Based Analysis
- **Pros**: Could learn patterns from successful documentation projects
- **Cons**: Training data requirements, model maintenance, unpredictable results
- **Decision**: Deferred to future versions; start with rule-based analysis
### Database-Backed Caching
- **Pros**: Faster repeat analysis, could store learning patterns
- **Cons**: Deployment complexity, staleness issues, synchronization problems
- **Decision**: Rejected for initial version; implement in-memory caching only
## Consequences
### Positive
- **Intelligent Recommendations**: Deep analysis enables sophisticated SSG matching
- **Extensible Architecture**: Easy to add new analysis dimensions
- **Performance Optimization**: Layered approach allows selective analysis depth
- **Quality Assessment**: Can identify and improve existing documentation
- **Future-Proof**: Architecture supports ML integration and advanced analytics
### Negative
- **Analysis Time**: Comprehensive analysis may be slower for large repositories
- **Complexity**: Multi-layered architecture requires careful coordination
- **Memory Usage**: Full repository analysis requires significant memory for large projects
### Risks and Mitigations
- **Performance**: Implement streaming analysis and configurable depth limits
- **Accuracy**: Validate analysis results against known project types
- **Maintenance**: Regular testing against diverse repository types
## Implementation Details
### Analysis Engine Structure
```typescript
interface RepositoryAnalysis {
fileSystem: FileSystemAnalysis;
languageEcosystem: LanguageEcosystemAnalysis;
content: ContentAnalysis;
metadata: ProjectMetadataAnalysis;
complexity: ComplexityAssessment;
}
interface AnalysisLayer {
analyze(repositoryPath: string): Promise<LayerResult>;
getMetrics(): AnalysisMetrics;
validate(): ValidationResult;
}
```
### Performance Optimizations
- **Parallel Analysis**: Independent layers run concurrently
- **Intelligent Filtering**: Skip irrelevant files and directories early
- **Progressive Analysis**: Start with lightweight analysis, deepen as needed
- **Caching Strategy**: Cache analysis results within session scope
- **Size Limits**: Configurable limits for very large repositories
### File Pattern Recognition
```typescript
const FILE_PATTERNS = {
documentation: [".md", ".rst", ".adoc", "docs/", "documentation/"],
configuration: ["config/", ".config/", "*.json", "*.yaml", "*.toml"],
source: ["src/", "lib/", "*.js", "*.ts", "*.py", "*.go", "*.rs"],
tests: ["test/", "tests/", "__tests__/", "*.test.*", "*.spec.*"],
build: ["build/", "dist/", "target/", "bin/", "*.lock"],
};
```
### Language Ecosystem Detection
```typescript
const ECOSYSTEM_INDICATORS = {
javascript: ["package.json", "node_modules/", "yarn.lock", "pnpm-lock.yaml"],
python: ["requirements.txt", "setup.py", "pyproject.toml", "Pipfile"],
rust: ["Cargo.toml", "Cargo.lock", "src/main.rs"],
go: ["go.mod", "go.sum", "main.go"],
java: ["pom.xml", "build.gradle", "gradlew"],
};
```
### Complexity Scoring Algorithm
```typescript
interface ComplexityFactors {
fileCount: number;
languageCount: number;
dependencyCount: number;
directoryDepth: number;
contributorCount: number;
apiSurfaceSize: number;
}
function calculateComplexityScore(factors: ComplexityFactors): ComplexityScore {
// Weighted scoring algorithm balancing multiple factors
// Returns: 'simple' | 'moderate' | 'complex' | 'enterprise'
}
```
## Quality Assurance
### Testing Strategy
- **Unit Tests**: Each analysis layer tested independently
- **Integration Tests**: Full analysis pipeline validation
- **Repository Fixtures**: Test suite with diverse project types
- **Performance Tests**: Analysis time benchmarks for various repository sizes
- **Accuracy Validation**: Manual verification against known project characteristics
### Monitoring and Metrics
- Analysis execution time by repository size
- Accuracy of complexity assessments
- Cache hit rates and memory usage
- Error rates and failure modes
## Future Enhancements
### Machine Learning Integration
- Pattern recognition for project types
- Automated documentation quality scoring
- Predictive analysis for maintenance needs
### Advanced Analytics
- Historical trend analysis
- Comparative analysis across similar projects
- Community best practice identification
### Performance Optimizations
- WebAssembly modules for intensive analysis
- Distributed analysis for very large repositories
- Incremental analysis for updated repositories
## Security Considerations
- **File System Access**: Restricted to repository boundaries
- **Content Scanning**: No sensitive data extraction or storage
- **Resource Limits**: Prevent resource exhaustion attacks
- **Input Validation**: Sanitize all repository paths and content
## Implementation Status
**Status**: ✅ Implemented (2025-12-12)
**Implementation Files**:
- `src/tools/analyze-repository.ts` - Main repository analysis tool
- `src/utils/code-scanner.ts` - Code scanning and analysis utilities
- `src/memory/knowledge-graph.ts` - Knowledge graph integration for storing analysis results
**Key Features Implemented**:
- ✅ Multi-layered analysis (file system, language ecosystem, content, metadata, complexity)
- ✅ Dependency detection and analysis
- ✅ Documentation quality assessment
- ✅ Project complexity evaluation
- ✅ Knowledge graph integration for historical tracking
- ✅ Progress reporting and context-aware analysis
**Validation**: The implementation has been validated against the architectural design and is actively used by other tools (SSG recommendation, content population, drift detection).
## References
- [Git Repository Analysis Best Practices](https://git-scm.com/docs)
- [Static Analysis Tools Comparison](https://analysis-tools.dev/)
- [Repository Metrics Standards](https://chaoss.community/)
- Commit: 577a312 - feat: Extend knowledge graph with documentation example entities (#78)
- GitHub Issue: #77 - Knowledge graph extensions (referenced in commit)
- GitHub Issue: #78 - Extend knowledge graph with documentation example entities
```
--------------------------------------------------------------------------------
/src/benchmarks/performance.ts:
--------------------------------------------------------------------------------
```typescript
// Performance benchmarking system per PERF-001 rules
import { promises as fs } from "fs";
import path from "path";
import { analyzeRepository } from "../tools/analyze-repository.js";
export interface BenchmarkResult {
repoSize: "small" | "medium" | "large";
fileCount: number;
executionTime: number;
targetTime: number;
passed: boolean;
performanceRatio: number;
details: {
startTime: number;
endTime: number;
memoryUsage: NodeJS.MemoryUsage;
};
}
export interface BenchmarkSuite {
testName: string;
results: BenchmarkResult[];
overallPassed: boolean;
averagePerformance: number;
summary: {
smallRepos: { count: number; avgTime: number; passed: number };
mediumRepos: { count: number; avgTime: number; passed: number };
largeRepos: { count: number; avgTime: number; passed: number };
};
}
// PERF-001 performance targets
const PERFORMANCE_TARGETS = {
small: 1000, // <1 second for <100 files
medium: 10000, // <10 seconds for 100-1000 files
large: 60000, // <60 seconds for 1000+ files
} as const;
export class PerformanceBenchmarker {
private results: BenchmarkResult[] = [];
async benchmarkRepository(
repoPath: string,
depth: "quick" | "standard" | "deep" = "standard",
): Promise<BenchmarkResult> {
const fileCount = await this.getFileCount(repoPath);
const repoSize = this.categorizeRepoSize(fileCount);
const targetTime = PERFORMANCE_TARGETS[repoSize];
// Capture initial memory state
const initialMemory = process.memoryUsage();
const startTime = Date.now();
try {
// Run the actual analysis
await analyzeRepository({ path: repoPath, depth });
const endTime = Date.now();
const executionTime = endTime - startTime;
const finalMemory = process.memoryUsage();
const performanceRatio = executionTime / targetTime;
const passed = executionTime <= targetTime;
const result: BenchmarkResult = {
repoSize,
fileCount,
executionTime,
targetTime,
passed,
performanceRatio,
details: {
startTime,
endTime,
memoryUsage: {
rss: finalMemory.rss - initialMemory.rss,
heapTotal: finalMemory.heapTotal - initialMemory.heapTotal,
heapUsed: finalMemory.heapUsed - initialMemory.heapUsed,
external: finalMemory.external - initialMemory.external,
arrayBuffers: finalMemory.arrayBuffers - initialMemory.arrayBuffers,
},
},
};
this.results.push(result);
return result;
} catch (error) {
const endTime = Date.now();
const executionTime = endTime - startTime;
// Even failed executions should be benchmarked
const result: BenchmarkResult = {
repoSize,
fileCount,
executionTime,
targetTime,
passed: false, // Failed execution = failed performance
performanceRatio: executionTime / targetTime,
details: {
startTime,
endTime,
memoryUsage: process.memoryUsage(),
},
};
this.results.push(result);
throw error;
}
}
async runBenchmarkSuite(
testRepos: Array<{ path: string; name: string }>,
): Promise<BenchmarkSuite> {
console.log("🚀 Starting performance benchmark suite...\n");
const results: BenchmarkResult[] = [];
for (const repo of testRepos) {
console.log(`📊 Benchmarking: ${repo.name}`);
try {
const result = await this.benchmarkRepository(repo.path);
results.push(result);
const status = result.passed ? "✅ PASS" : "❌ FAIL";
const ratio = (result.performanceRatio * 100).toFixed(1);
console.log(
` ${status} ${result.executionTime}ms (${ratio}% of target) - ${result.repoSize} repo with ${result.fileCount} files`,
);
} catch (error) {
console.log(` ❌ ERROR: ${error}`);
}
}
console.log("\n📈 Generating performance summary...\n");
return this.generateSuite("Full Benchmark Suite", results);
}
generateSuite(testName: string, results: BenchmarkResult[]): BenchmarkSuite {
const overallPassed = results.every((r) => r.passed);
const averagePerformance =
results.reduce((sum, r) => sum + r.performanceRatio, 0) / results.length;
// Categorize results
const smallRepos = results.filter((r) => r.repoSize === "small");
const mediumRepos = results.filter((r) => r.repoSize === "medium");
const largeRepos = results.filter((r) => r.repoSize === "large");
const suite: BenchmarkSuite = {
testName,
results,
overallPassed,
averagePerformance,
summary: {
smallRepos: {
count: smallRepos.length,
avgTime:
smallRepos.reduce((sum, r) => sum + r.executionTime, 0) /
smallRepos.length || 0,
passed: smallRepos.filter((r) => r.passed).length,
},
mediumRepos: {
count: mediumRepos.length,
avgTime:
mediumRepos.reduce((sum, r) => sum + r.executionTime, 0) /
mediumRepos.length || 0,
passed: mediumRepos.filter((r) => r.passed).length,
},
largeRepos: {
count: largeRepos.length,
avgTime:
largeRepos.reduce((sum, r) => sum + r.executionTime, 0) /
largeRepos.length || 0,
passed: largeRepos.filter((r) => r.passed).length,
},
},
};
return suite;
}
printDetailedReport(suite: BenchmarkSuite): void {
console.log(`📋 Performance Benchmark Report: ${suite.testName}`);
console.log("=".repeat(60));
console.log(
`Overall Status: ${suite.overallPassed ? "✅ PASSED" : "❌ FAILED"}`,
);
console.log(
`Average Performance: ${(suite.averagePerformance * 100).toFixed(
1,
)}% of target`,
);
console.log(`Total Tests: ${suite.results.length}\n`);
// Summary by repo size
console.log("📊 Performance by Repository Size:");
console.log("-".repeat(40));
const categories = [
{
name: "Small (<100 files)",
data: suite.summary.smallRepos,
target: PERFORMANCE_TARGETS.small,
},
{
name: "Medium (100-1000 files)",
data: suite.summary.mediumRepos,
target: PERFORMANCE_TARGETS.medium,
},
{
name: "Large (1000+ files)",
data: suite.summary.largeRepos,
target: PERFORMANCE_TARGETS.large,
},
];
categories.forEach((cat) => {
if (cat.data.count > 0) {
const passRate = ((cat.data.passed / cat.data.count) * 100).toFixed(1);
const avgTime = cat.data.avgTime.toFixed(0);
const targetTime = (cat.target / 1000).toFixed(1);
console.log(`${cat.name}:`);
console.log(
` Tests: ${cat.data.count} | Passed: ${cat.data.passed}/${cat.data.count} (${passRate}%)`,
);
console.log(` Avg Time: ${avgTime}ms | Target: <${targetTime}s`);
console.log("");
}
});
// Detailed results
console.log("🔍 Detailed Results:");
console.log("-".repeat(40));
suite.results.forEach((result, i) => {
const status = result.passed ? "✅" : "❌";
const ratio = (result.performanceRatio * 100).toFixed(1);
const memoryMB = (
result.details.memoryUsage.heapUsed /
1024 /
1024
).toFixed(1);
console.log(
`${status} Test ${i + 1}: ${
result.executionTime
}ms (${ratio}% of target)`,
);
console.log(
` Size: ${result.repoSize} (${result.fileCount} files) | Memory: ${memoryMB}MB heap`,
);
});
console.log("\n" + "=".repeat(60));
}
exportResults(suite: BenchmarkSuite, outputPath: string): Promise<void> {
const report = {
timestamp: new Date().toISOString(),
suite,
systemInfo: {
node: process.version,
platform: process.platform,
arch: process.arch,
memoryUsage: process.memoryUsage(),
},
performanceTargets: PERFORMANCE_TARGETS,
};
return fs.writeFile(outputPath, JSON.stringify(report, null, 2));
}
private async getFileCount(repoPath: string): Promise<number> {
let fileCount = 0;
async function countFiles(dir: string, level = 0): Promise<void> {
if (level > 10) return; // Prevent infinite recursion
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.name.startsWith(".") && entry.name !== ".github") continue;
if (entry.name === "node_modules" || entry.name === "vendor")
continue;
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
await countFiles(fullPath, level + 1);
} else {
fileCount++;
}
}
} catch (error) {
// Skip inaccessible directories
}
}
await countFiles(repoPath);
return fileCount;
}
private categorizeRepoSize(fileCount: number): "small" | "medium" | "large" {
if (fileCount < 100) return "small";
if (fileCount < 1000) return "medium";
return "large";
}
// Utility method to clear results for fresh benchmarking
reset(): void {
this.results = [];
}
// Get current benchmark results
getResults(): BenchmarkResult[] {
return [...this.results];
}
}
// Factory function for easy usage
export function createBenchmarker(): PerformanceBenchmarker {
return new PerformanceBenchmarker();
}
```
--------------------------------------------------------------------------------
/docs/how-to/drift-priority-scoring.md:
--------------------------------------------------------------------------------
```markdown
# Prioritizing Documentation Drift Updates
This guide explains how to use the priority scoring system to identify and triage documentation drift based on importance and urgency.
## Overview
The priority scoring system ranks documentation drift issues by importance, helping teams focus on the most critical documentation updates first. Each drift detection result receives a comprehensive priority score based on multiple factors.
## Priority Levels
| Level | Score Range | SLA | Description |
| ------------ | ----------- | ---------------------- | ------------------------------------------------------- |
| **Critical** | 80-100 | Update immediately | Breaking changes in heavily-used APIs with complex code |
| **High** | 60-79 | Update within 1 day | Major changes affecting documented features |
| **Medium** | 40-59 | Update within 1 week | Multiple minor changes or stale documentation |
| **Low** | 0-39 | Update when convenient | Patch-level changes or well-documented code |
## Scoring Factors
The priority score (0-100) is calculated using six weighted factors:
### 1. Code Complexity (20% weight)
Measures how complex the changed code is:
- Uses AST analysis complexity metrics
- Higher complexity = higher priority
- Adjusted by drift severity (critical/high/medium/low)
**Example**: A complex algorithm change scores higher than a simple utility function change.
### 2. Usage Frequency (25% weight)
Estimates how often the code is used:
- Based on export count (public APIs)
- Documentation references
- Optional: Actual usage metrics (function calls, imports)
**Example**: A widely-imported authentication function scores higher than an internal helper.
### 3. Change Magnitude (25% weight)
Evaluates the size and impact of changes:
- Breaking changes: 100 (maximum priority)
- Major changes: 20 points each
- Minor changes: 8 points each
**Example**: Changing a function signature (breaking) scores 100, while adding a parameter with default (major) scores 20.
### 4. Documentation Coverage (15% weight)
Inverted scoring - lower coverage = higher priority:
- Missing documentation: 90
- Partially documented: 40-80 (based on coverage ratio)
- Well documented: 0-40
**Example**: Undocumented new features score higher than changes to well-documented APIs.
### 5. Staleness (10% weight)
Based on how long since documentation was last updated:
- 90+ days old: 100
- 30-90 days: 80
- 14-30 days: 60
- 7-14 days: 40
- Less than 7 days: 20
**Example**: Documentation untouched for 3 months scores higher than recently updated docs.
### 6. User Feedback (5% weight)
Future integration with issue tracking:
- Currently returns 0 (placeholder)
- Will incorporate reported documentation issues
- User complaints increase priority
## Basic Usage
### Detect Drift with Priority Scores
```typescript
import { DriftDetector } from "./utils/drift-detector.js";
const detector = new DriftDetector(projectPath);
await detector.initialize();
// Create snapshots
const oldSnapshot = await detector.loadLatestSnapshot();
const newSnapshot = await detector.createSnapshot(projectPath, docsPath);
// Detect drift with priority scoring
const results = await detector.detectDriftWithPriority(
oldSnapshot,
newSnapshot,
);
for (const result of results) {
console.log(`File: ${result.filePath}`);
console.log(`Priority: ${result.priorityScore.recommendation}`);
console.log(`Score: ${result.priorityScore.overall}/100`);
console.log(`Action: ${result.priorityScore.suggestedAction}`);
}
```
### Get Prioritized Results
Results sorted by priority (highest first):
```typescript
const prioritizedResults = await detector.getPrioritizedDriftResults(
oldSnapshot,
newSnapshot,
);
// Handle critical issues first
const critical = prioritizedResults.filter(
(r) => r.priorityScore?.recommendation === "critical",
);
for (const result of critical) {
console.log(`URGENT: ${result.filePath}`);
console.log(`Breaking changes: ${result.impactAnalysis.breakingChanges}`);
}
```
## Advanced Configuration
### Custom Weights
Adjust scoring weights based on your team's priorities:
```typescript
const detector = new DriftDetector(projectPath);
// Emphasize change magnitude and usage frequency
detector.setCustomWeights({
changeMagnitude: 0.35, // Increased from 0.25
usageFrequency: 0.3, // Increased from 0.25
codeComplexity: 0.15, // Decreased from 0.20
documentationCoverage: 0.1,
staleness: 0.08,
userFeedback: 0.02,
});
```
**Note**: Weights are applied as-is in the weighted sum calculation. The default weights sum to 1.0, but custom weights don't need to. Partial updates merge with defaults.
### Usage Metadata
Provide actual usage metrics for more accurate scoring:
```typescript
import { UsageMetadata } from "./utils/drift-detector.js";
const usageMetadata: UsageMetadata = {
filePath: "/src/api/auth.ts",
functionCalls: new Map([
["authenticate", 1500], // Called 1500 times
["validateToken", 800],
]),
classInstantiations: new Map([["AuthManager", 50]]),
imports: new Map([
["authenticate", 25], // Imported by 25 files
]),
};
const results = await detector.detectDriftWithPriority(
oldSnapshot,
newSnapshot,
usageMetadata,
);
```
## Integration Examples
### CI/CD Pipeline
Fail builds for critical drift:
```typescript
const results = await detector.getPrioritizedDriftResults(
oldSnapshot,
newSnapshot,
);
const criticalCount = results.filter(
(r) => r.priorityScore?.recommendation === "critical",
).length;
if (criticalCount > 0) {
console.error(`❌ ${criticalCount} critical documentation drift issues`);
process.exit(1);
}
```
### Task Management Integration
Export to GitHub Issues or Jira:
```typescript
for (const result of prioritizedResults) {
const score = result.priorityScore;
await createIssue({
title: `[${score.recommendation.toUpperCase()}] Update docs for ${
result.filePath
}`,
body: `
## Priority Score: ${score.overall}/100
${score.suggestedAction}
### Factors:
- Code Complexity: ${score.factors.codeComplexity}
- Usage Frequency: ${score.factors.usageFrequency}
- Change Magnitude: ${score.factors.changeMagnitude}
- Coverage: ${score.factors.documentationCoverage}
- Staleness: ${score.factors.staleness}
### Impact:
- Breaking: ${result.impactAnalysis.breakingChanges}
- Major: ${result.impactAnalysis.majorChanges}
- Minor: ${result.impactAnalysis.minorChanges}
`.trim(),
labels: [score.recommendation, "documentation", "drift"],
priority: score.recommendation,
});
}
```
### Dashboard Visualization
Group by priority level:
```typescript
const byPriority = {
critical: results.filter(
(r) => r.priorityScore?.recommendation === "critical",
),
high: results.filter((r) => r.priorityScore?.recommendation === "high"),
medium: results.filter((r) => r.priorityScore?.recommendation === "medium"),
low: results.filter((r) => r.priorityScore?.recommendation === "low"),
};
console.log(`
📊 Documentation Drift Summary
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 Critical: ${byPriority.critical.length} (update immediately)
🟠 High: ${byPriority.high.length} (update within 1 day)
🟡 Medium: ${byPriority.medium.length} (update within 1 week)
🟢 Low: ${byPriority.low.length} (update when convenient)
`);
```
## Best Practices
### 1. Regular Scanning
Run drift detection regularly to catch issues early:
```bash
# Daily CI job
npm run drift:detect --priority-threshold=high
```
### 2. Triage Workflow
1. **Critical (immediate)**: Assign to on-call developer
2. **High (1 day)**: Add to current sprint
3. **Medium (1 week)**: Add to backlog
4. **Low (when convenient)**: Batch with other low-priority updates
### 3. Custom Weights by Project
Different projects have different priorities:
**API Library**:
- High weight on usage frequency (30%)
- High weight on breaking changes (30%)
**Internal Tool**:
- Lower weight on usage frequency (15%)
- Higher weight on complexity (25%)
### 4. Monitor Trends
Track priority scores over time:
```typescript
// Store scores in time series database
const metrics = {
timestamp: new Date(),
criticalCount: byPriority.critical.length,
averageScore:
results.reduce((sum, r) => sum + r.priorityScore.overall, 0) /
results.length,
totalDrift: results.length,
};
await metricsDB.insert(metrics);
```
## Troubleshooting
### Scores Seem Too High/Low
**Problem**: All drift scores high priority, or all low priority.
**Solutions**:
1. Adjust custom weights for your context
2. Verify snapshot data is accurate
3. Check if usage metadata is available
4. Review complexity calculations
### Missing Documentation Dominates
**Problem**: Missing docs always score 90, drowning out other issues.
**Solutions**:
1. Lower documentationCoverage weight
2. Focus on documented code first
3. Use filters: `results.filter(r => r.impactAnalysis.affectedDocFiles.length > 0)`
### Breaking Changes Not Prioritized
**Problem**: Breaking changes should be critical but aren't.
**Solutions**:
1. Increase changeMagnitude weight
2. Verify impact analysis is detecting breaking changes correctly
3. Check if other factors (low complexity, no docs) are pulling down score
## Related Documentation
- [Drift Detection System](./repository-analysis.md#drift-detection)
- [AST-Based Analysis](../explanation/ast-analysis.md)
- [CI/CD Integration](./github-pages-deployment.md)
## API Reference
See [DriftDetector API Reference](../reference/drift-detector.md) for complete method documentation.
```
--------------------------------------------------------------------------------
/docs/adrs/adr-0001-mcp-server-architecture.md:
--------------------------------------------------------------------------------
```markdown
---
id: adr-1-mcp-server-architecture
title: "ADR-001: MCP Server Architecture using TypeScript SDK"
sidebar_label: "ADR-001: MCP Server Architecture"
sidebar_position: 1
documcp:
last_updated: "2025-11-20T00:46:21.934Z"
last_validated: "2025-12-09T19:41:38.566Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# ADR-001: MCP Server Architecture using TypeScript SDK
## Status
Accepted
## Context
DocuMCP requires a robust server architecture that can integrate seamlessly with development environments like GitHub Copilot, Claude Desktop, and other MCP-enabled tools. The server needs to provide intelligent repository analysis, static site generator recommendations, and automated documentation deployment workflows.
Key requirements:
- Standards-compliant MCP protocol implementation
- Stateless operation for consistency and reliability
- Modular design separating concerns
- Integration with existing developer workflows
- Scalable architecture supporting complex multi-step operations
## Decision
We will implement the DocuMCP server using the TypeScript Model Context Protocol SDK, following a modular, stateless architecture pattern.
### Core Architectural Components:
1. **MCP Server Foundation**: TypeScript-based implementation using official MCP SDK
2. **Repository Analysis Engine**: Multi-layered analysis of project characteristics
3. **Static Site Generator Recommendation Engine**: Algorithmic decision framework
4. **File Generation and Template System**: Template-based configuration generation
5. **GitHub Integration Layer**: Automated deployment orchestration
### Design Principles:
- **Stateless Operation**: Each invocation analyzes current repository state
- **Modular Design**: Clear separation between analysis, recommendation, generation, and deployment
- **Standards Compliance**: Full adherence to MCP specification requirements
- **Session Context**: Temporary context preservation within single sessions for complex workflows
## Alternatives Considered
### Python-based Implementation
- **Pros**: Rich ecosystem for NLP and analysis, familiar to many developers
- **Cons**: Less mature MCP SDK, deployment complexity, slower startup times
- **Decision**: Rejected due to MCP ecosystem maturity in TypeScript
### Go-based Implementation
- **Pros**: High performance, excellent concurrency, small binary size
- **Cons**: Limited MCP SDK support, smaller ecosystem for documentation tools
- **Decision**: Rejected due to limited MCP tooling and development velocity concerns
### Stateful Server with Database
- **Pros**: Could cache analysis results, maintain user preferences
- **Cons**: Deployment complexity, synchronization issues, potential staleness
- **Decision**: Rejected to maintain simplicity and ensure consistency
## Consequences
### Positive
- **Developer Familiarity**: TypeScript is widely known in the target developer community
- **MCP Ecosystem**: Mature tooling and extensive documentation available
- **Rapid Development**: Rich ecosystem accelerates feature development
- **Integration**: Seamless integration with existing JavaScript/TypeScript tooling
- **Consistency**: Stateless design eliminates synchronization issues
- **Reliability**: Reduces complexity and potential failure modes
### Negative
- **Runtime Overhead**: Node.js runtime may have higher memory usage than compiled alternatives
- **Startup Time**: Node.js startup may be slower than Go or Rust alternatives
- **Dependency Management**: npm ecosystem can introduce supply chain complexity
### Risks and Mitigations
- **Supply Chain Security**: Use npm audit and dependency scanning in CI/CD
- **Performance**: Implement intelligent caching and optimize hot paths
- **Memory Usage**: Monitor and optimize memory allocation patterns
## Implementation Details
### Project Structure
```
src/
├── server/ # MCP server implementation
├── analysis/ # Repository analysis engine
├── recommendation/ # SSG recommendation logic
├── generation/ # File and template generation
├── deployment/ # GitHub integration
└── types/ # TypeScript type definitions
```
### Key Dependencies
- `@modelcontextprotocol/typescript-sdk`: MCP protocol implementation
- `typescript`: Type safety and development experience
- `zod`: Runtime type validation for MCP tools
- `yaml`: Configuration file parsing and generation
- `mustache`: Template rendering engine
- `simple-git`: Git repository interaction
### Error Handling Strategy
- Comprehensive input validation using Zod schemas
- Structured error responses with actionable guidance
- Graceful degradation for partial analysis failures
- Detailed logging for debugging and monitoring
## Compliance and Standards
- Full MCP specification compliance for protocol interactions
- JSON-RPC message handling with proper error codes
- Standardized tool parameter validation and responses
- Security best practices for file system access and Git operations
## Research Integration (2025-01-14)
### Performance Validation
**Research Findings Incorporated**: Comprehensive analysis validates our architectural decisions:
1. **TypeScript MCP SDK Performance**:
- ✅ JSON-RPC 2.0 protocol provides minimal communication overhead
- ✅ Native WebSocket/stdio transport layers optimize performance
- ✅ Type safety adds compile-time benefits without runtime performance cost
2. **Node.js Memory Optimization** (Critical for Repository Analysis):
- **Streaming Implementation**: 10x memory reduction for files >100MB
- **Worker Thread Pool**: 3-4x performance improvement for parallel processing
- **Memory-Mapped Files**: 5x speed improvement for large directory traversal
### Updated Implementation Strategy
Based on research validation, the architecture will implement:
```typescript
// Enhanced streaming approach for large repositories
class RepositoryAnalyzer {
private workerPool: WorkerPool;
private streamThreshold = 10 * 1024 * 1024; // 10MB
async analyzeRepository(repoPath: string): Promise<AnalysisResult> {
try {
const files = await this.scanDirectory(repoPath);
// Parallel processing with worker threads
const chunks = this.chunkFiles(files, this.workerPool.size);
const results = await Promise.allSettled(
chunks.map((chunk) => this.workerPool.execute("analyzeChunk", chunk)),
);
// Handle partial failures gracefully
const successfulResults = results
.filter(
(result): result is PromiseFulfilledResult<any> =>
result.status === "fulfilled",
)
.map((result) => result.value);
if (successfulResults.length === 0) {
throw new Error("All analysis chunks failed");
}
return this.aggregateResults(successfulResults);
} catch (error) {
throw new Error(`Repository analysis failed: ${error.message}`);
}
}
private async analyzeFile(filePath: string): Promise<FileAnalysis> {
try {
const stats = await fs.stat(filePath);
// Use streaming for large files
if (stats.size > this.streamThreshold) {
return await this.analyzeFileStream(filePath);
}
return await this.analyzeFileStandard(filePath);
} catch (error) {
throw new Error(`File analysis failed for ${filePath}: ${error.message}`);
}
}
}
```
### Performance Benchmarks
Research-validated performance targets:
- **Small Repositories** (<100 files): <1 second analysis time
- **Medium Repositories** (100-1000 files): <10 seconds analysis time
- **Large Repositories** (1000+ files): <60 seconds analysis time
- **Memory Usage**: Constant memory profile regardless of repository size
## Code Execution with MCP (CE-MCP) Compatibility (2025-12-09)
### Validation of Architectural Decisions
**Research Findings**: The emergence of Code Execution with MCP (CE-MCP) / Code Mode validates our architectural decisions:
1. **Stateless Design is Optimal**: Our stateless operation model (see Design Principles above) is perfect for Code Mode workflows where clients orchestrate tools through generated code
2. **Tool-Based Architecture**: The modular tool design enables seamless code generation and orchestration by CE-MCP clients
3. **Zero Migration Required**: documcp is already fully compatible with Code Mode clients (Claude Code, pctx, Cloudflare Workers AI)
### CE-MCP Performance Benefits
When used with Code Mode clients, documcp workflows achieve:
- **98.7% token reduction** through dynamic tool discovery
- **75x cost reduction** via summary-only results
- **60% faster execution** through parallel tool orchestration
- **19.2% fewer API calls** via direct code-based coordination
### Server vs Client Responsibilities
**documcp (Server) provides**:
- Standard MCP protocol tools (already implemented)
- Zod-validated schemas for type-safe code generation
- JSON-RPC interface for universal client compatibility
**Code Mode Clients handle**:
- Code generation (TypeScript/Python orchestration)
- Sandboxed execution (Docker, isolates)
- Tool discovery and filesystem navigation
- Security enforcement (AgentBound-style frameworks)
### Implementation Status
✅ **Full CE-MCP compatibility validated** (2025-12-09)
- MCP SDK upgraded to v1.24.0 (PR #69)
- All tests passing (91.67% coverage)
- No architectural changes required
For detailed analysis, see [ADR-011: CE-MCP Compatibility](adr-0011-ce-mcp-compatibility.md).
## Future Considerations
- Potential migration to WebAssembly for performance-critical components
- Plugin architecture for extensible SSG support
- Distributed analysis for large repository handling (validated by research)
- Machine learning integration for improved recommendations
- MCP Tasks API integration for long-running operations (SDK 1.24.0)
## References
- [MCP TypeScript SDK Documentation](https://github.com/modelcontextprotocol/typescript-sdk)
- [Model Context Protocol Specification](https://spec.modelcontextprotocol.io/)
- [TypeScript Performance Best Practices](https://github.com/microsoft/TypeScript/wiki/Performance)
```
--------------------------------------------------------------------------------
/src/tools/validate-documentation-freshness.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Validate Documentation Freshness Tool
*
* Validates documentation freshness, initializes metadata for files without it,
* and updates timestamps based on code changes.
*/
import { z } from "zod";
import path from "path";
import { simpleGit } from "simple-git";
import {
findMarkdownFiles,
parseDocFrontmatter,
updateDocFrontmatter,
initializeFreshnessMetadata,
STALENESS_PRESETS,
type DocFreshnessMetadata,
scanDocumentationFreshness,
} from "../utils/freshness-tracker.js";
import { type MCPToolResponse } from "../types/api.js";
import {
storeFreshnessEvent,
updateFreshnessEvent,
} from "../memory/freshness-kg-integration.js";
/**
* Input schema for validate_documentation_freshness tool
*/
export const ValidateDocumentationFreshnessSchema = z.object({
docsPath: z.string().describe("Path to documentation directory"),
projectPath: z
.string()
.describe("Path to project root (for git integration)"),
initializeMissing: z
.boolean()
.optional()
.default(true)
.describe("Initialize metadata for files without it"),
updateExisting: z
.boolean()
.optional()
.default(false)
.describe("Update last_validated timestamp for all files"),
updateFrequency: z
.enum(["realtime", "active", "recent", "weekly", "monthly", "quarterly"])
.optional()
.default("monthly")
.describe("Default update frequency for new metadata"),
validateAgainstGit: z
.boolean()
.optional()
.default(true)
.describe("Validate against current git commit"),
});
export type ValidateDocumentationFreshnessInput = z.input<
typeof ValidateDocumentationFreshnessSchema
>;
/**
* Validation result for a single file
*/
interface FileValidationResult {
filePath: string;
relativePath: string;
action: "initialized" | "updated" | "skipped" | "error";
metadata?: DocFreshnessMetadata;
error?: string;
}
/**
* Validation report
*/
interface ValidationReport {
validatedAt: string;
docsPath: string;
projectPath: string;
totalFiles: number;
initialized: number;
updated: number;
skipped: number;
errors: number;
currentCommit?: string;
files: FileValidationResult[];
}
/**
* Format validation report for display
*/
function formatValidationReport(report: ValidationReport): string {
let output = "# Documentation Freshness Validation Report\n\n";
output += `**Validated at**: ${new Date(
report.validatedAt,
).toLocaleString()}\n`;
output += `**Documentation path**: ${report.docsPath}\n`;
if (report.currentCommit) {
output += `**Current commit**: ${report.currentCommit.substring(0, 7)}\n`;
}
output += "\n## Summary\n\n";
output += `- **Total files**: ${report.totalFiles}\n`;
output += `- **Initialized**: ${report.initialized} files\n`;
output += `- **Updated**: ${report.updated} files\n`;
output += `- **Skipped**: ${report.skipped} files\n`;
if (report.errors > 0) {
output += `- **Errors**: ${report.errors} files\n`;
}
output += "\n## Actions Performed\n\n";
// Group by action
const grouped = {
initialized: report.files.filter((f) => f.action === "initialized"),
updated: report.files.filter((f) => f.action === "updated"),
error: report.files.filter((f) => f.action === "error"),
};
if (grouped.initialized.length > 0) {
output += `### ✨ Initialized (${grouped.initialized.length})\n\n`;
for (const file of grouped.initialized) {
output += `- ${file.relativePath}\n`;
}
output += "\n";
}
if (grouped.updated.length > 0) {
output += `### 🔄 Updated (${grouped.updated.length})\n\n`;
for (const file of grouped.updated) {
output += `- ${file.relativePath}\n`;
}
output += "\n";
}
if (grouped.error.length > 0) {
output += `### ❌ Errors (${grouped.error.length})\n\n`;
for (const file of grouped.error) {
output += `- ${file.relativePath}: ${file.error}\n`;
}
output += "\n";
}
// Recommendations
output += "## Next Steps\n\n";
if (report.initialized > 0) {
output += `→ ${report.initialized} files now have freshness tracking enabled\n`;
}
if (report.updated > 0) {
output += `→ ${report.updated} files have been marked as validated\n`;
}
output += `→ Run \`track_documentation_freshness\` to view current freshness status\n`;
return output;
}
/**
* Validate documentation freshness
*/
export async function validateDocumentationFreshness(
input: ValidateDocumentationFreshnessInput,
): Promise<MCPToolResponse> {
const startTime = Date.now();
try {
const {
docsPath,
projectPath,
initializeMissing,
updateExisting,
updateFrequency,
validateAgainstGit,
} = input;
// Get current git commit if requested
let currentCommit: string | undefined;
if (validateAgainstGit) {
try {
const git = simpleGit(projectPath);
const isRepo = await git.checkIsRepo();
if (isRepo) {
const log = await git.log({ maxCount: 1 });
currentCommit = log.latest?.hash;
}
} catch (error) {
// Git not available, continue without it
}
}
// Find all markdown files
const markdownFiles = await findMarkdownFiles(docsPath);
const results: FileValidationResult[] = [];
for (const filePath of markdownFiles) {
const relativePath = path.relative(docsPath, filePath);
try {
const frontmatter = await parseDocFrontmatter(filePath);
const hasMetadata = !!frontmatter.documcp?.last_updated;
if (!hasMetadata && initializeMissing) {
// Initialize metadata
await initializeFreshnessMetadata(filePath, {
updateFrequency,
autoUpdated: false,
});
// If git is available, set validated_against_commit
if (currentCommit) {
await updateDocFrontmatter(filePath, {
validated_against_commit: currentCommit,
});
}
const updatedFrontmatter = await parseDocFrontmatter(filePath);
results.push({
filePath,
relativePath,
action: "initialized",
metadata: updatedFrontmatter.documcp,
});
} else if (hasMetadata && updateExisting) {
// Update existing metadata
const updateData: Partial<DocFreshnessMetadata> = {
last_validated: new Date().toISOString(),
};
if (currentCommit) {
updateData.validated_against_commit = currentCommit;
}
await updateDocFrontmatter(filePath, updateData);
const updatedFrontmatter = await parseDocFrontmatter(filePath);
results.push({
filePath,
relativePath,
action: "updated",
metadata: updatedFrontmatter.documcp,
});
} else {
results.push({
filePath,
relativePath,
action: "skipped",
metadata: frontmatter.documcp,
});
}
} catch (error) {
results.push({
filePath,
relativePath,
action: "error",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
// Generate report
const report: ValidationReport = {
validatedAt: new Date().toISOString(),
docsPath,
projectPath,
totalFiles: markdownFiles.length,
initialized: results.filter((r) => r.action === "initialized").length,
updated: results.filter((r) => r.action === "updated").length,
skipped: results.filter((r) => r.action === "skipped").length,
errors: results.filter((r) => r.action === "error").length,
currentCommit,
files: results,
};
const formattedReport = formatValidationReport(report);
// Store validation event in knowledge graph
let eventId: string | undefined;
if (report.initialized > 0 || report.updated > 0) {
try {
// Scan current state to get freshness metrics
const scanReport = await scanDocumentationFreshness(docsPath, {
warning: STALENESS_PRESETS.monthly,
stale: {
value: STALENESS_PRESETS.monthly.value * 2,
unit: STALENESS_PRESETS.monthly.unit,
},
critical: {
value: STALENESS_PRESETS.monthly.value * 3,
unit: STALENESS_PRESETS.monthly.unit,
},
});
// Determine event type
const eventType = report.initialized > 0 ? "initialization" : "update";
// Store in KG
eventId = await storeFreshnessEvent(
projectPath,
docsPath,
scanReport,
eventType,
);
// Update event with validation details
await updateFreshnessEvent(eventId, {
filesInitialized: report.initialized,
filesUpdated: report.updated,
eventType,
});
} catch (error) {
// KG storage failed, but continue with the response
console.warn(
"Failed to store validation event in knowledge graph:",
error,
);
}
}
const response: MCPToolResponse = {
success: true,
data: {
summary: `Validated ${report.totalFiles} files: ${report.initialized} initialized, ${report.updated} updated`,
report,
formattedReport,
kgEventId: eventId,
},
metadata: {
toolVersion: "1.0.0",
executionTime: Date.now() - startTime,
timestamp: new Date().toISOString(),
},
recommendations: [],
};
return response;
} catch (error) {
return {
success: false,
error: {
code: "FRESHNESS_VALIDATION_FAILED",
message:
error instanceof Error
? error.message
: "Unknown error validating documentation freshness",
resolution:
"Check that the documentation and project paths exist and are readable",
},
metadata: {
toolVersion: "1.0.0",
executionTime: Date.now() - startTime,
timestamp: new Date().toISOString(),
},
};
}
}
```
--------------------------------------------------------------------------------
/tests/tools/generate-llm-context.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
import { promises as fs } from "fs";
import path from "path";
import os from "os";
import {
generateLLMContext,
setToolDefinitions,
GenerateLLMContextInputSchema,
} from "../../src/tools/generate-llm-context.js";
import { z } from "zod";
describe("generate_llm_context", () => {
let tmpDir: string;
beforeEach(async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "generate-llm-context-"));
// Set up mock tool definitions
const mockTools = [
{
name: "analyze_repository",
description: "Analyze repository structure and dependencies",
inputSchema: z.object({
path: z.string(),
depth: z.enum(["quick", "standard", "deep"]).optional(),
}),
},
{
name: "recommend_ssg",
description: "Recommend static site generator",
inputSchema: z.object({
analysisId: z.string(),
userId: z.string().optional(),
}),
},
{
name: "sync_code_to_docs",
description: "Synchronize code with documentation",
inputSchema: z.object({
projectPath: z.string(),
docsPath: z.string(),
mode: z.enum(["detect", "preview", "apply", "auto"]).optional(),
}),
},
];
setToolDefinitions(mockTools);
});
afterEach(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
});
describe("Basic Generation", () => {
it("should generate LLM context file with default options", async () => {
const result = await generateLLMContext({
projectPath: tmpDir,
});
// Check result structure
expect(result.content).toBeDefined();
expect(result.content[0].text).toContain("path");
// Check file exists
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
const fileExists = await fs
.access(outputPath)
.then(() => true)
.catch(() => false);
expect(fileExists).toBe(true);
// Check file content
const content = await fs.readFile(outputPath, "utf-8");
expect(content).toContain("# DocuMCP LLM Context Reference");
expect(content).toContain("analyze_repository");
expect(content).toContain("recommend_ssg");
expect(content).toContain("sync_code_to_docs");
});
it("should include examples when requested", async () => {
await generateLLMContext({
projectPath: tmpDir,
includeExamples: true,
});
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
const content = await fs.readFile(outputPath, "utf-8");
expect(content).toContain("**Example**:");
expect(content).toContain("```typescript");
});
it("should generate concise format", async () => {
await generateLLMContext({
projectPath: tmpDir,
format: "concise",
includeExamples: false,
});
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
const content = await fs.readFile(outputPath, "utf-8");
expect(content).toContain("# DocuMCP LLM Context Reference");
expect(content).not.toContain("**Parameters**:");
});
it("should generate detailed format with parameters", async () => {
await generateLLMContext({
projectPath: tmpDir,
format: "detailed",
});
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
const content = await fs.readFile(outputPath, "utf-8");
expect(content).toContain("# DocuMCP LLM Context Reference");
expect(content).toContain("**Parameters**:");
});
});
describe("Content Sections", () => {
it("should include overview section", async () => {
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
await generateLLMContext({ projectPath: tmpDir });
const content = await fs.readFile(outputPath, "utf-8");
expect(content).toContain("## Overview");
expect(content).toContain("DocuMCP is an intelligent MCP server");
});
it("should include core tools section", async () => {
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
await generateLLMContext({ projectPath: tmpDir });
const content = await fs.readFile(outputPath, "utf-8");
expect(content).toContain("## Core Documentation Tools");
});
it("should include Phase 3 tools section", async () => {
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
await generateLLMContext({ projectPath: tmpDir });
const content = await fs.readFile(outputPath, "utf-8");
expect(content).toContain(
"## Phase 3: Code-to-Docs Synchronization Tools",
);
expect(content).toContain("sync_code_to_docs");
});
it("should include memory system section", async () => {
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
await generateLLMContext({ projectPath: tmpDir });
const content = await fs.readFile(outputPath, "utf-8");
expect(content).toContain("## Memory Knowledge Graph System");
expect(content).toContain("### Entity Types");
expect(content).toContain("### Relationship Types");
});
it("should include workflows section", async () => {
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
await generateLLMContext({ projectPath: tmpDir });
const content = await fs.readFile(outputPath, "utf-8");
expect(content).toContain("## Common Workflows");
expect(content).toContain("### 1. New Documentation Site Setup");
});
it("should include quick reference table", async () => {
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
await generateLLMContext({ projectPath: tmpDir });
const content = await fs.readFile(outputPath, "utf-8");
expect(content).toContain("## Quick Reference Table");
expect(content).toContain("| Tool | Primary Use |");
});
});
describe("Input Validation", () => {
it("should validate input schema", () => {
expect(() => {
GenerateLLMContextInputSchema.parse({
projectPath: "/test/path",
includeExamples: true,
format: "detailed",
});
}).not.toThrow();
});
it("should use default values for optional fields", () => {
const result = GenerateLLMContextInputSchema.parse({
projectPath: "/test/path",
});
expect(result.projectPath).toBe("/test/path");
expect(result.includeExamples).toBe(true);
expect(result.format).toBe("detailed");
});
it("should require projectPath", () => {
expect(() => {
GenerateLLMContextInputSchema.parse({});
}).toThrow();
});
it("should reject invalid format", () => {
expect(() => {
GenerateLLMContextInputSchema.parse({
projectPath: "/test/path",
format: "invalid",
});
}).toThrow();
});
});
describe("Error Handling", () => {
it("should handle write errors gracefully", async () => {
const invalidPath = "/invalid/path/that/does/not/exist";
const result = await generateLLMContext({
projectPath: invalidPath,
});
expect(result.content[0].text).toContain("GENERATION_ERROR");
expect(result.isError).toBe(true);
});
});
describe("File Output", () => {
it("should create LLM_CONTEXT.md in project root", async () => {
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
await generateLLMContext({ projectPath: tmpDir });
const fileExists = await fs
.access(outputPath)
.then(() => true)
.catch(() => false);
expect(fileExists).toBe(true);
});
it("should overwrite existing file", async () => {
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
// Write first time
await generateLLMContext({ projectPath: tmpDir });
const firstContent = await fs.readFile(outputPath, "utf-8");
// Wait a moment to ensure timestamp changes
await new Promise((resolve) => setTimeout(resolve, 10));
// Write second time
await generateLLMContext({ projectPath: tmpDir });
const secondContent = await fs.readFile(outputPath, "utf-8");
// Content should be different (timestamp changed)
expect(firstContent).not.toEqual(secondContent);
});
it("should report correct file stats", async () => {
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
const result = await generateLLMContext({ projectPath: tmpDir });
const data = JSON.parse(result.content[0].text);
expect(data.stats).toBeDefined();
expect(data.stats.totalTools).toBe(3);
expect(data.stats.fileSize).toBeGreaterThan(0);
expect(data.stats.sections).toBeInstanceOf(Array);
});
});
describe("Tool Extraction", () => {
it("should extract tool names correctly", async () => {
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
await generateLLMContext({ projectPath: tmpDir });
const content = await fs.readFile(outputPath, "utf-8");
expect(content).toContain("`analyze_repository`");
expect(content).toContain("`recommend_ssg`");
expect(content).toContain("`sync_code_to_docs`");
});
it("should extract tool descriptions", async () => {
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
await generateLLMContext({ projectPath: tmpDir });
const content = await fs.readFile(outputPath, "utf-8");
expect(content).toContain(
"Analyze repository structure and dependencies",
);
expect(content).toContain("Recommend static site generator");
});
it("should handle tools with no examples", async () => {
const outputPath = path.join(tmpDir, "LLM_CONTEXT.md");
await generateLLMContext({ projectPath: tmpDir, includeExamples: true });
const content = await fs.readFile(outputPath, "utf-8");
// recommend_ssg doesn't have an example defined
const ssgSection = content.match(
/### `recommend_ssg`[\s\S]*?(?=###|$)/,
)?.[0];
expect(ssgSection).toBeDefined();
});
});
});
```