This is page 4 of 23. Use http://codebase.md/tosin2013/documcp?lines=false&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/llm-integration.md:
--------------------------------------------------------------------------------
```markdown
---
id: llm-integration
title: LLM Integration for Semantic Code Analysis
sidebar_label: LLM Integration
sidebar_position: 10
---
# LLM Integration for Semantic Code Analysis
DocuMCP now includes an optional LLM integration layer that enables semantic analysis of code changes beyond AST-based syntax comparison. This feature supports the DocuMCP Orchestrator's requirements for intelligent documentation synchronization.
## Overview
The LLM integration provides:
- **Semantic code change analysis**: Detect behavioral changes within the same function signature
- **Code execution simulation**: Validate documentation examples without running code
- **Intelligent documentation suggestions**: Generate context-aware update recommendations
- **Multi-provider support**: DeepSeek, OpenAI, Anthropic, and Ollama
- **Graceful fallback**: Automatic fallback to AST-only analysis when LLM is unavailable
## Configuration
### Environment Variables
Configure the LLM integration using environment variables:
```bash
# Required: API key for your chosen provider
export DOCUMCP_LLM_API_KEY="your-api-key-here"
# Optional: Provider selection (default: deepseek)
export DOCUMCP_LLM_PROVIDER="deepseek" # or "openai", "anthropic", "ollama"
# Optional: Model name (default: deepseek-chat)
export DOCUMCP_LLM_MODEL="deepseek-chat"
# Optional: Custom base URL (for self-hosted or alternative endpoints)
export DOCUMCP_LLM_BASE_URL="https://api.deepseek.com/v1"
```
### Supported Providers
#### DeepSeek (Default)
```bash
export DOCUMCP_LLM_PROVIDER="deepseek"
export DOCUMCP_LLM_API_KEY="sk-..."
export DOCUMCP_LLM_MODEL="deepseek-chat"
```
#### OpenAI
```bash
export DOCUMCP_LLM_PROVIDER="openai"
export DOCUMCP_LLM_API_KEY="sk-..."
export DOCUMCP_LLM_MODEL="gpt-4"
```
#### Anthropic
```bash
export DOCUMCP_LLM_PROVIDER="anthropic"
export DOCUMCP_LLM_API_KEY="sk-ant-..."
export DOCUMCP_LLM_MODEL="claude-3-opus-20240229"
```
#### Ollama (Local)
```bash
export DOCUMCP_LLM_PROVIDER="ollama"
export DOCUMCP_LLM_BASE_URL="http://localhost:11434/v1"
export DOCUMCP_LLM_MODEL="codellama"
# No API key needed for local Ollama
```
## Usage
### Semantic Code Analysis
```typescript
import { SemanticAnalyzer } from "./utils/semantic-analyzer.js";
// Create analyzer with default configuration
const analyzer = new SemanticAnalyzer();
await analyzer.initialize();
// Analyze semantic impact of code changes
const codeBefore = `
function multiply(a: number, b: number): number {
return a * b;
}
`;
const codeAfter = `
function multiply(a: number, b: number): number {
return a + b; // Bug: changed to addition!
}
`;
const analysis = await analyzer.analyzeSemanticImpact(
codeBefore,
codeAfter,
"multiply",
);
console.log("Analysis mode:", analysis.analysisMode); // 'llm', 'ast', or 'hybrid'
console.log("Behavioral change:", analysis.hasBehavioralChange); // true
console.log("Breaking for examples:", analysis.breakingForExamples); // true
console.log("Description:", analysis.changeDescription);
console.log("Confidence:", analysis.confidence);
console.log("Affected sections:", analysis.affectedDocSections);
```
### Validating Documentation Examples
```typescript
// Validate that documentation examples work with current implementation
const examples = [
"const result = multiply(6, 7); // Should return 42",
"const doubled = multiply(21, 2); // Should return 42",
];
const implementation = `
function multiply(a: number, b: number): number {
return a * b;
}
`;
const validation = await analyzer.validateExamples(examples, implementation);
console.log("Valid:", validation.isValid);
console.log("Confidence:", validation.overallConfidence);
console.log("Manual review needed:", validation.requiresManualReview);
// Check individual examples
validation.examples.forEach((ex, i) => {
console.log(`Example ${i + 1}:`);
console.log(" Valid:", ex.isValid);
console.log(" Issues:", ex.issues);
});
```
### Batch Analysis
```typescript
const changes = [
{
before: "function add(x: number, y: number) { return x + y; }",
after: "function add(x: number, y: number) { return x - y; }",
name: "add",
},
{
before: "function greet(name: string) { return `Hello ${name}`; }",
after: "function greet(name: string) { return `Hi ${name}!`; }",
name: "greet",
},
];
const results = await analyzer.analyzeBatch(changes);
results.forEach((result, i) => {
console.log(`Change ${i + 1}:`, result.changeDescription);
});
```
### Custom Configuration
```typescript
import {
SemanticAnalyzer,
createSemanticAnalyzer,
} from "./utils/semantic-analyzer.js";
// Disable LLM (AST-only mode)
const astOnlyAnalyzer = createSemanticAnalyzer({
useLLM: false,
});
// Custom confidence threshold for hybrid mode
const strictAnalyzer = createSemanticAnalyzer({
confidenceThreshold: 0.9, // Higher threshold = more likely to use hybrid mode
});
// Custom LLM configuration
const customAnalyzer = createSemanticAnalyzer({
llmConfig: {
provider: "openai",
apiKey: "custom-key",
model: "gpt-4",
},
});
```
## Analysis Modes
The semantic analyzer operates in three modes:
### LLM Mode
- **When**: LLM is available and confidence is above threshold (default: 0.7)
- **Advantages**: Deep semantic understanding, detects behavioral changes
- **Use case**: Critical code changes affecting public APIs
### AST Mode
- **When**: LLM is unavailable or disabled
- **Advantages**: Fast, reliable, no external dependencies
- **Use case**: Quick syntax checks, CI/CD environments without LLM access
### Hybrid Mode
- **When**: LLM confidence is below threshold
- **Advantages**: Combines LLM insights with AST verification
- **Use case**: Complex changes requiring both semantic and structural analysis
## Rate Limiting
The LLM client includes built-in rate limiting to prevent API quota exhaustion:
- Default: 10 requests per minute
- Automatic backoff when limit is reached
- Configurable per-instance
## Error Handling
The integration is designed to fail gracefully:
```typescript
// If LLM fails, analyzer falls back to AST mode
const analyzer = new SemanticAnalyzer();
const result = await analyzer.analyzeSemanticImpact(before, after);
// Check which mode was used
if (result.analysisMode === "ast" && !result.llmAvailable) {
console.warn("LLM unavailable, using AST analysis only");
}
// Low confidence analysis
if (result.confidence < 0.5) {
console.warn("Low confidence analysis - manual review recommended");
}
```
## Best Practices
### 1. Set Appropriate Thresholds
```typescript
// For critical code paths
const criticalAnalyzer = createSemanticAnalyzer({
confidenceThreshold: 0.9, // High threshold
});
// For routine changes
const routineAnalyzer = createSemanticAnalyzer({
confidenceThreshold: 0.6, // Lower threshold
});
```
### 2. Check Availability Before Relying on LLM
```typescript
if (!analyzer.isLLMAvailable()) {
console.warn("LLM not configured - using AST analysis only");
}
```
### 3. Handle Low Confidence Results
```typescript
const result = await analyzer.analyzeSemanticImpact(before, after);
if (result.confidence < 0.7) {
// Trigger manual review workflow
console.log("Manual review required for:", result.changeDescription);
}
```
### 4. Use Batch Analysis for Multiple Changes
```typescript
// More efficient than individual calls
const results = await analyzer.analyzeBatch(changes);
```
### 5. Validate Examples Before Publishing
```typescript
const validation = await analyzer.validateExamples(examples, implementation);
if (!validation.isValid) {
console.error("Some examples may be invalid:");
validation.suggestions.forEach((s) => console.error(" -", s));
// Don't publish until examples are fixed
throw new Error("Invalid documentation examples detected");
}
```
## Integration with DocuMCP Orchestrator
This LLM integration layer is designed to support the [DocuMCP Orchestrator](https://github.com/tosin2013/documcp-orchestrator) requirements:
- **ADR-009**: Content Accuracy Validation Framework
- **ADR-010**: LLM-Validated Documentation Examples
The orchestrator uses these capabilities to:
1. Detect when code changes require documentation updates
2. Validate that documentation examples match code behavior
3. Generate intelligent update suggestions
4. Maintain documentation accuracy over time
## Troubleshooting
### LLM Not Available
**Symptom**: `analyzer.isLLMAvailable()` returns `false`
**Solutions**:
- Check that `DOCUMCP_LLM_API_KEY` is set
- Verify API key is valid
- For Ollama: ensure server is running at specified base URL
### Low Confidence Results
**Symptom**: `result.confidence < 0.7`
**Solutions**:
- Review the change manually
- Use hybrid mode by setting lower threshold
- Check if code change is particularly complex
### Rate Limit Errors
**Symptom**: Requests timing out or failing
**Solutions**:
- Reduce number of concurrent requests
- Increase rate limit window
- Use batch analysis for multiple changes
### Timeout Errors
**Symptom**: "LLM request timed out"
**Solutions**:
- Increase timeout in configuration
- Check network connectivity to LLM provider
- Consider using a faster model
## Security Considerations
1. **API Keys**: Never commit API keys to version control
2. **Code Privacy**: Be aware that code is sent to external LLM providers
3. **Rate Limits**: Monitor API usage to avoid unexpected costs
4. **Fallback**: System works without LLM for sensitive environments
## Performance
- **LLM Analysis**: ~2-5 seconds per code change
- **AST Analysis**: ~50-100ms per code change
- **Hybrid Analysis**: ~2-5 seconds (LLM) + ~100ms (AST)
- **Rate Limit**: 10 requests/minute (default)
## Future Enhancements
Planned improvements:
- Caching of LLM responses for identical code changes
- Support for additional LLM providers
- Fine-tuned models for specific languages
- Streaming responses for large code bases
- Confidence calibration based on historical accuracy
## Related Documentation
- [AST-based Code Analysis](../reference/ast-analyzer.md)
- [Drift Detection](../reference/drift-detector.md)
- [DocuMCP Orchestrator](https://github.com/tosin2013/documcp-orchestrator)
- [ADR-009: Content Accuracy Validation](../adrs/adr-0009-content-accuracy-validation-framework.md)
```
--------------------------------------------------------------------------------
/src/utils/llm-client.ts:
--------------------------------------------------------------------------------
```typescript
/**
* LLM Client for Semantic Code Analysis
*
* Provides a unified interface for multiple LLM providers (DeepSeek, OpenAI, Anthropic, Ollama)
* with rate limiting, error handling, and fallback mechanisms.
*/
export interface LLMConfig {
provider: "deepseek" | "openai" | "anthropic" | "ollama";
apiKey?: string;
baseUrl?: string;
model: string;
maxTokens?: number;
timeout?: number;
}
export interface SemanticAnalysis {
hasBehavioralChange: boolean;
breakingForExamples: boolean;
changeDescription: string;
affectedDocSections: string[];
confidence: number;
}
export interface SimulationResult {
success: boolean;
expectedOutput: string;
actualOutput: string;
matches: boolean;
differences: string[];
confidence: number;
}
export interface LLMResponse {
content: string;
usage?: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
}
export interface LLMClient {
complete(prompt: string): Promise<string>;
analyzeCodeChange(before: string, after: string): Promise<SemanticAnalysis>;
simulateExecution(
example: string,
implementation: string,
): Promise<SimulationResult>;
}
/**
* Rate limiter for API requests
*/
class RateLimiter {
private requests: number[] = [];
private readonly maxRequests: number;
private readonly windowMs: number;
constructor(maxRequests: number = 10, windowMs: number = 60000) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
}
async acquire(): Promise<void> {
const now = Date.now();
this.requests = this.requests.filter((time) => now - time < this.windowMs);
if (this.requests.length >= this.maxRequests) {
const oldestRequest = this.requests[0];
const waitTime = this.windowMs - (now - oldestRequest);
await new Promise((resolve) => setTimeout(resolve, waitTime));
return this.acquire();
}
this.requests.push(now);
}
}
/**
* DeepSeek LLM Client (OpenAI-compatible API)
*/
export class DeepSeekClient implements LLMClient {
private config: LLMConfig;
private rateLimiter: RateLimiter;
private available: boolean = true;
constructor(config: LLMConfig) {
this.config = {
baseUrl: config.baseUrl || "https://api.deepseek.com/v1",
maxTokens: config.maxTokens || 4000,
timeout: config.timeout || 30000,
...config,
};
this.rateLimiter = new RateLimiter(10, 60000);
}
/**
* Check if the LLM service is available
*/
isAvailable(): boolean {
return this.available && !!this.config.apiKey;
}
/**
* Generic completion method
*/
async complete(prompt: string): Promise<string> {
if (!this.isAvailable()) {
throw new Error(
"LLM client is not available. Check API key configuration.",
);
}
await this.rateLimiter.acquire();
const requestBody = {
model: this.config.model,
messages: [
{
role: "user",
content: prompt,
},
],
max_tokens: this.config.maxTokens,
temperature: 0.7,
};
try {
const controller = new AbortController();
const timeoutId = setTimeout(
() => controller.abort(),
this.config.timeout,
);
const response = await fetch(`${this.config.baseUrl}/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this.config.apiKey}`,
},
body: JSON.stringify(requestBody),
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`LLM API error: ${response.status} - ${errorText}`);
}
const data = (await response.json()) as any;
return data.choices[0]?.message?.content || "";
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
throw new Error("LLM request timed out");
}
throw error;
}
}
/**
* Analyze semantic impact of code changes
*/
async analyzeCodeChange(
before: string,
after: string,
): Promise<SemanticAnalysis> {
const prompt = `You are a code analysis expert. Compare these two code versions and analyze the semantic differences.
**Before:**
\`\`\`
${before}
\`\`\`
**After:**
\`\`\`
${after}
\`\`\`
Analyze and respond in JSON format with the following structure:
{
"hasBehavioralChange": boolean (true if behavior changed, not just syntax),
"breakingForExamples": boolean (true if existing examples would break),
"changeDescription": string (brief description of the change),
"affectedDocSections": string[] (list of documentation sections that need updates),
"confidence": number (0-1 score indicating analysis confidence)
}
Focus on:
1. Changes in function behavior (not just signature)
2. Changes in return values or side effects
3. Changes that would break existing usage examples
4. Changes that affect API contracts
5. Changes in error handling or edge cases`;
try {
const response = await this.complete(prompt);
// Extract JSON from response (handle markdown code blocks)
let jsonStr = response.trim();
if (jsonStr.startsWith("```json")) {
jsonStr = jsonStr.replace(/```json\n?/g, "").replace(/```\n?$/g, "");
} else if (jsonStr.startsWith("```")) {
jsonStr = jsonStr.replace(/```\n?/g, "");
}
const analysis = JSON.parse(jsonStr) as SemanticAnalysis;
// Validate and normalize the response
return {
hasBehavioralChange: Boolean(analysis.hasBehavioralChange),
breakingForExamples: Boolean(analysis.breakingForExamples),
changeDescription: analysis.changeDescription || "Code change detected",
affectedDocSections: Array.isArray(analysis.affectedDocSections)
? analysis.affectedDocSections
: [],
confidence: this.normalizeConfidence(analysis.confidence),
};
} catch (error) {
// Return low-confidence fallback result on error
return {
hasBehavioralChange: false,
breakingForExamples: false,
changeDescription: `Analysis failed: ${
error instanceof Error ? error.message : "Unknown error"
}`,
affectedDocSections: [],
confidence: 0,
};
}
}
/**
* Simulate execution of code to validate examples
*/
async simulateExecution(
example: string,
implementation: string,
): Promise<SimulationResult> {
const prompt = `You are a code execution simulator. Given a code example and its implementation, predict the execution result.
**Example Usage:**
\`\`\`
${example}
\`\`\`
**Implementation:**
\`\`\`
${implementation}
\`\`\`
Analyze the code flow without actually executing it. Respond in JSON format:
{
"success": boolean (would the example execute successfully?),
"expectedOutput": string (what the example expects),
"actualOutput": string (what the implementation would produce),
"matches": boolean (do they match?),
"differences": string[] (list of differences if any),
"confidence": number (0-1 score for prediction confidence)
}
Consider:
1. Function signatures and parameters
2. Return types and values
3. Error handling
4. Side effects
5. Dependencies and imports`;
try {
const response = await this.complete(prompt);
// Extract JSON from response
let jsonStr = response.trim();
if (jsonStr.startsWith("```json")) {
jsonStr = jsonStr.replace(/```json\n?/g, "").replace(/```\n?$/g, "");
} else if (jsonStr.startsWith("```")) {
jsonStr = jsonStr.replace(/```\n?/g, "");
}
const result = JSON.parse(jsonStr) as SimulationResult;
// Validate and normalize
return {
success: Boolean(result.success),
expectedOutput: result.expectedOutput || "",
actualOutput: result.actualOutput || "",
matches: Boolean(result.matches),
differences: Array.isArray(result.differences)
? result.differences
: [],
confidence: this.normalizeConfidence(result.confidence),
};
} catch (error) {
// Return low-confidence failure result on error
return {
success: false,
expectedOutput: "Unable to determine",
actualOutput: "Unable to determine",
matches: false,
differences: [
`Simulation failed: ${
error instanceof Error ? error.message : "Unknown error"
}`,
],
confidence: 0,
};
}
}
/**
* Normalize confidence score to 0-1 range
*/
private normalizeConfidence(confidence: unknown): number {
if (typeof confidence === "number") {
return Math.max(0, Math.min(1, confidence));
}
return 0.5; // Default confidence for invalid values
}
}
/**
* Factory function to create LLM client based on configuration
*/
export function createLLMClient(config?: Partial<LLMConfig>): LLMClient | null {
// Check environment variables for configuration
const provider = (config?.provider ||
process.env.DOCUMCP_LLM_PROVIDER ||
"deepseek") as LLMConfig["provider"];
const apiKey = config?.apiKey || process.env.DOCUMCP_LLM_API_KEY;
const baseUrl = config?.baseUrl || process.env.DOCUMCP_LLM_BASE_URL;
const model =
config?.model || process.env.DOCUMCP_LLM_MODEL || "deepseek-chat";
// If no API key, return null to indicate LLM is unavailable
if (!apiKey) {
return null;
}
const fullConfig: LLMConfig = {
provider,
apiKey,
baseUrl,
model,
maxTokens: config?.maxTokens,
timeout: config?.timeout,
};
switch (provider) {
case "deepseek":
case "openai":
case "anthropic":
case "ollama":
// For now, all use OpenAI-compatible API (DeepSeekClient)
// Future: implement provider-specific clients
return new DeepSeekClient(fullConfig);
default:
throw new Error(`Unsupported LLM provider: ${provider}`);
}
}
/**
* Check if LLM is available based on environment configuration
* Note: OPENAI_API_KEY is checked as fallback for backward compatibility
*/
export function isLLMAvailable(): boolean {
return !!(process.env.DOCUMCP_LLM_API_KEY || process.env.OPENAI_API_KEY);
}
```
--------------------------------------------------------------------------------
/tests/tools/analyze-coverage.test.ts:
--------------------------------------------------------------------------------
```typescript
// Additional tests to improve analyze-repository coverage
import { promises as fs } from "fs";
import path from "path";
import os from "os";
import { analyzeRepository } from "../../src/tools/analyze-repository";
describe("Analyze Repository Additional Coverage", () => {
let tempDir: string;
beforeAll(async () => {
tempDir = path.join(os.tmpdir(), "analyze-coverage");
await fs.mkdir(tempDir, { recursive: true });
});
afterAll(async () => {
try {
await fs.rm(tempDir, { recursive: true, force: true });
} catch (error) {
// Cleanup errors are okay
}
});
describe("Different Repository Types", () => {
it("should analyze Ruby project", async () => {
const rubyDir = path.join(tempDir, "ruby-project");
await fs.mkdir(rubyDir, { recursive: true });
await fs.writeFile(
path.join(rubyDir, "Gemfile"),
`
source 'https://rubygems.org'
gem 'rails', '~> 7.0'
gem 'puma'
gem 'redis'
`,
);
await fs.writeFile(path.join(rubyDir, "app.rb"), 'puts "Hello Ruby"');
await fs.writeFile(path.join(rubyDir, "README.md"), "# Ruby Project");
const result = await analyzeRepository({
path: rubyDir,
depth: "standard",
});
expect(result.content).toBeDefined();
const analysis = JSON.parse(result.content[0].text);
expect(analysis.dependencies.ecosystem).toBe("ruby");
});
it("should analyze Go project", async () => {
const goDir = path.join(tempDir, "go-project");
await fs.mkdir(goDir, { recursive: true });
await fs.writeFile(
path.join(goDir, "go.mod"),
`
module example.com/myapp
go 1.21
require (
github.com/gin-gonic/gin v1.9.0
github.com/stretchr/testify v1.8.0
)
`,
);
await fs.writeFile(path.join(goDir, "main.go"), "package main");
await fs.writeFile(path.join(goDir, "README.md"), "# Go Project");
const result = await analyzeRepository({
path: goDir,
depth: "standard",
});
expect(result.content).toBeDefined();
const analysis = JSON.parse(result.content[0].text);
expect(analysis.dependencies.ecosystem).toBe("go");
});
it("should analyze Java project", async () => {
const javaDir = path.join(tempDir, "java-project");
await fs.mkdir(javaDir, { recursive: true });
await fs.writeFile(
path.join(javaDir, "pom.xml"),
`
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
`,
);
await fs.writeFile(path.join(javaDir, "App.java"), "public class App {}");
const result = await analyzeRepository({
path: javaDir,
depth: "standard",
});
expect(result.content).toBeDefined();
const analysis = JSON.parse(result.content[0].text);
expect(analysis.dependencies.ecosystem).toBeDefined(); // May be 'java' or 'unknown' depending on detection
});
it("should analyze project with Docker", async () => {
const dockerDir = path.join(tempDir, "docker-project");
await fs.mkdir(dockerDir, { recursive: true });
await fs.writeFile(
path.join(dockerDir, "Dockerfile"),
`
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]
`,
);
await fs.writeFile(
path.join(dockerDir, "docker-compose.yml"),
`
version: '3'
services:
app:
build: .
ports:
- "3000:3000"
`,
);
await fs.writeFile(
path.join(dockerDir, "package.json"),
'{"name": "docker-app"}',
);
const result = await analyzeRepository({
path: dockerDir,
depth: "standard",
});
expect(result.content).toBeDefined();
const analysis = JSON.parse(result.content[0].text);
// Verify basic analysis works - Docker detection not implemented
expect(analysis.structure).toBeDefined();
expect(analysis.structure.totalFiles).toBe(3);
expect(analysis.dependencies.ecosystem).toBe("javascript");
});
it("should analyze project with existing docs", async () => {
const docsDir = path.join(tempDir, "docs-project");
await fs.mkdir(path.join(docsDir, "docs"), { recursive: true });
await fs.mkdir(path.join(docsDir, "documentation"), { recursive: true });
await fs.writeFile(
path.join(docsDir, "docs", "index.md"),
"# Documentation",
);
await fs.writeFile(
path.join(docsDir, "docs", "api.md"),
"# API Reference",
);
await fs.writeFile(
path.join(docsDir, "documentation", "guide.md"),
"# User Guide",
);
await fs.writeFile(
path.join(docsDir, "README.md"),
"# Project with Docs",
);
const result = await analyzeRepository({
path: docsDir,
depth: "standard",
});
expect(result.content).toBeDefined();
const analysis = JSON.parse(result.content[0].text);
expect(analysis.structure.hasDocs).toBe(true);
});
});
describe("Edge Cases and Error Handling", () => {
it("should handle empty repository", async () => {
const emptyDir = path.join(tempDir, "empty-repo");
await fs.mkdir(emptyDir, { recursive: true });
const result = await analyzeRepository({
path: emptyDir,
depth: "quick",
});
expect(result.content).toBeDefined();
const analysis = JSON.parse(result.content[0].text);
expect(analysis.dependencies.ecosystem).toBe("unknown");
});
it("should handle repository with only config files", async () => {
const configDir = path.join(tempDir, "config-only");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(path.join(configDir, ".gitignore"), "node_modules/");
await fs.writeFile(
path.join(configDir, ".editorconfig"),
"indent_style = space",
);
await fs.writeFile(path.join(configDir, "LICENSE"), "MIT License");
const result = await analyzeRepository({
path: configDir,
depth: "standard",
});
expect(result.content).toBeDefined();
expect(result.content.length).toBeGreaterThan(0);
});
it("should handle deep analysis depth", async () => {
const deepDir = path.join(tempDir, "deep-analysis");
await fs.mkdir(deepDir, { recursive: true });
// Create nested structure
await fs.mkdir(path.join(deepDir, "src", "components", "ui"), {
recursive: true,
});
await fs.mkdir(path.join(deepDir, "src", "utils", "helpers"), {
recursive: true,
});
await fs.mkdir(path.join(deepDir, "tests", "unit"), { recursive: true });
await fs.writeFile(
path.join(deepDir, "package.json"),
JSON.stringify({
name: "deep-project",
scripts: {
test: "jest",
build: "webpack",
lint: "eslint .",
},
}),
);
await fs.writeFile(
path.join(deepDir, "src", "index.js"),
'console.log("app");',
);
await fs.writeFile(
path.join(deepDir, "src", "components", "ui", "Button.js"),
"export default Button;",
);
await fs.writeFile(
path.join(deepDir, "tests", "unit", "test.js"),
'test("sample", () => {});',
);
const result = await analyzeRepository({ path: deepDir, depth: "deep" });
expect(result.content).toBeDefined();
const analysis = JSON.parse(result.content[0].text);
expect(analysis.structure.hasTests).toBe(true);
});
it("should analyze repository with multiple ecosystems", async () => {
const multiDir = path.join(tempDir, "multi-ecosystem");
await fs.mkdir(multiDir, { recursive: true });
// JavaScript
await fs.writeFile(
path.join(multiDir, "package.json"),
'{"name": "frontend"}',
);
// Python
await fs.writeFile(
path.join(multiDir, "requirements.txt"),
"flask==2.0.0",
);
// Ruby
await fs.writeFile(path.join(multiDir, "Gemfile"), 'gem "rails"');
const result = await analyzeRepository({
path: multiDir,
depth: "standard",
});
expect(result.content).toBeDefined();
// Should detect the primary ecosystem (usually the one with most files/config)
const analysis = JSON.parse(result.content[0].text);
expect(["javascript", "python", "ruby"]).toContain(
analysis.dependencies.ecosystem,
);
});
});
describe("Repository Complexity Analysis", () => {
it("should calculate complexity metrics", async () => {
const complexDir = path.join(tempDir, "complex-repo");
await fs.mkdir(path.join(complexDir, ".github", "workflows"), {
recursive: true,
});
// Create various files to test complexity
await fs.writeFile(
path.join(complexDir, "package.json"),
JSON.stringify({
name: "complex-app",
dependencies: {
react: "^18.0.0",
express: "^4.0.0",
webpack: "^5.0.0",
},
devDependencies: {
jest: "^29.0.0",
eslint: "^8.0.0",
},
}),
);
await fs.writeFile(
path.join(complexDir, ".github", "workflows", "ci.yml"),
`
name: CI
on: push
jobs:
test:
runs-on: ubuntu-latest
`,
);
await fs.writeFile(
path.join(complexDir, "README.md"),
"# Complex Project\n\nWith detailed documentation",
);
await fs.writeFile(
path.join(complexDir, "CONTRIBUTING.md"),
"# Contributing Guide",
);
const result = await analyzeRepository({
path: complexDir,
depth: "deep",
});
expect(result.content).toBeDefined();
const analysis = JSON.parse(result.content[0].text);
expect(analysis.structure.hasCI).toBe(true);
expect(analysis.documentation.hasReadme).toBe(true);
});
});
});
```
--------------------------------------------------------------------------------
/src/utils/user-feedback-integration.ts:
--------------------------------------------------------------------------------
```typescript
/**
* User Feedback Integration for Priority Scoring (ADR-012 Phase 3)
*
* Integrates with issue tracking systems (GitHub Issues, GitLab Issues, etc.)
* to incorporate user-reported documentation issues into priority scoring.
*/
import { DriftDetectionResult } from "./drift-detector.js";
export interface IssueTrackerConfig {
provider: "github" | "gitlab" | "jira" | "linear";
apiToken?: string;
baseUrl?: string;
owner?: string;
repo?: string;
project?: string;
}
export interface DocumentationIssue {
id: string;
title: string;
body: string;
state: "open" | "closed";
labels: string[];
createdAt: string;
updatedAt: string;
affectedFiles?: string[];
affectedSymbols?: string[];
severity?: "low" | "medium" | "high" | "critical";
}
export interface UserFeedbackScore {
totalIssues: number;
openIssues: number;
criticalIssues: number;
recentIssues: number; // Issues updated in last 30 days
score: number; // 0-100
}
/**
* User Feedback Integration for ADR-012
*
* Fetches documentation-related issues from issue trackers
* and calculates user feedback scores for priority scoring.
*/
export class UserFeedbackIntegration {
private config: IssueTrackerConfig | null = null;
private cache: Map<
string,
{ issues: DocumentationIssue[]; timestamp: number }
> = new Map();
private cacheTTL = 5 * 60 * 1000; // 5 minutes
constructor(config?: IssueTrackerConfig) {
this.config = config || null;
}
/**
* Configure issue tracker connection
*/
configure(config: IssueTrackerConfig): void {
this.config = config;
this.cache.clear(); // Clear cache when config changes
}
/**
* Calculate user feedback score for a drift detection result
*/
async calculateFeedbackScore(result: DriftDetectionResult): Promise<number> {
if (!this.config) {
return 0; // No feedback integration configured
}
try {
const issues = await this.getDocumentationIssues(result.filePath);
const feedback = this.analyzeIssues(issues, result);
return feedback.score;
} catch (error) {
console.warn(
`Failed to fetch user feedback for ${result.filePath}:`,
error,
);
return 0; // Graceful degradation
}
}
/**
* Get documentation-related issues for a file
*/
private async getDocumentationIssues(
filePath: string,
): Promise<DocumentationIssue[]> {
const cacheKey = `issues:${filePath}`;
const cached = this.cache.get(cacheKey);
// Return cached data if still valid
if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
return cached.issues;
}
if (!this.config) {
return [];
}
let issues: DocumentationIssue[] = [];
try {
switch (this.config.provider) {
case "github":
issues = await this.fetchGitHubIssues(filePath);
break;
case "gitlab":
issues = await this.fetchGitLabIssues(filePath);
break;
case "jira":
issues = await this.fetchJiraIssues(filePath);
break;
case "linear":
issues = await this.fetchLinearIssues(filePath);
break;
}
// Cache the results
this.cache.set(cacheKey, {
issues,
timestamp: Date.now(),
});
} catch (error) {
console.warn(
`Failed to fetch issues from ${this.config.provider}:`,
error,
);
}
return issues;
}
/**
* Fetch GitHub Issues related to documentation
*/
private async fetchGitHubIssues(
filePath: string,
): Promise<DocumentationIssue[]> {
if (!this.config?.apiToken || !this.config.owner || !this.config.repo) {
return [];
}
const url = `https://api.github.com/repos/${this.config.owner}/${this.config.repo}/issues?state=all&labels=documentation,docs`;
const headers: Record<string, string> = {
Accept: "application/vnd.github.v3+json",
"User-Agent": "DocuMCP/1.0",
};
if (this.config.apiToken) {
headers.Authorization = `token ${this.config.apiToken}`;
}
try {
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status}`);
}
const data = (await response.json()) as any[];
return this.parseGitHubIssues(data, filePath);
} catch (error) {
console.warn("GitHub API fetch failed:", error);
return [];
}
}
/**
* Parse GitHub Issues API response
*/
private parseGitHubIssues(
data: any[],
filePath: string,
): DocumentationIssue[] {
return data
.filter((issue) => !issue.pull_request) // Exclude PRs
.map((issue) => {
// Extract affected files/symbols from issue body
const affectedFiles = this.extractFileReferences(issue.body || "");
const affectedSymbols = this.extractSymbolReferences(issue.body || "");
// Determine severity from labels
const severity = this.determineSeverityFromLabels(issue.labels || []);
return {
id: issue.number.toString(),
title: issue.title,
body: issue.body || "",
state: (issue.state === "open" ? "open" : "closed") as
| "open"
| "closed",
labels: (issue.labels || []).map((l: any) => l.name || l),
createdAt: issue.created_at,
updatedAt: issue.updated_at,
affectedFiles,
affectedSymbols,
severity,
};
})
.filter((issue) => {
// Filter to issues that mention the file or its symbols
const fileMatches = issue.affectedFiles?.some(
(f) => filePath.includes(f) || f.includes(filePath),
);
const isDocumentationRelated = issue.labels.some((l: string) =>
["documentation", "docs", "doc"].includes(l.toLowerCase()),
);
return fileMatches || isDocumentationRelated;
});
}
/**
* Fetch GitLab Issues (placeholder)
*/
private async fetchGitLabIssues(
_filePath: string,
): Promise<DocumentationIssue[]> {
// TODO: Implement GitLab API integration
return [];
}
/**
* Fetch Jira Issues (placeholder)
*/
private async fetchJiraIssues(
_filePath: string,
): Promise<DocumentationIssue[]> {
// TODO: Implement Jira API integration
return [];
}
/**
* Fetch Linear Issues (placeholder)
*/
private async fetchLinearIssues(
_filePath: string,
): Promise<DocumentationIssue[]> {
// TODO: Implement Linear API integration
return [];
}
/**
* Extract file references from issue body
*/
private extractFileReferences(body: string): string[] {
const files: string[] = [];
// Match file paths in markdown code blocks or inline code
const filePatterns = [
/`([^`]+\.(ts|js|tsx|jsx|md|mdx))`/g,
/\[([^\]]+\.(ts|js|tsx|jsx|md|mdx))\]/g,
/(?:file|path|location):\s*([^\s]+\.(ts|js|tsx|jsx|md|mdx))/gi,
];
for (const pattern of filePatterns) {
const matches = body.matchAll(pattern);
for (const match of matches) {
if (match[1]) {
files.push(match[1]);
}
}
}
return [...new Set(files)];
}
/**
* Extract symbol references from issue body
*/
private extractSymbolReferences(body: string): string[] {
const symbols: string[] = [];
// Match function/class names in code blocks
const symbolPatterns = [
/`([A-Za-z_][A-Za-z0-9_]*\(\)?)`/g,
/(?:function|class|method|API):\s*`?([A-Za-z_][A-Za-z0-9_]*)`?/gi,
];
for (const pattern of symbolPatterns) {
const matches = body.matchAll(pattern);
for (const match of matches) {
if (match[1]) {
symbols.push(match[1]);
}
}
}
return [...new Set(symbols)];
}
/**
* Determine severity from issue labels
*/
private determineSeverityFromLabels(
labels: Array<{ name?: string } | string>,
): "low" | "medium" | "high" | "critical" {
const labelNames = labels.map((l) =>
typeof l === "string" ? l : l.name || "",
);
const lowerLabels = labelNames.map((l) => l.toLowerCase());
if (
lowerLabels.some((l) =>
["critical", "p0", "severity: critical", "priority: critical"].includes(
l,
),
)
) {
return "critical";
}
if (
lowerLabels.some((l) =>
["high", "p1", "severity: high", "priority: high"].includes(l),
)
) {
return "high";
}
if (
lowerLabels.some((l) =>
["medium", "p2", "severity: medium", "priority: medium"].includes(l),
)
) {
return "medium";
}
return "low";
}
/**
* Analyze issues and calculate feedback score
*/
private analyzeIssues(
issues: DocumentationIssue[],
result: DriftDetectionResult,
): UserFeedbackScore {
const now = Date.now();
const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1000;
const openIssues = issues.filter((i) => i.state === "open");
const criticalIssues = issues.filter(
(i) => i.severity === "critical" && i.state === "open",
);
const recentIssues = issues.filter(
(i) => new Date(i.updatedAt).getTime() > thirtyDaysAgo,
);
// Calculate score based on issue metrics
let score = 0;
// Critical open issues contribute heavily
score += criticalIssues.length * 30;
score = Math.min(score, 100);
// Open issues contribute moderately
score += openIssues.length * 10;
score = Math.min(score, 100);
// Recent activity indicates ongoing concern
if (recentIssues.length > 0) {
score += Math.min(recentIssues.length * 5, 20);
score = Math.min(score, 100);
}
// Match issues to affected symbols for higher relevance
const affectedSymbols = new Set<string>();
for (const drift of result.drifts) {
for (const diff of drift.codeChanges) {
affectedSymbols.add(diff.name);
}
}
const relevantIssues = issues.filter((issue) => {
if (!issue.affectedSymbols) return false;
return issue.affectedSymbols.some((symbol) =>
affectedSymbols.has(symbol),
);
});
if (relevantIssues.length > 0) {
score += Math.min(relevantIssues.length * 15, 30);
score = Math.min(score, 100);
}
return {
totalIssues: issues.length,
openIssues: openIssues.length,
criticalIssues: criticalIssues.length,
recentIssues: recentIssues.length,
score: Math.round(score),
};
}
/**
* Clear cache (useful for testing or forced refresh)
*/
clearCache(): void {
this.cache.clear();
}
}
```
--------------------------------------------------------------------------------
/docs/reference/api-overview.md:
--------------------------------------------------------------------------------
```markdown
---
sidebar_position: 1
documcp:
last_updated: "2025-11-20T00:46:21.959Z"
last_validated: "2025-12-09T19:41:38.589Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# API Overview
DocuMCP provides **45 specialized tools** organized into functional categories for intelligent documentation deployment via the Model Context Protocol (MCP).
## 🎯 Quick Reference: LLM_CONTEXT.md
For AI assistants and LLMs, reference the **comprehensive context file**:
**File**: `/LLM_CONTEXT.md` (in project root)
This auto-generated file provides:
- All 45 tool descriptions with parameters
- Usage examples and code snippets
- Common workflow patterns
- Memory system documentation
- Phase 3 code-to-docs sync features
**Usage in AI assistants**:
```
@LLM_CONTEXT.md help me deploy documentation to GitHub Pages
```
## 📚 Tool Categories
### Core Documentation Tools (9 tools)
Essential tools for repository analysis, recommendations, and deployment:
| Tool | Purpose | Key Parameters |
| ------------------------------- | ---------------------------------------- | ---------------------------------- |
| `analyze_repository` | Analyze project structure & dependencies | `path`, `depth` |
| `recommend_ssg` | Recommend static site generator | `analysisId`, `preferences` |
| `generate_config` | Generate SSG configuration files | `ssg`, `projectName`, `outputPath` |
| `setup_structure` | Create Diataxis documentation structure | `path`, `ssg` |
| `deploy_pages` | Deploy to GitHub Pages with tracking | `repository`, `ssg`, `userId` |
| `verify_deployment` | Verify deployment status | `repository`, `url` |
| `populate_diataxis_content` | Generate project-specific content | `analysisId`, `docsPath` |
| `update_existing_documentation` | Update existing docs intelligently | `analysisId`, `docsPath` |
| `validate_diataxis_content` | Validate documentation quality | `contentPath`, `validationType` |
### README Analysis & Generation (6 tools)
Specialized tools for README creation and optimization:
| Tool | Purpose | Key Parameters |
| --------------------------- | ----------------------------------------- | -------------------------------------------- |
| `evaluate_readme_health` | Assess README quality & onboarding | `readme_path`, `project_type` |
| `readme_best_practices` | Analyze against best practices | `readme_path`, `generate_template` |
| `generate_readme_template` | Create standardized README | `projectName`, `description`, `templateType` |
| `validate_readme_checklist` | Validate against community standards | `readmePath`, `strict` |
| `analyze_readme` | Comprehensive length & structure analysis | `project_path`, `optimization_level` |
| `optimize_readme` | Restructure and condense content | `readme_path`, `strategy`, `max_length` |
### Phase 3: Code-to-Docs Synchronization (2 tools)
Advanced AST-based code analysis and drift detection:
| Tool | Purpose | Key Parameters |
| ----------------------------- | ---------------------------------- | --------------------------------- |
| `sync_code_to_docs` | Detect and fix documentation drift | `projectPath`, `docsPath`, `mode` |
| `generate_contextual_content` | Generate docs from code analysis | `filePath`, `documentationType` |
**Supported Languages**: TypeScript, JavaScript, Python, Go, Rust, Java, Ruby, Bash
**Drift Types Detected**: Outdated, Incorrect, Missing, Breaking
### Memory & Analytics Tools (2 tools)
User preferences and deployment pattern analysis:
| Tool | Purpose | Key Parameters |
| --------------------- | -------------------------------------- | ----------------------------------- |
| `manage_preferences` | Manage user preferences & SSG history | `action`, `userId`, `preferences` |
| `analyze_deployments` | Analyze deployment patterns & insights | `analysisType`, `ssg`, `periodDays` |
### Validation & Testing Tools (4 tools)
Quality assurance and deployment testing:
| Tool | Purpose | Key Parameters |
| --------------------------- | ------------------------------------ | -------------------------------------------- |
| `validate_content` | Validate links, code, and references | `contentPath`, `validationType` |
| `check_documentation_links` | Comprehensive link validation | `documentation_path`, `check_external_links` |
| `test_local_deployment` | Test build and local server | `repositoryPath`, `ssg`, `port` |
| `setup_playwright_tests` | Generate E2E test infrastructure | `repositoryPath`, `ssg`, `projectName` |
### Utility Tools (3 tools)
Additional functionality and management:
| Tool | Purpose | Key Parameters |
| --------------------------- | --------------------------------- | ------------------------------------- |
| `detect_documentation_gaps` | Identify missing content | `repositoryPath`, `documentationPath` |
| `manage_sitemap` | Generate and validate sitemap.xml | `action`, `docsPath`, `baseUrl` |
| `read_directory` | List files within allowed roots | `path` |
### Advanced Memory Tools (19 tools)
Sophisticated memory, learning, and knowledge graph operations:
| Tool Category | Tools | Purpose |
| ------------------- | ---------------------------------------------------------------------- | ----------------------------- |
| **Memory Recall** | `memory_recall`, `memory_contextual_search` | Retrieve and search memories |
| **Intelligence** | `memory_intelligent_analysis`, `memory_enhanced_recommendation` | AI-powered insights |
| **Knowledge Graph** | `memory_knowledge_graph`, `memory_learning_stats` | Graph queries and statistics |
| **Collaboration** | `memory_agent_network` | Multi-agent memory sharing |
| **Insights** | `memory_insights`, `memory_similar`, `memory_temporal_analysis` | Pattern analysis |
| **Data Management** | `memory_export`, `memory_cleanup`, `memory_pruning` | Export, cleanup, optimization |
| **Visualization** | `memory_visualization` | Visual representations |
| **Advanced I/O** | `memory_export_advanced`, `memory_import_advanced`, `memory_migration` | Complex data operations |
| **Metrics** | `memory_optimization_metrics` | Performance analysis |
## 🔗 Detailed Documentation
### Full API Reference
- **[MCP Tools API](./mcp-tools.md)** - Complete tool descriptions with examples
- **[TypeDoc API](../api/)** - Auto-generated API documentation for all classes, interfaces, and functions
- **[LLM Context Reference](../../LLM_CONTEXT.md)** - Comprehensive tool reference for AI assistants
### Configuration & Usage
- **[Configuration Options](./configuration.md)** - All configuration settings
- **[CLI Commands](./cli.md)** - Command-line interface reference
- **[Prompt Templates](./prompt-templates.md)** - Pre-built prompt examples
## 🚀 Common Workflows
### 1. New Documentation Site
```
analyze_repository → recommend_ssg → generate_config →
setup_structure → populate_diataxis_content → deploy_pages
```
### 2. Documentation Sync (Phase 3)
```
sync_code_to_docs (detect) → review drift →
sync_code_to_docs (apply) → manual review
```
### 3. Existing Docs Improvement
```
analyze_repository → update_existing_documentation →
validate_diataxis_content → check_documentation_links
```
### 4. README Enhancement
```
analyze_readme → evaluate_readme_health →
readme_best_practices → optimize_readme
```
## 📦 Memory Knowledge Graph
DocuMCP includes a persistent memory system that learns from every analysis:
### Entity Types
- **Project**: Software projects with analysis history
- **User**: User preferences and SSG patterns
- **Configuration**: SSG deployment configs with success rates
- **Documentation**: Documentation structures and patterns
- **CodeFile**: Source code files with change tracking
- **DocumentationSection**: Docs sections linked to code
- **Technology**: Languages, frameworks, and tools
### Relationship Types
- `project_uses_technology`: Links projects to tech stack
- `user_prefers_ssg`: Tracks user SSG preferences
- `project_deployed_with`: Records deployment outcomes
- `similar_to`: Identifies similar projects
- `documents`: Links code files to documentation
- `outdated_for`: Flags out-of-sync documentation
- `depends_on`: Tracks technology dependencies
### Storage Location
- **Default**: `.documcp/memory/`
- **Entities**: `.documcp/memory/knowledge-graph-entities.jsonl`
- **Relationships**: `.documcp/memory/knowledge-graph-relationships.jsonl`
- **Backups**: `.documcp/memory/backups/`
- **Snapshots**: `.documcp/snapshots/` (for drift detection)
## 🎓 Getting Started
1. **Start with tutorials**: [Getting Started Guide](../tutorials/getting-started.md)
2. **Learn effective prompting**: [Prompting Guide](../how-to/prompting-guide.md)
3. **Reference LLM_CONTEXT.md**: Use `@LLM_CONTEXT.md` in AI assistants
4. **Explore workflows**: [Common Workflows](#common-workflows)
## 📊 Tool Statistics
- **Total Tools**: 45
- **Core Documentation**: 9 tools
- **README Management**: 6 tools
- **Phase 3 Sync**: 2 tools
- **Memory & Analytics**: 2 tools
- **Validation**: 4 tools
- **Utilities**: 3 tools
- **Advanced Memory**: 19 tools
## 🔍 Search & Discovery
- **By functionality**: Use the category tables above
- **By name**: See [MCP Tools API](./mcp-tools.md)
- **By code**: Browse [TypeDoc API](../api/)
- **For AI assistants**: Reference [LLM_CONTEXT.md](../../LLM_CONTEXT.md)
---
_Documentation auto-generated from DocuMCP v0.3.2_
```
--------------------------------------------------------------------------------
/docs/reference/deploy-pages.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.961Z"
last_validated: "2025-12-09T19:41:38.591Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# Deploy Pages Tool Documentation
## Overview
The `deploy_pages` tool provides automated GitHub Pages deployment setup with intelligent SSG (Static Site Generator) detection, optimized workflow generation, and comprehensive deployment tracking through the Knowledge Graph system.
## Features
- **SSG Auto-Detection**: Automatically retrieves SSG recommendations from Knowledge Graph using analysisId
- **Optimized Workflows**: Generates SSG-specific GitHub Actions workflows with best practices
- **Package Manager Detection**: Supports npm, yarn, and pnpm with automatic lockfile detection
- **Documentation Folder Detection**: Intelligently detects docs folders (docs/, website/, documentation/)
- **Custom Domain Support**: Automatic CNAME file generation
- **Deployment Tracking**: Integrates with Knowledge Graph to track deployment success/failure
- **User Preference Learning**: Tracks SSG usage patterns for personalized recommendations
## Usage
### Basic Usage
```javascript
// Deploy with explicit SSG
const result = await callTool("deploy_pages", {
repository: "/path/to/project",
ssg: "docusaurus",
});
```
### Advanced Usage with Knowledge Graph Integration
```javascript
// Deploy using SSG from previous analysis
const result = await callTool("deploy_pages", {
repository: "https://github.com/user/repo.git",
analysisId: "repo-analysis-123", // SSG retrieved from KG
projectPath: "/local/path",
projectName: "My Documentation Site",
customDomain: "docs.example.com",
userId: "developer-1",
});
```
## Parameters
| Parameter | Type | Required | Description |
| -------------- | -------- | -------- | --------------------------------------------------------------------------- |
| `repository` | `string` | ✅ | Repository path (local) or URL (remote) |
| `ssg` | `enum` | ⚠️\* | Static site generator: `jekyll`, `hugo`, `docusaurus`, `mkdocs`, `eleventy` |
| `branch` | `string` | ❌ | Target branch for deployment (default: `gh-pages`) |
| `customDomain` | `string` | ❌ | Custom domain for GitHub Pages |
| `projectPath` | `string` | ❌ | Local project path for tracking |
| `projectName` | `string` | ❌ | Project name for tracking |
| `analysisId` | `string` | ❌ | Repository analysis ID for SSG retrieval |
| `userId` | `string` | ❌ | User ID for preference tracking (default: `default`) |
\*Required unless `analysisId` is provided for SSG retrieval from Knowledge Graph
## SSG-Specific Workflows
### Docusaurus
- Node.js setup with configurable version
- Package manager auto-detection (npm/yarn/pnpm)
- Build caching optimization
- Working directory support for monorepos
### Hugo
- Extended Hugo version with latest releases
- Asset optimization and minification
- Submodule support for themes
- Custom build command detection
### Jekyll
- Ruby environment with Bundler
- Gemfile dependency management
- Production environment variables
- Custom plugin support
### MkDocs
- Python environment setup
- Requirements.txt dependency installation
- Direct GitHub Pages deployment
- Custom branch targeting
### Eleventy (11ty)
- Node.js with flexible configuration
- Custom output directory detection
- Plugin ecosystem support
- Development server integration
## Generated Workflow Features
### Security Best Practices
- **Minimal Permissions**: Only required `pages:write` and `id-token:write` permissions
- **OIDC Token Authentication**: JWT-based deployment validation
- **Environment Protection**: Production deployment safeguards
- **Dependency Scanning**: Automated security vulnerability checks
### Performance Optimizations
- **Build Caching**: Package manager and dependency caching
- **Incremental Builds**: Only rebuild changed content when possible
- **Asset Optimization**: Minification and compression
- **Parallel Processing**: Multi-stage builds where applicable
### Error Handling
- **Graceful Failures**: Comprehensive error reporting and recovery
- **Debug Information**: Detailed logging for troubleshooting
- **Health Checks**: Post-deployment validation
- **Rollback Support**: Automated rollback on deployment failures
## Knowledge Graph Integration
### Deployment Tracking
```typescript
// Successful deployment tracking
await trackDeployment(projectId, ssg, true, {
buildTime: executionTime,
branch: targetBranch,
customDomain: domain,
});
// Failed deployment tracking
await trackDeployment(projectId, ssg, false, {
errorMessage: error.message,
failureStage: "build|deploy|verification",
});
```
### SSG Retrieval Logic
1. **Check Analysis ID**: Query project node in Knowledge Graph
2. **Get Recommendations**: Retrieve SSG recommendations sorted by confidence
3. **Fallback to History**: Use most recent successful deployment
4. **Smart Filtering**: Only consider successful deployments
### User Preference Learning
- **Success Rate Tracking**: Monitor SSG deployment success rates
- **Usage Pattern Analysis**: Track frequency of SSG selections
- **Personalized Recommendations**: Weight future suggestions based on history
- **Multi-User Support**: Separate preference tracking per userId
## Examples
### Complete Workflow Integration
```javascript
try {
// 1. Analyze repository
const analysis = await callTool("analyze_repository", {
path: "/path/to/project",
});
// 2. Get SSG recommendation
const recommendation = await callTool("recommend_ssg", {
analysisId: analysis.analysisId,
});
// 3. Deploy with recommended SSG
const deployment = await callTool("deploy_pages", {
repository: "/path/to/project",
analysisId: analysis.analysisId,
projectName: "My Project",
userId: "developer-1",
});
console.log(`Deployed ${deployment.ssg} to ${deployment.branch}`);
} catch (error) {
console.error("Deployment workflow failed:", error);
}
```
### Custom Domain Setup
```javascript
const result = await callTool("deploy_pages", {
repository: "/path/to/docs",
ssg: "hugo",
customDomain: "docs.mycompany.com",
branch: "main", // Deploy from main branch
});
// CNAME file automatically created
console.log(`CNAME created: ${result.cnameCreated}`);
```
### Monorepo Documentation
```javascript
const result = await callTool("deploy_pages", {
repository: "/path/to/monorepo",
ssg: "docusaurus",
// Will detect docs/ folder automatically
projectPath: "/path/to/monorepo/packages/docs",
});
console.log(`Docs folder: ${result.detectedConfig.docsFolder}`);
console.log(`Build command: ${result.detectedConfig.buildCommand}`);
```
## Response Format
### Success Response
```javascript
{
repository: "/path/to/project",
ssg: "docusaurus",
branch: "gh-pages",
customDomain: "docs.example.com",
workflowPath: "deploy-docs.yml",
cnameCreated: true,
repoPath: "/path/to/project",
detectedConfig: {
docsFolder: "docs",
buildCommand: "npm run build",
outputPath: "./build",
packageManager: "npm",
workingDirectory: "docs"
}
}
```
### Error Response
```javascript
{
success: false,
error: {
code: "SSG_NOT_SPECIFIED",
message: "SSG parameter is required. Either provide it directly or ensure analysisId points to a project with SSG recommendations.",
resolution: "Run analyze_repository and recommend_ssg first, or specify the SSG parameter explicitly."
}
}
```
## Error Codes
| Code | Description | Resolution |
| ---------------------------- | ------------------------------------------------- | --------------------------------------------------- |
| `SSG_NOT_SPECIFIED` | No SSG provided and none found in Knowledge Graph | Provide SSG parameter or run analysis first |
| `DEPLOYMENT_SETUP_FAILED` | Failed to create workflow files | Check repository permissions and path accessibility |
| `INVALID_REPOSITORY` | Repository path or URL invalid | Verify repository exists and is accessible |
| `WORKFLOW_GENERATION_FAILED` | Failed to generate SSG-specific workflow | Check SSG parameter and project structure |
## Best Practices
### Repository Structure
- Place documentation in standard folders (`docs/`, `website/`, `documentation/`)
- Include `package.json` for Node.js projects with proper scripts
- Use lockfiles (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`) for dependency consistency
### Workflow Optimization
- Enable GitHub Pages in repository settings before first deployment
- Use semantic versioning for documentation releases
- Configure branch protection rules for production deployments
- Monitor deployment logs for performance bottlenecks
### Knowledge Graph Benefits
- Run `analyze_repository` before deployment for optimal SSG selection
- Use consistent `userId` for personalized recommendations
- Provide `projectName` and `projectPath` for deployment tracking
- Review deployment history through Knowledge Graph queries
## Troubleshooting
### Common Issues
**Build Failures**
- Verify all dependencies are listed in `package.json` or `requirements.txt`
- Check Node.js/Python version compatibility
- Ensure build scripts are properly configured
**Permission Errors**
- Enable GitHub Actions in repository settings
- Check workflow file permissions (should be automatically handled)
- Verify GitHub Pages is enabled for the target branch
**Custom Domain Issues**
- Verify DNS configuration points to GitHub Pages
- Allow 24-48 hours for DNS propagation
- Check CNAME file is created in repository root
### Debug Workflow
1. Check GitHub Actions logs in repository
2. Verify workflow file syntax using GitHub workflow validator
3. Test build locally using same commands as workflow
4. Review Knowledge Graph deployment history for patterns
## Related Tools
- [`analyze_repository`](../how-to/repository-analysis.md) - Repository analysis for SSG recommendations
- [`recommend_ssg`](./mcp-tools.md#recommend_ssg) - SSG recommendation engine
- [`verify_deployment`](./mcp-tools.md#verify_deployment) - Deployment verification and health checks
- [`manage_preferences`](./mcp-tools.md#manage_preferences) - User preference management
```
--------------------------------------------------------------------------------
/src/tools/track-documentation-freshness.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Track Documentation Freshness Tool
*
* Scans documentation directory for staleness markers,
* identifies files needing updates based on configurable time thresholds.
*/
import { z } from "zod";
import {
scanDocumentationFreshness,
STALENESS_PRESETS,
type StalenessThreshold,
type FreshnessScanReport,
} from "../utils/freshness-tracker.js";
import { type MCPToolResponse } from "../types/api.js";
import {
storeFreshnessEvent,
getStalenessInsights,
} from "../memory/freshness-kg-integration.js";
/**
* Input schema for track_documentation_freshness tool
*/
export const TrackDocumentationFreshnessSchema = z.object({
docsPath: z.string().describe("Path to documentation directory"),
projectPath: z
.string()
.optional()
.describe("Path to project root (for knowledge graph tracking)"),
warningThreshold: z
.object({
value: z.number().positive(),
unit: z.enum(["minutes", "hours", "days"]),
})
.optional()
.describe("Warning threshold (yellow flag)"),
staleThreshold: z
.object({
value: z.number().positive(),
unit: z.enum(["minutes", "hours", "days"]),
})
.optional()
.describe("Stale threshold (orange flag)"),
criticalThreshold: z
.object({
value: z.number().positive(),
unit: z.enum(["minutes", "hours", "days"]),
})
.optional()
.describe("Critical threshold (red flag)"),
preset: z
.enum(["realtime", "active", "recent", "weekly", "monthly", "quarterly"])
.optional()
.describe("Use predefined threshold preset"),
includeFileList: z
.boolean()
.optional()
.default(true)
.describe("Include detailed file list in response"),
sortBy: z
.enum(["age", "path", "staleness"])
.optional()
.default("staleness")
.describe("Sort order for file list"),
storeInKG: z
.boolean()
.optional()
.default(true)
.describe(
"Store tracking event in knowledge graph for historical analysis",
),
});
export type TrackDocumentationFreshnessInput = z.input<
typeof TrackDocumentationFreshnessSchema
>;
/**
* Format freshness report for display
*/
function formatFreshnessReport(
report: FreshnessScanReport,
includeFileList: boolean,
sortBy: "age" | "path" | "staleness",
): string {
const {
totalFiles,
filesWithMetadata,
filesWithoutMetadata,
freshFiles,
warningFiles,
staleFiles,
criticalFiles,
files,
thresholds,
} = report;
let output = "# Documentation Freshness Report\n\n";
output += `**Scanned at**: ${new Date(report.scannedAt).toLocaleString()}\n`;
output += `**Documentation path**: ${report.docsPath}\n\n`;
// Summary statistics
output += "## Summary Statistics\n\n";
output += `- **Total files**: ${totalFiles}\n`;
output += `- **With metadata**: ${filesWithMetadata} (${Math.round(
(filesWithMetadata / totalFiles) * 100,
)}%)\n`;
output += `- **Without metadata**: ${filesWithoutMetadata}\n\n`;
// Freshness breakdown
output += "## Freshness Breakdown\n\n";
output += `- ✅ **Fresh**: ${freshFiles} files\n`;
output += `- 🟡 **Warning**: ${warningFiles} files (older than ${thresholds.warning.value} ${thresholds.warning.unit})\n`;
output += `- 🟠 **Stale**: ${staleFiles} files (older than ${thresholds.stale.value} ${thresholds.stale.unit})\n`;
output += `- 🔴 **Critical**: ${criticalFiles} files (older than ${thresholds.critical.value} ${thresholds.critical.unit})\n`;
output += `- ❓ **Unknown**: ${filesWithoutMetadata} files (no metadata)\n\n`;
// Recommendations
if (filesWithoutMetadata > 0 || criticalFiles > 0 || staleFiles > 0) {
output += "## Recommendations\n\n";
if (filesWithoutMetadata > 0) {
output += `⚠️ **${filesWithoutMetadata} files lack freshness metadata**. Run \`validate_documentation_freshness\` to initialize metadata.\n\n`;
}
if (criticalFiles > 0) {
output += `🔴 **${criticalFiles} files are critically stale**. Immediate review and update recommended.\n\n`;
} else if (staleFiles > 0) {
output += `🟠 **${staleFiles} files are stale**. Consider reviewing and updating soon.\n\n`;
}
}
// File list
if (includeFileList && files.length > 0) {
output += "## File Details\n\n";
// Sort files
const sortedFiles = [...files];
switch (sortBy) {
case "age":
sortedFiles.sort((a, b) => (b.ageInMs || 0) - (a.ageInMs || 0));
break;
case "path":
sortedFiles.sort((a, b) =>
a.relativePath.localeCompare(b.relativePath),
);
break;
case "staleness": {
const order = {
critical: 0,
stale: 1,
warning: 2,
fresh: 3,
unknown: 4,
};
sortedFiles.sort(
(a, b) => order[a.stalenessLevel] - order[b.stalenessLevel],
);
break;
}
}
// Group by staleness level
const grouped = {
critical: sortedFiles.filter((f) => f.stalenessLevel === "critical"),
stale: sortedFiles.filter((f) => f.stalenessLevel === "stale"),
warning: sortedFiles.filter((f) => f.stalenessLevel === "warning"),
fresh: sortedFiles.filter((f) => f.stalenessLevel === "fresh"),
unknown: sortedFiles.filter((f) => f.stalenessLevel === "unknown"),
};
for (const [level, levelFiles] of Object.entries(grouped)) {
if (levelFiles.length === 0) continue;
const icon = {
critical: "🔴",
stale: "🟠",
warning: "🟡",
fresh: "✅",
unknown: "❓",
}[level];
output += `### ${icon} ${
level.charAt(0).toUpperCase() + level.slice(1)
} (${levelFiles.length})\n\n`;
for (const file of levelFiles) {
output += `- **${file.relativePath}**`;
if (file.ageFormatted) {
output += ` - Last updated ${file.ageFormatted} ago`;
}
if (file.metadata?.validated_against_commit) {
output += ` (commit: ${file.metadata.validated_against_commit.substring(
0,
7,
)})`;
}
if (!file.hasMetadata) {
output += " - ⚠️ No metadata";
}
output += "\n";
}
output += "\n";
}
}
return output;
}
/**
* Track documentation freshness
*/
export async function trackDocumentationFreshness(
input: TrackDocumentationFreshnessInput,
): Promise<MCPToolResponse> {
const startTime = Date.now();
try {
const {
docsPath,
projectPath,
warningThreshold,
staleThreshold,
criticalThreshold,
preset,
includeFileList,
sortBy,
storeInKG,
} = input;
// Determine thresholds
let thresholds: {
warning?: StalenessThreshold;
stale?: StalenessThreshold;
critical?: StalenessThreshold;
} = {};
if (preset) {
// Use preset thresholds
const presetThreshold = STALENESS_PRESETS[preset];
thresholds = {
warning: presetThreshold,
stale: { value: presetThreshold.value * 2, unit: presetThreshold.unit },
critical: {
value: presetThreshold.value * 3,
unit: presetThreshold.unit,
},
};
} else {
// Use custom thresholds
if (warningThreshold) thresholds.warning = warningThreshold;
if (staleThreshold) thresholds.stale = staleThreshold;
if (criticalThreshold) thresholds.critical = criticalThreshold;
}
// Scan documentation
const report = await scanDocumentationFreshness(docsPath, thresholds);
// Store in knowledge graph if requested and projectPath provided
let kgInsights:
| Awaited<ReturnType<typeof getStalenessInsights>>
| undefined;
if (storeInKG !== false && projectPath) {
try {
await storeFreshnessEvent(projectPath, docsPath, report, "scan");
kgInsights = await getStalenessInsights(projectPath);
} catch (error) {
// KG storage failed, but continue with the response
console.warn(
"Failed to store freshness event in knowledge graph:",
error,
);
}
}
// Format response
const formattedReport = formatFreshnessReport(
report,
includeFileList ?? true,
sortBy ?? "staleness",
);
// Add KG insights to formatted report if available
let enhancedReport = formattedReport;
if (kgInsights && kgInsights.totalEvents > 0) {
enhancedReport += "\n## Historical Insights\n\n";
enhancedReport += `- **Total tracking events**: ${kgInsights.totalEvents}\n`;
enhancedReport += `- **Average improvement score**: ${(
kgInsights.averageImprovementScore * 100
).toFixed(1)}%\n`;
enhancedReport += `- **Trend**: ${
kgInsights.trend === "improving"
? "📈 Improving"
: kgInsights.trend === "declining"
? "📉 Declining"
: "➡️ Stable"
}\n\n`;
if (kgInsights.recommendations.length > 0) {
enhancedReport += "### Knowledge Graph Insights\n\n";
for (const rec of kgInsights.recommendations) {
enhancedReport += `${rec}\n\n`;
}
}
}
// Convert KG insights to Recommendation objects
const recommendations =
kgInsights?.recommendations.map((rec) => {
// Determine type based on content
let type: "info" | "warning" | "critical" = "info";
if (rec.includes("🔴") || rec.includes("critical")) {
type = "critical";
} else if (
rec.includes("🟠") ||
rec.includes("⚠️") ||
rec.includes("warning")
) {
type = "warning";
}
return {
type,
title: "Documentation Freshness Insight",
description: rec,
};
}) || [];
const response: MCPToolResponse = {
success: true,
data: {
summary: `Scanned ${report.totalFiles} files: ${report.criticalFiles} critical, ${report.staleFiles} stale, ${report.warningFiles} warnings, ${report.freshFiles} fresh`,
report,
thresholds: thresholds,
formattedReport: enhancedReport,
kgInsights,
},
metadata: {
toolVersion: "1.0.0",
executionTime: Date.now() - startTime,
timestamp: new Date().toISOString(),
},
recommendations,
};
return response;
} catch (error) {
return {
success: false,
error: {
code: "FRESHNESS_TRACKING_FAILED",
message:
error instanceof Error
? error.message
: "Unknown error tracking documentation freshness",
resolution: "Check that the documentation path exists and is readable",
},
metadata: {
toolVersion: "1.0.0",
executionTime: Date.now() - startTime,
timestamp: new Date().toISOString(),
},
};
}
}
```
--------------------------------------------------------------------------------
/docs/tutorials/development-setup.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.970Z"
last_validated: "2025-12-09T19:41:38.601Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# Setting Up Your Development Environment
This tutorial covers setting up a development environment for ongoing documentation work with DocuMCP, including local testing, content workflows, and maintenance automation.
## What You'll Set Up
By the end of this tutorial, you'll have:
- Local documentation development environment
- Live reload and preview capabilities
- Content validation and testing workflow
- Automated quality checks
- Integration with your existing development tools
## Prerequisites
- Completed [Getting Started](getting-started.md) and [First Deployment](first-deployment.md)
- Node.js 20.0.0+ installed
- Your preferred code editor (VS Code recommended)
- Git and GitHub CLI (optional but recommended)
## Development Environment Setup
### Step 1: Local Development Server
Set up local development with live reload:
```bash
# Test local deployment before pushing to GitHub
"test my documentation build locally with live reload"
```
This will:
- Install development dependencies
- Start local server (typically on http://localhost:3000)
- Enable live reload for instant preview
- Validate build process
**For different SSGs:**
**Docusaurus:**
```bash
npm run start
# Opens http://localhost:3000 with live reload
```
**MkDocs:**
```bash
mkdocs serve
# Opens http://127.0.0.1:8000 with auto-reload
```
**Hugo:**
```bash
hugo server -D
# Opens http://localhost:1313 with live reload
```
**Jekyll:**
```bash
bundle exec jekyll serve --livereload
# Opens http://localhost:4000 with live reload
```
### Step 2: Content Validation Workflow
Set up automated content validation:
```bash
# Validate all documentation content
"validate my documentation content for accuracy and completeness"
```
This checks:
- **Link validation**: Internal and external links
- **Code syntax**: All code blocks and examples
- **Image references**: Missing or broken images
- **Content structure**: Diataxis compliance
- **SEO optimization**: Meta tags, headings
### Step 3: Quality Assurance Integration
Integrate quality checks into your workflow:
```bash
# Set up comprehensive documentation quality checks
"check all documentation links and validate content quality"
```
**Available validation levels:**
- **Basic**: Link checking and syntax validation
- **Comprehensive**: Full content analysis with Diataxis compliance
- **Advanced**: Performance testing and SEO analysis
### Step 4: Development Scripts Setup
Add these scripts to your `package.json`:
```json
{
"scripts": {
"docs:dev": "docusaurus start",
"docs:build": "docusaurus build",
"docs:serve": "docusaurus serve",
"docs:validate": "npm run docs:check-links && npm run docs:test-build",
"docs:check-links": "markdown-link-check docs/**/*.md",
"docs:test-build": "npm run docs:build && npm run docs:serve -- --no-open",
"docs:deploy": "npm run docs:validate && npm run docs:build"
}
}
```
## Editor Configuration
### VS Code Setup
Create `.vscode/settings.json`:
```json
{
"markdownlint.config": {
"MD013": false,
"MD033": false
},
"files.associations": {
"*.mdx": "mdx"
},
"editor.wordWrap": "on",
"editor.quickSuggestions": {
"strings": true
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.quickSuggestions": {
"comments": "off",
"strings": "off",
"other": "off"
}
}
}
```
**Recommended VS Code Extensions:**
- Markdown All in One
- markdownlint
- Prettier - Code formatter
- GitLens
- Live Server (for static preview)
### Content Writing Workflow
Establish a content creation workflow:
1. **Create branch** for documentation changes
2. **Write content** using Diataxis principles
3. **Test locally** with live server
4. **Validate content** using DocuMCP tools
5. **Review and refine** based on validation feedback
6. **Commit and push** to trigger deployment
## Automated Quality Checks
### Pre-commit Hooks
Set up automated checks before commits:
```bash
# Install husky for git hooks
npm install --save-dev husky
# Set up pre-commit hook
npx husky add .husky/pre-commit "npm run docs:validate"
```
Create `.husky/pre-commit`:
```bash
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
echo "🔍 Validating documentation..."
npm run docs:validate
echo "📝 Checking markdown formatting..."
npx prettier --check "docs/**/*.md"
echo "🔗 Validating links..."
npm run docs:check-links
```
### GitHub Actions Integration
Enhance your deployment workflow with quality gates:
```yaml
# .github/workflows/docs-quality.yml
name: Documentation Quality
on:
pull_request:
paths: ["docs/**", "*.md"]
jobs:
quality-check:
runs-on: ubuntu-latest
steps:
- 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: Validate documentation
run: |
npm run docs:validate
npm run docs:check-links
- name: Test build
run: npm run docs:build
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '✅ Documentation quality checks passed!'
});
```
## Content Management Strategies
### Diataxis Organization
Organize content following Diataxis principles:
**Directory Structure:**
```
docs/
├── tutorials/ # Learning-oriented (beginner-friendly)
│ ├── getting-started.md
│ ├── first-project.md
│ └── advanced-concepts.md
├── how-to-guides/ # Problem-solving (practical steps)
│ ├── troubleshooting.md
│ ├── configuration.md
│ └── deployment.md
├── reference/ # Information-oriented (comprehensive)
│ ├── api-reference.md
│ ├── cli-commands.md
│ └── configuration-options.md
└── explanation/ # Understanding-oriented (concepts)
├── architecture.md
├── design-decisions.md
└── best-practices.md
```
### Content Templates
Create content templates for consistency:
**Tutorial Template:**
```markdown
# [Action] Tutorial
## What You'll Learn
- Objective 1
- Objective 2
## Prerequisites
- Requirement 1
- Requirement 2
## Step-by-Step Instructions
### Step 1: [Action]
Instructions...
### Step 2: [Action]
Instructions...
## Verification
How to confirm success...
## Next Steps
Where to go next...
```
**How-to Guide Template:**
```markdown
# How to [Solve Problem]
## Problem
Clear problem statement...
## Solution
Step-by-step solution...
## Alternative Approaches
Other ways to solve this...
## Troubleshooting
Common issues and fixes...
```
## Performance Optimization
### Build Performance
Optimize build times:
```bash
# Enable build caching
export GATSBY_CACHE_DIR=.cache
export GATSBY_PUBLIC_DIR=public
# Parallel processing
export NODE_OPTIONS="--max-old-space-size=8192"
```
**For large sites:**
- Enable incremental builds
- Use build caching
- Optimize image processing
- Minimize plugin usage
### Development Server Performance
Speed up local development:
```bash
# Fast refresh mode (Docusaurus)
npm run start -- --fast-refresh
# Hot reload with polling (for file system issues)
npm run start -- --poll
# Open specific page
npm run start -- --host 0.0.0.0 --port 3001
```
## Maintenance Automation
### Scheduled Content Validation
Set up scheduled validation:
```yaml
# .github/workflows/scheduled-validation.yml
name: Scheduled Documentation Validation
on:
schedule:
- cron: "0 2 * * 1" # Every Monday at 2 AM
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Full validation
run: |
"check all documentation links with external validation"
"validate all content for accuracy and completeness"
- name: Create issue on failure
if: failure()
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Scheduled Documentation Validation Failed',
body: 'The weekly documentation validation found issues. Check the workflow logs.',
labels: ['documentation', 'maintenance']
});
```
### Dependency Updates
Automate dependency maintenance:
```yaml
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
labels:
- "dependencies"
- "documentation"
```
## Collaboration Workflow
### Team Development
For team documentation:
1. **Branching strategy**: Feature branches for documentation changes
2. **Review process**: PR reviews for all documentation updates
3. **Style guide**: Consistent writing and formatting standards
4. **Content ownership**: Assign sections to team members
### Review Checklist
Documentation PR review checklist:
- [ ] Content follows Diataxis principles
- [ ] All links work (internal and external)
- [ ] Code examples are tested and accurate
- [ ] Images are optimized and accessible
- [ ] SEO metadata is complete
- [ ] Mobile responsiveness verified
- [ ] Build succeeds locally and in CI
## Next Steps
Your development environment is now ready! Next:
1. **[Learn advanced prompting](../how-to/prompting-guide.md)** for DocuMCP
2. **[Set up monitoring](../how-to/site-monitoring.md)** for your live site
3. **[Optimize for performance](../how-to/performance-optimization.md)**
4. **[Configure custom domains](../how-to/custom-domains.md)** (optional)
## Troubleshooting
**Common development issues:**
**Port conflicts:**
```bash
# Change default port
npm run start -- --port 3001
```
**Memory issues:**
```bash
# Increase Node.js memory limit
export NODE_OPTIONS="--max-old-space-size=8192"
```
**File watching problems:**
```bash
# Enable polling for file changes
npm run start -- --poll
```
**Cache issues:**
```bash
# Clear build cache
rm -rf .docusaurus .cache public
npm run start
```
## Summary
You now have:
✅ Local development environment with live reload
✅ Content validation and quality checking
✅ Automated pre-commit hooks
✅ CI/CD integration for quality gates
✅ Performance optimization
✅ Maintenance automation
✅ Team collaboration workflow
Your documentation development environment is production-ready!
```
--------------------------------------------------------------------------------
/tests/tools/recommend-ssg-preferences.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for Phase 2.2: User Preference Integration
* Tests recommend_ssg tool with user preference learning and application
*/
import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
import { promises as fs } from "fs";
import { join } from "path";
import { tmpdir } from "os";
import {
initializeKnowledgeGraph,
createOrUpdateProject,
} from "../../src/memory/kg-integration.js";
import { recommendSSG } from "../../src/tools/recommend-ssg.js";
import { MemoryManager } from "../../src/memory/manager.js";
import {
getUserPreferenceManager,
clearPreferenceManagerCache,
} from "../../src/memory/user-preferences.js";
describe("recommendSSG with User Preferences (Phase 2.2)", () => {
let testDir: string;
let originalEnv: string | undefined;
let memoryManager: MemoryManager;
// Helper to create analysis memory entry in correct format
const createAnalysisMemory = async (analysisData: any) => {
return await memoryManager.remember("analysis", analysisData);
};
beforeEach(async () => {
// Create temporary test directory
testDir = join(tmpdir(), `recommend-ssg-preferences-test-${Date.now()}`);
await fs.mkdir(testDir, { recursive: true });
// Set environment variable for storage
originalEnv = process.env.DOCUMCP_STORAGE_DIR;
process.env.DOCUMCP_STORAGE_DIR = testDir;
// Initialize KG and memory
await initializeKnowledgeGraph(testDir);
memoryManager = new MemoryManager(testDir);
await memoryManager.initialize();
// Clear preference manager cache
clearPreferenceManagerCache();
});
afterEach(async () => {
// Restore environment
if (originalEnv) {
process.env.DOCUMCP_STORAGE_DIR = originalEnv;
} else {
delete process.env.DOCUMCP_STORAGE_DIR;
}
// Clean up test directory
try {
await fs.rm(testDir, { recursive: true, force: true });
} catch (error) {
console.warn("Failed to clean up test directory:", error);
}
// Clear preference manager cache
clearPreferenceManagerCache();
});
describe("User Preference Application", () => {
it("should apply user preferences when auto-apply is enabled", async () => {
// Set up user preferences
const userId = "test-user-1";
const manager = await getUserPreferenceManager(userId);
await manager.updatePreferences({
preferredSSGs: ["hugo", "eleventy"],
autoApplyPreferences: true,
});
// Create analysis that would normally recommend Docusaurus
const memoryEntry = await createAnalysisMemory({
path: "/test/js-project",
dependencies: {
ecosystem: "javascript",
languages: ["javascript", "typescript"],
},
structure: { totalFiles: 60 },
});
// Get recommendation
const result = await recommendSSG({
analysisId: memoryEntry.id,
userId,
});
const content = result.content[0];
expect(content.type).toBe("text");
const data = JSON.parse(content.text);
// Should recommend Hugo (user's top preference)
expect(data.recommended).toBe("hugo");
expect(data.reasoning[0]).toContain("Switched to hugo");
expect(data.reasoning[0]).toContain("usage history");
});
it("should not apply preferences when auto-apply is disabled", async () => {
const userId = "test-user-2";
const manager = await getUserPreferenceManager(userId);
await manager.updatePreferences({
preferredSSGs: ["jekyll"],
autoApplyPreferences: false,
});
const memoryEntry = await createAnalysisMemory({
path: "/test/js-project",
dependencies: {
ecosystem: "javascript",
languages: ["javascript"],
},
structure: { totalFiles: 60 },
});
const result = await recommendSSG({
analysisId: memoryEntry.id,
userId,
});
const content = result.content[0];
const data = JSON.parse(content.text);
// Should use default recommendation, not user preference
expect(data.recommended).toBe("docusaurus");
expect(data.reasoning[0]).not.toContain("Switched");
});
it("should keep recommendation if it matches user preference", async () => {
const userId = "test-user-3";
const manager = await getUserPreferenceManager(userId);
await manager.updatePreferences({
preferredSSGs: ["mkdocs"],
autoApplyPreferences: true,
});
const memoryEntry = await createAnalysisMemory({
path: "/test/python-project",
dependencies: {
ecosystem: "python",
languages: ["python"],
},
structure: { totalFiles: 40 },
});
const result = await recommendSSG({
analysisId: memoryEntry.id,
userId,
});
const content = result.content[0];
const data = JSON.parse(content.text);
// Should recommend mkdocs (matches both analysis and preference)
expect(data.recommended).toBe("mkdocs");
// Either "Matches" or "Switched to" is acceptable - both indicate preference was applied
expect(data.reasoning[0]).toMatch(
/Matches your preferred SSG|Switched to mkdocs/,
);
});
it("should switch to user preference even if not ideal for ecosystem", async () => {
const userId = "test-user-4";
const manager = await getUserPreferenceManager(userId);
await manager.updatePreferences({
preferredSSGs: ["mkdocs", "jekyll"], // Python/Ruby SSGs
autoApplyPreferences: true,
});
const memoryEntry = await createAnalysisMemory({
path: "/test/js-project",
dependencies: {
ecosystem: "javascript",
languages: ["javascript"],
},
structure: { totalFiles: 60 },
});
const result = await recommendSSG({
analysisId: memoryEntry.id,
userId,
});
const content = result.content[0];
const data = JSON.parse(content.text);
// Should switch to mkdocs (user's top preference)
// User preferences override ecosystem recommendations
expect(data.recommended).toBe("mkdocs");
expect(data.reasoning[0]).toContain("Switched to mkdocs");
expect(data.reasoning[0]).toContain("usage history");
});
});
describe("Preference Tracking Integration", () => {
it("should use default user when no userId provided", async () => {
const memoryEntry = await createAnalysisMemory({
path: "/test/project",
dependencies: {
ecosystem: "javascript",
languages: ["javascript"],
},
structure: { totalFiles: 50 },
});
// Should not throw error with no userId
const result = await recommendSSG({
analysisId: memoryEntry.id,
});
const content = result.content[0];
expect(content.type).toBe("text");
const data = JSON.parse(content.text);
expect(data.recommended).toBeDefined();
});
it("should work with multiple users independently", async () => {
const user1 = "user1";
const user2 = "user2";
// Set different preferences for each user
const manager1 = await getUserPreferenceManager(user1);
await manager1.updatePreferences({
preferredSSGs: ["hugo"],
autoApplyPreferences: true,
});
const manager2 = await getUserPreferenceManager(user2);
await manager2.updatePreferences({
preferredSSGs: ["eleventy"],
autoApplyPreferences: true,
});
const memoryEntry = await createAnalysisMemory({
path: "/test/project",
dependencies: {
ecosystem: "javascript",
languages: ["javascript"],
},
structure: { totalFiles: 50 },
});
// Get recommendations for both users
const result1 = await recommendSSG({
analysisId: memoryEntry.id,
userId: user1,
});
const result2 = await recommendSSG({
analysisId: memoryEntry.id,
userId: user2,
});
const data1 = JSON.parse(result1.content[0].text);
const data2 = JSON.parse(result2.content[0].text);
// Each user should get their preferred SSG
expect(data1.recommended).toBe("hugo");
expect(data2.recommended).toBe("eleventy");
});
});
describe("Confidence Adjustment", () => {
it("should boost confidence when preference is applied", async () => {
const userId = "test-user-5";
const manager = await getUserPreferenceManager(userId);
await manager.updatePreferences({
preferredSSGs: ["eleventy"],
autoApplyPreferences: true,
});
const memoryEntry = await createAnalysisMemory({
path: "/test/js-project",
dependencies: {
ecosystem: "javascript",
languages: ["javascript"],
},
structure: { totalFiles: 60 },
});
const result = await recommendSSG({
analysisId: memoryEntry.id,
userId,
});
const content = result.content[0];
const data = JSON.parse(content.text);
// Confidence should be boosted when preference is applied
// Base confidence varies by SSG, but preference adds +0.05 boost
expect(data.confidence).toBeGreaterThan(0.7);
expect(data.reasoning[0]).toContain("🎯");
});
});
describe("Edge Cases", () => {
it("should handle empty preferred SSGs list", async () => {
const userId = "test-user-6";
const manager = await getUserPreferenceManager(userId);
await manager.updatePreferences({
preferredSSGs: [],
autoApplyPreferences: true,
});
const memoryEntry = await createAnalysisMemory({
path: "/test/project",
dependencies: {
ecosystem: "javascript",
languages: ["javascript"],
},
structure: { totalFiles: 50 },
});
const result = await recommendSSG({
analysisId: memoryEntry.id,
userId,
});
const content = result.content[0];
const data = JSON.parse(content.text);
// Should use default recommendation
expect(data.recommended).toBe("docusaurus");
expect(data.reasoning[0]).not.toContain("Switched");
});
it("should handle preference manager initialization failure gracefully", async () => {
const memoryEntry = await createAnalysisMemory({
path: "/test/project",
dependencies: {
ecosystem: "javascript",
languages: ["javascript"],
},
structure: { totalFiles: 50 },
});
// Should not throw even with invalid userId
const result = await recommendSSG({
analysisId: memoryEntry.id,
userId: "any-user-id",
});
const content = result.content[0];
expect(content.type).toBe("text");
const data = JSON.parse(content.text);
expect(data.recommended).toBeDefined();
});
});
});
```
--------------------------------------------------------------------------------
/tests/prompts/guided-workflow-prompts.test.ts:
--------------------------------------------------------------------------------
```typescript
import { generateTechnicalWriterPrompts } from "../../src/prompts/technical-writer-prompts.js";
import { promises as fs } from "fs";
import { join } from "path";
import { tmpdir } from "os";
describe("Guided Workflow Prompts", () => {
let tempDir: string;
beforeEach(async () => {
tempDir = join(
tmpdir(),
`test-prompts-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
);
await fs.mkdir(tempDir, { recursive: true });
// Create a test project structure
await fs.writeFile(
join(tempDir, "package.json"),
JSON.stringify({
name: "test-project",
version: "1.0.0",
dependencies: { react: "^18.0.0" },
scripts: { test: "jest", build: "webpack" },
}),
);
await fs.writeFile(
join(tempDir, "README.md"),
"# Test Project\n\nThis is a test project.",
);
await fs.mkdir(join(tempDir, "src"));
await fs.writeFile(join(tempDir, "src/index.js"), 'console.log("hello");');
await fs.mkdir(join(tempDir, "tests"));
await fs.writeFile(
join(tempDir, "tests/index.test.js"),
'test("basic", () => {});',
);
});
afterEach(async () => {
try {
await fs.rm(tempDir, { recursive: true });
} catch {
// Ignore cleanup errors
}
});
describe("analyze-and-recommend prompt", () => {
it("should generate comprehensive analysis and recommendation prompt", async () => {
const messages = await generateTechnicalWriterPrompts(
"analyze-and-recommend",
tempDir,
{
analysis_depth: "standard",
preferences: "performance and ease of use",
},
);
expect(messages).toHaveLength(1);
expect(messages[0]).toHaveProperty("role", "user");
expect(messages[0]).toHaveProperty("content");
expect(messages[0].content).toHaveProperty("type", "text");
expect(messages[0].content.text).toContain(
"Execute a complete repository analysis",
);
expect(messages[0].content.text).toContain("SSG recommendation workflow");
expect(messages[0].content.text).toContain("Analysis Depth: standard");
expect(messages[0].content.text).toContain(
"Preferences: performance and ease of use",
);
expect(messages[0].content.text).toContain("Repository Analysis");
expect(messages[0].content.text).toContain("Implementation Guidance");
expect(messages[0].content.text).toContain("Best Practices");
});
it("should use default values when optional parameters are not provided", async () => {
const messages = await generateTechnicalWriterPrompts(
"analyze-and-recommend",
tempDir,
{},
);
expect(messages[0].content.text).toContain("Analysis Depth: standard");
expect(messages[0].content.text).toContain(
"balanced approach with good community support",
);
});
it("should include project context information", async () => {
const messages = await generateTechnicalWriterPrompts(
"analyze-and-recommend",
tempDir,
{
analysis_depth: "deep",
},
);
expect(messages[0].content.text).toContain("Type: node_application");
expect(messages[0].content.text).toContain("Has Tests: true");
expect(messages[0].content.text).toContain("Package Manager: npm");
});
});
describe("setup-documentation prompt", () => {
it("should generate comprehensive documentation setup prompt", async () => {
const messages = await generateTechnicalWriterPrompts(
"setup-documentation",
tempDir,
{
ssg_type: "docusaurus",
include_examples: true,
},
);
expect(messages).toHaveLength(1);
expect(messages[0]).toHaveProperty("role", "user");
expect(messages[0].content.text).toContain(
"Create a comprehensive documentation structure",
);
expect(messages[0].content.text).toContain("SSG Type: docusaurus");
expect(messages[0].content.text).toContain("Include Examples: true");
expect(messages[0].content.text).toContain(
"Diataxis Framework Implementation",
);
expect(messages[0].content.text).toContain(
"Tutorials: Learning-oriented content",
);
expect(messages[0].content.text).toContain(
"How-to Guides: Problem-solving content",
);
expect(messages[0].content.text).toContain(
"Reference: Information-oriented content",
);
expect(messages[0].content.text).toContain(
"Explanations: Understanding-oriented content",
);
expect(messages[0].content.text).toContain("Configuration Setup");
expect(messages[0].content.text).toContain("GitHub Pages deployment");
expect(messages[0].content.text).toContain("with examples");
});
it("should handle minimal configuration", async () => {
const messages = await generateTechnicalWriterPrompts(
"setup-documentation",
tempDir,
{
include_examples: false,
},
);
expect(messages[0].content.text).toContain(
"SSG Type: recommended based on project analysis",
);
expect(messages[0].content.text).toContain("Include Examples: false");
expect(messages[0].content.text).toContain("templates");
expect(messages[0].content.text).not.toContain("with examples");
});
it("should include current documentation gaps", async () => {
const messages = await generateTechnicalWriterPrompts(
"setup-documentation",
tempDir,
{},
);
expect(messages[0].content.text).toContain("Current Documentation Gaps:");
expect(messages[0].content.text).toContain("Development Integration");
expect(messages[0].content.text).toContain(
"production-ready documentation system",
);
});
});
describe("troubleshoot-deployment prompt", () => {
it("should generate comprehensive troubleshooting prompt", async () => {
const messages = await generateTechnicalWriterPrompts(
"troubleshoot-deployment",
tempDir,
{
repository: "owner/repo",
deployment_url: "https://owner.github.io/repo",
issue_description: "build failing on GitHub Actions",
},
);
expect(messages).toHaveLength(1);
expect(messages[0]).toHaveProperty("role", "user");
expect(messages[0].content.text).toContain(
"Diagnose and fix GitHub Pages deployment issues",
);
expect(messages[0].content.text).toContain("Repository: owner/repo");
expect(messages[0].content.text).toContain(
"Expected URL: https://owner.github.io/repo",
);
expect(messages[0].content.text).toContain(
"Issue Description: build failing on GitHub Actions",
);
expect(messages[0].content.text).toContain("Troubleshooting Checklist");
expect(messages[0].content.text).toContain("Repository Settings");
expect(messages[0].content.text).toContain("Build Configuration");
expect(messages[0].content.text).toContain("Content Issues");
expect(messages[0].content.text).toContain("Deployment Workflow");
expect(messages[0].content.text).toContain("Performance and Security");
expect(messages[0].content.text).toContain("Root cause analysis");
expect(messages[0].content.text).toContain("Systematic Testing");
});
it("should use default values for optional parameters", async () => {
const messages = await generateTechnicalWriterPrompts(
"troubleshoot-deployment",
tempDir,
{
repository: "test/repo",
},
);
expect(messages[0].content.text).toContain(
"Expected URL: GitHub Pages URL",
);
expect(messages[0].content.text).toContain(
"Issue Description: deployment not working as expected",
);
});
it("should include project context for troubleshooting", async () => {
const messages = await generateTechnicalWriterPrompts(
"troubleshoot-deployment",
tempDir,
{
repository: "test/repo",
},
);
expect(messages[0].content.text).toContain("Project Context");
expect(messages[0].content.text).toContain("Type: node_application");
expect(messages[0].content.text).toContain("Diagnostic Approach");
expect(messages[0].content.text).toContain("Systematic Testing");
});
});
describe("Error handling", () => {
it("should throw error for unknown prompt type", async () => {
await expect(
generateTechnicalWriterPrompts("unknown-prompt-type", tempDir, {}),
).rejects.toThrow("Unknown prompt type: unknown-prompt-type");
});
it("should handle missing project directory gracefully", async () => {
const nonExistentDir = join(tmpdir(), "non-existent-dir");
// Should not throw, but may have reduced context
const messages = await generateTechnicalWriterPrompts(
"analyze-and-recommend",
nonExistentDir,
{},
);
expect(messages).toHaveLength(1);
expect(messages[0].content.text).toContain("repository analysis");
});
it("should handle malformed package.json gracefully", async () => {
await fs.writeFile(join(tempDir, "package.json"), "invalid json content");
const messages = await generateTechnicalWriterPrompts(
"setup-documentation",
tempDir,
{},
);
expect(messages).toHaveLength(1);
expect(messages[0].content.text).toContain("documentation structure");
});
});
describe("Prompt content validation", () => {
it("should generate prompts with consistent structure", async () => {
const promptTypes = [
"analyze-and-recommend",
"setup-documentation",
"troubleshoot-deployment",
];
for (const promptType of promptTypes) {
const args =
promptType === "troubleshoot-deployment"
? { repository: "test/repo" }
: {};
const messages = await generateTechnicalWriterPrompts(
promptType,
tempDir,
args,
);
expect(messages).toHaveLength(1);
expect(messages[0]).toHaveProperty("role", "user");
expect(messages[0]).toHaveProperty("content");
expect(messages[0].content).toHaveProperty("type", "text");
expect(messages[0].content.text).toBeTruthy();
expect(messages[0].content.text.length).toBeGreaterThan(100);
}
});
it("should include project-specific information in all prompts", async () => {
const promptTypes = ["analyze-and-recommend", "setup-documentation"];
for (const promptType of promptTypes) {
const messages = await generateTechnicalWriterPrompts(
promptType,
tempDir,
{},
);
expect(messages[0].content.text).toContain("Project Context");
expect(messages[0].content.text).toContain("Type:");
expect(messages[0].content.text).toContain("Languages:");
}
});
});
});
```
--------------------------------------------------------------------------------
/tests/utils/llm-client.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for LLM Client
*/
import {
createLLMClient,
DeepSeekClient,
isLLMAvailable,
type LLMConfig,
type SemanticAnalysis,
type SimulationResult,
} from '../../src/utils/llm-client.js';
// Mock fetch globally
global.fetch = jest.fn();
describe('LLM Client', () => {
beforeEach(() => {
jest.clearAllMocks();
// Clear environment variables
delete process.env.DOCUMCP_LLM_API_KEY;
delete process.env.DOCUMCP_LLM_PROVIDER;
delete process.env.DOCUMCP_LLM_MODEL;
});
describe('createLLMClient', () => {
test('should return null when no API key is provided', () => {
const client = createLLMClient();
expect(client).toBeNull();
});
test('should create client with environment variables', () => {
process.env.DOCUMCP_LLM_API_KEY = 'test-key';
const client = createLLMClient();
expect(client).not.toBeNull();
expect(client).toBeInstanceOf(DeepSeekClient);
});
test('should create client with config parameter', () => {
const client = createLLMClient({
provider: 'deepseek',
apiKey: 'test-key',
model: 'deepseek-chat',
});
expect(client).not.toBeNull();
expect(client).toBeInstanceOf(DeepSeekClient);
});
test('should use default provider and model', () => {
process.env.DOCUMCP_LLM_API_KEY = 'test-key';
const client = createLLMClient();
expect(client).not.toBeNull();
});
test('should support multiple providers', () => {
const providers = ['deepseek', 'openai', 'anthropic', 'ollama'] as const;
for (const provider of providers) {
const client = createLLMClient({
provider,
apiKey: 'test-key',
model: 'test-model',
});
expect(client).not.toBeNull();
}
});
});
describe('isLLMAvailable', () => {
test('should return false when no API key is set', () => {
expect(isLLMAvailable()).toBe(false);
});
test('should return true when DOCUMCP_LLM_API_KEY is set', () => {
process.env.DOCUMCP_LLM_API_KEY = 'test-key';
expect(isLLMAvailable()).toBe(true);
});
test('should return true when OPENAI_API_KEY is set', () => {
process.env.OPENAI_API_KEY = 'test-key';
expect(isLLMAvailable()).toBe(true);
});
});
describe('DeepSeekClient', () => {
let client: DeepSeekClient;
const config: LLMConfig = {
provider: 'deepseek',
apiKey: 'test-api-key',
model: 'deepseek-chat',
maxTokens: 1000,
timeout: 5000,
};
beforeEach(() => {
client = new DeepSeekClient(config);
});
describe('isAvailable', () => {
test('should return true when API key is present', () => {
expect(client.isAvailable()).toBe(true);
});
test('should return false when API key is missing', () => {
const noKeyClient = new DeepSeekClient({ ...config, apiKey: undefined });
expect(noKeyClient.isAvailable()).toBe(false);
});
});
describe('complete', () => {
test('should throw error when client is not available', async () => {
const noKeyClient = new DeepSeekClient({ ...config, apiKey: undefined });
await expect(noKeyClient.complete('test prompt')).rejects.toThrow(
'LLM client is not available'
);
});
test('should make successful API request', async () => {
const mockResponse = {
choices: [
{
message: {
content: 'Test response',
},
},
],
};
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => mockResponse,
});
const response = await client.complete('Test prompt');
expect(response).toBe('Test response');
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/chat/completions'),
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
'Content-Type': 'application/json',
'Authorization': 'Bearer test-api-key',
}),
})
);
});
test('should handle API errors', async () => {
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: false,
status: 500,
text: async () => 'Internal server error',
});
await expect(client.complete('Test prompt')).rejects.toThrow(
'LLM API error: 500'
);
});
test('should handle timeout', async () => {
const shortTimeoutClient = new DeepSeekClient({ ...config, timeout: 100 });
(global.fetch as jest.Mock).mockImplementationOnce(() =>
new Promise((resolve) => setTimeout(resolve, 1000))
);
await expect(shortTimeoutClient.complete('Test prompt')).rejects.toThrow();
});
test('should handle network errors', async () => {
(global.fetch as jest.Mock).mockRejectedValueOnce(new Error('Network error'));
await expect(client.complete('Test prompt')).rejects.toThrow('Network error');
});
});
describe('analyzeCodeChange', () => {
test('should analyze code changes successfully', async () => {
const mockAnalysis: SemanticAnalysis = {
hasBehavioralChange: true,
breakingForExamples: false,
changeDescription: 'Function parameter type changed',
affectedDocSections: ['API Reference'],
confidence: 0.9,
};
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({
choices: [
{
message: {
content: JSON.stringify(mockAnalysis),
},
},
],
}),
});
const codeBefore = 'function test(x: number) { return x * 2; }';
const codeAfter = 'function test(x: string) { return x.repeat(2); }';
const result = await client.analyzeCodeChange(codeBefore, codeAfter);
expect(result.hasBehavioralChange).toBe(true);
expect(result.breakingForExamples).toBe(false);
expect(result.confidence).toBe(0.9);
expect(result.affectedDocSections).toContain('API Reference');
});
test('should handle JSON in markdown code blocks', async () => {
const mockAnalysis: SemanticAnalysis = {
hasBehavioralChange: true,
breakingForExamples: true,
changeDescription: 'Breaking change',
affectedDocSections: ['Examples'],
confidence: 0.85,
};
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({
choices: [
{
message: {
content: '```json\n' + JSON.stringify(mockAnalysis) + '\n```',
},
},
],
}),
});
const result = await client.analyzeCodeChange('code1', 'code2');
expect(result.hasBehavioralChange).toBe(true);
expect(result.confidence).toBe(0.85);
});
test('should return fallback result on parse error', async () => {
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({
choices: [
{
message: {
content: 'Invalid JSON response',
},
},
],
}),
});
const result = await client.analyzeCodeChange('code1', 'code2');
expect(result.confidence).toBe(0);
expect(result.hasBehavioralChange).toBe(false);
expect(result.changeDescription).toContain('Analysis failed');
});
test('should normalize confidence values', async () => {
const mockAnalysis = {
hasBehavioralChange: true,
breakingForExamples: false,
changeDescription: 'Test',
affectedDocSections: [],
confidence: 1.5, // Invalid: > 1
};
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({
choices: [
{
message: {
content: JSON.stringify(mockAnalysis),
},
},
],
}),
});
const result = await client.analyzeCodeChange('code1', 'code2');
expect(result.confidence).toBe(1); // Should be clamped to 1
});
});
describe('simulateExecution', () => {
test('should simulate execution successfully', async () => {
const mockSimulation: SimulationResult = {
success: true,
expectedOutput: '42',
actualOutput: '42',
matches: true,
differences: [],
confidence: 0.95,
};
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({
choices: [
{
message: {
content: JSON.stringify(mockSimulation),
},
},
],
}),
});
const example = 'const result = multiply(6, 7);';
const implementation = 'function multiply(a, b) { return a * b; }';
const result = await client.simulateExecution(example, implementation);
expect(result.success).toBe(true);
expect(result.matches).toBe(true);
expect(result.confidence).toBe(0.95);
expect(result.differences).toHaveLength(0);
});
test('should detect mismatches', async () => {
const mockSimulation: SimulationResult = {
success: true,
expectedOutput: '42',
actualOutput: '43',
matches: false,
differences: ['Output mismatch: expected 42, got 43'],
confidence: 0.8,
};
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({
choices: [
{
message: {
content: JSON.stringify(mockSimulation),
},
},
],
}),
});
const result = await client.simulateExecution('example', 'impl');
expect(result.matches).toBe(false);
expect(result.differences.length).toBeGreaterThan(0);
});
test('should return fallback result on error', async () => {
(global.fetch as jest.Mock).mockRejectedValueOnce(new Error('Network error'));
const result = await client.simulateExecution('example', 'impl');
expect(result.success).toBe(false);
expect(result.matches).toBe(false);
expect(result.confidence).toBe(0);
expect(result.differences.length).toBeGreaterThan(0);
});
});
describe('rate limiting', () => {
test('should respect rate limits', async () => {
const mockResponse = {
choices: [{ message: { content: 'Response' } }],
};
(global.fetch as jest.Mock).mockResolvedValue({
ok: true,
json: async () => mockResponse,
});
// Make multiple requests quickly
const promises = Array(5).fill(null).map(() =>
client.complete('test')
);
const results = await Promise.all(promises);
expect(results).toHaveLength(5);
expect(global.fetch).toHaveBeenCalledTimes(5);
});
});
});
});
```
--------------------------------------------------------------------------------
/docs/how-to/local-testing.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.952Z"
last_validated: "2025-12-09T19:41:38.583Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# Local Documentation Testing
This guide shows how to test your documentation locally before deploying to GitHub Pages using containerized environments that don't affect your system.
## 🎯 Best Practice: Test Build Before Pushing
**Always test your documentation build locally before pushing to git** to ensure GitHub Actions will build successfully:
### Option 1: Test Node.js Build (Recommended - Matches GitHub Actions)
```bash
# Test the same build process GitHub Actions uses
cd docs
npm ci
npm run build
```
This uses the exact same process as GitHub Actions and catches build issues early.
### Option 2: Test Docker Build (Optional - For Container Validation)
```bash
# Quick Docker validation (if Dockerfile is configured)
docker build -f Dockerfile.docs -t documcp-docs-test . && echo "✅ Docker build ready"
```
**Note**: Docker testing validates containerized environments, but GitHub Actions uses Node.js directly, so Option 1 is more reliable for CI validation.
## Quick Start - Containerized Testing
DocuMCP automatically generates a containerized testing environment that requires only Docker or Podman:
```bash
# Run the containerized testing script
./test-docs-local.sh
```
This script will:
1. **Detect** your container runtime (Podman or Docker)
2. **Build** a documentation container
3. **Check** for broken links in your documentation
4. **Serve** the documentation at http://localhost:3001
### Prerequisites
You need either Docker or Podman installed:
**Option 1: Podman (rootless, more secure)**
```bash
# macOS
brew install podman
# Ubuntu/Debian
sudo apt-get install podman
# RHEL/CentOS/Fedora
sudo dnf install podman
```
**Option 2: Docker**
```bash
# macOS
brew install docker
# Or download from: https://docs.docker.com/get-docker/
```
## Container-Based Testing Methods
### Method 1: Using the Generated Script (Recommended)
```bash
# Simple one-command testing
./test-docs-local.sh
```
### Method 2: Using Docker Compose
```bash
# Build and run with Docker Compose
docker-compose -f docker-compose.docs.yml up --build
# Or with Podman Compose
podman-compose -f docker-compose.docs.yml up --build
```
### Method 3: Manual Container Commands
```bash
# Build the container
docker build -f Dockerfile.docs -t documcp-docs .
# or: podman build -f Dockerfile.docs -t documcp-docs .
# Run the container
docker run --rm -p 3001:3001 documcp-docs
# or: podman run --rm -p 3001:3001 documcp-docs
```
### Method 4: Pre-Push Docker Validation
**Recommended workflow before pushing to git:**
```bash
# 1. Test Docker build (validates CI will work)
docker build -f Dockerfile.docs -t documcp-docs-test .
# 2. If successful, test locally
docker run --rm -p 3001:3001 documcp-docs-test
# 3. Verify at http://localhost:3001, then push to git
```
This ensures your Docker build matches what GitHub Actions will use.
### Method 5: Legacy Local Installation (Not Recommended)
If you prefer to install dependencies locally (affects your system):
```bash
cd docs
npm install
npm run build
npm run serve
```
## Pre-Push Checklist
Before pushing documentation changes to git, ensure:
- [ ] **Node.js build succeeds**: `cd docs && npm ci && npm run build` (matches GitHub Actions)
- [ ] **Local preview works**: Documentation serves correctly at http://localhost:3001
- [ ] **No broken links**: Run link checker (included in test script)
- [ ] **Build output valid**: Check `docs/build` directory structure
- [ ] **No console errors**: Check browser console for JavaScript errors
**Quick pre-push validation command (Node.js - Recommended):**
```bash
cd docs && npm ci && npm run build && echo "✅ Ready to push!"
```
**Alternative Docker validation (if Dockerfile is configured):**
```bash
docker build -f Dockerfile.docs -t documcp-docs-test . && \
docker run --rm -d -p 3001:3001 --name docs-test documcp-docs-test && \
sleep 5 && curl -f http://localhost:3001 > /dev/null && \
docker stop docs-test && echo "✅ Ready to push!"
```
**Note**: GitHub Actions uses Node.js directly (not Docker), so testing with `npm run build` is the most reliable way to validate CI will succeed.
## Verification Checklist
### ✅ Content Verification
- [ ] All pages load without errors
- [ ] Navigation works correctly
- [ ] Links between pages function properly
- [ ] Search functionality works (if enabled)
- [ ] Code blocks render correctly with syntax highlighting
- [ ] Images and assets load properly
### ✅ Structure Verification
- [ ] Sidebar navigation reflects your documentation structure
- [ ] Categories and sections are properly organized
- [ ] Page titles and descriptions are accurate
- [ ] Breadcrumb navigation works
- [ ] Footer links are functional
### ✅ Content Quality
- [ ] No broken internal links
- [ ] No broken external links
- [ ] Code examples are up-to-date
- [ ] Screenshots are current and clear
- [ ] All content follows Diataxis framework principles
### ✅ Performance Testing
- [ ] Pages load quickly (< 3 seconds)
- [ ] Search is responsive
- [ ] No console errors in browser developer tools
- [ ] Mobile responsiveness works correctly
## Troubleshooting Common Issues
### Container Build Failures
**Problem**: Container build fails
**Solutions**:
```bash
# Clean up any existing containers and images
docker system prune -f
# or: podman system prune -f
# Rebuild from scratch
docker build --no-cache -f Dockerfile.docs -t documcp-docs .
# or: podman build --no-cache -f Dockerfile.docs -t documcp-docs .
# Check for syntax errors in markdown files
find docs -name "*.md" -exec npx markdownlint {} \;
```
### Container Runtime Issues
**Problem**: "Neither Podman nor Docker found"
**Solutions**:
```bash
# Check if Docker/Podman is installed and running
docker --version
podman --version
# On macOS, ensure Docker Desktop is running
# On Linux, ensure Docker daemon is started:
sudo systemctl start docker
# For Podman on macOS, start the machine:
podman machine start
```
### Broken Links
**Problem**: Links between documentation pages don't work
**Solutions**:
- Check that file paths in your markdown match actual file locations
- Ensure relative links use correct syntax (e.g., `[text](../reference/configuration.md)`)
- Verify that `sidebars.js` references match actual file names
### Missing Pages
**Problem**: Some documentation pages don't appear in navigation
**Solutions**:
- Update `docs-site/sidebars.js` to include new pages
- Ensure files are in the correct directory structure
- Check that frontmatter is properly formatted
### Styling Issues
**Problem**: Documentation doesn't look right
**Solutions**:
- Check `docs-site/src/css/custom.css` for custom styles
- Verify Docusaurus theme configuration
- Clear browser cache and reload
## Link Checking
### Automated Link Checking
DocuMCP provides built-in link checking:
```bash
# Check all links
npm run docs:check-links
# Check only external links
npm run docs:check-links:external
# Check only internal links
npm run docs:check-links:internal
```
### Manual Link Checking
Use markdown-link-check for comprehensive link validation:
```bash
# Install globally
npm install -g markdown-link-check
# Check specific file
markdown-link-check docs/index.md
# Check all markdown files
find docs -name "*.md" -exec markdown-link-check {} \;
```
## Container Configuration Testing
### Verify Container Configuration
```bash
# Test container health
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# or: podman ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# Check container logs
docker logs documcp-docs-test
# or: podman logs documcp-docs-test
# Execute commands inside running container
docker exec -it documcp-docs-test sh
# or: podman exec -it documcp-docs-test sh
```
### Test Different Container Environments
```bash
# Test production build in container
docker run --rm -e NODE_ENV=production -p 3001:3001 documcp-docs
# Interactive debugging mode
docker run --rm -it --entrypoint sh documcp-docs
# Inside container: cd docs-site && npm run build --verbose
```
## Deployment Preview
Before deploying to GitHub Pages, test with production settings:
```bash
# Build with production configuration
npm run build
# Serve the production build locally
npm run serve
```
This simulates exactly what GitHub Pages will serve.
## Integration with Development Workflow
### Pre-commit Testing
Add documentation testing to your git hooks:
```bash
# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# Run documentation tests
./test-docs-local.sh --build-only
# Run your regular tests
npm test
```
### CI/CD Integration
Add documentation testing to your GitHub Actions:
```yaml
# .github/workflows/docs-test.yml
name: Documentation Tests
on:
pull_request:
paths:
- "docs/**"
- "docs-site/**"
jobs:
test-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: "docs-site/package-lock.json"
- name: Test documentation build
run: ./test-docs-local.sh --build-only
```
## Advanced Testing
### Performance Testing
```bash
# Install lighthouse CLI
npm install -g lighthouse
# Test performance of local documentation
lighthouse http://localhost:3001 --output=json --output-path=./lighthouse-report.json
# Check specific performance metrics
lighthouse http://localhost:3001 --only-categories=performance
```
### Accessibility Testing
```bash
# Test accessibility
lighthouse http://localhost:3001 --only-categories=accessibility
# Use axe for detailed accessibility testing
npm install -g axe-cli
axe http://localhost:3001
```
### SEO Testing
```bash
# Test SEO optimization
lighthouse http://localhost:3001 --only-categories=seo
# Check meta tags and structure
curl -s http://localhost:3001 | grep -E "<title>|<meta"
```
## Automated Testing Script
Create a comprehensive test script:
```bash
#!/bin/bash
# comprehensive-docs-test.sh
echo "🧪 Running comprehensive documentation tests..."
# Build test
echo "📦 Testing build..."
cd docs-site && npm run build
# Link checking
echo "🔗 Checking links..."
cd .. && npm run docs:check-links:all
# Performance test (if lighthouse is available)
if command -v lighthouse &> /dev/null; then
echo "⚡ Testing performance..."
cd docs-site && npm run serve &
SERVER_PID=$!
sleep 5
lighthouse http://localhost:3001 --quiet --only-categories=performance
kill $SERVER_PID
fi
echo "✅ All tests completed!"
```
## Best Practices
### 1. Test Early and Often
- Test after every significant documentation change
- Include documentation testing in your regular development workflow
- Set up automated testing in CI/CD pipelines
### 2. Test Different Scenarios
- Test with different screen sizes and devices
- Test with JavaScript disabled
- Test with slow internet connections
### 3. Monitor Performance
- Keep an eye on build times
- Monitor page load speeds
- Check for large images or files that slow down the site
### 4. Validate Content Quality
- Use spell checkers and grammar tools
- Ensure code examples work and are current
- Verify that external links are still valid
By following this guide, you can ensure your documentation works perfectly before deploying to GitHub Pages, providing a better experience for your users and avoiding broken deployments.
```
--------------------------------------------------------------------------------
/MCP_PHASE2_IMPLEMENTATION.md:
--------------------------------------------------------------------------------
```markdown
# MCP Phase 2 Implementation: Roots Permission System
**Status:** ✅ Complete
**Implementation Date:** October 9, 2025
**Build Status:** ✅ Successful
**Test Status:** ✅ 127/127 tests passing
## Overview
Phase 2 implements the **Roots Permission System** for DocuMCP, adding user-granted file/folder access control following MCP best practices. This enhances security by restricting server operations to explicitly allowed directories and improves UX by enabling autonomous file discovery.
## Key Features Implemented
### 1. **Roots Capability Declaration**
- Added `roots.listChanged: true` to server capabilities
- Signals to MCP clients that the server supports roots management
- Enables clients to query allowed directories via `ListRoots` request
### 2. **CLI Argument Parsing**
- Added `--root` flag support for specifying allowed directories
- Supports multiple roots: `--root /path/one --root /path/two`
- Automatic `~` expansion for home directory paths
- Defaults to current working directory if no roots specified
### 3. **ListRoots Handler**
- Implements MCP `ListRootsRequest` protocol
- Returns all allowed roots as file:// URIs
- Provides friendly names using `path.basename()`
- Example response:
```json
{
"roots": [
{ "uri": "file:///Users/user/projects", "name": "projects" },
{ "uri": "file:///Users/user/workspace", "name": "workspace" }
]
}
```
### 4. **Permission Checker Utility**
- **Location:** `src/utils/permission-checker.ts`
- **Functions:**
- `isPathAllowed(requestedPath, allowedRoots)` - Validates path access
- `getPermissionDeniedMessage(requestedPath, allowedRoots)` - User-friendly error messages
- **Security:** Uses `path.relative()` to detect directory traversal attempts
- **Algorithm:** Resolves paths to absolute, checks if relative path doesn't start with `..`
### 5. **read_directory Tool**
- New tool for discovering files and directories within allowed roots
- Enables autonomous exploration without requiring full absolute paths from users
- Returns structured data:
```typescript
{
path: string,
files: string[],
directories: string[],
totalFiles: number,
totalDirectories: number
}
```
- Enforces permission checks before listing
### 6. **Permission Enforcement in File-Based Tools**
- Added permission checks to 5 critical tools:
- `analyze_repository`
- `setup_structure`
- `populate_diataxis_content`
- `validate_diataxis_content`
- `check_documentation_links`
- Returns structured `PERMISSION_DENIED` errors with resolution guidance
- Example error:
```json
{
"success": false,
"error": {
"code": "PERMISSION_DENIED",
"message": "Access denied: Path \"/etc/passwd\" is outside allowed roots. Allowed roots: /Users/user/project",
"resolution": "Request access to this directory by starting the server with --root argument, or use a path within allowed roots."
}
}
```
## Files Modified
### 1. `src/index.ts` (+120 lines)
**Changes:**
- Added default `path` import and permission checker imports (lines 17, 44-48)
- CLI argument parsing for `--root` flags (lines 69-84)
- Added roots capability to server (lines 101-103)
- Added `read_directory` tool definition (lines 706-717)
- Implemented `ListRoots` handler (lines 1061-1067)
- Implemented `read_directory` handler (lines 1874-1938)
- Added permission checks to 5 file-based tools (multiple sections)
### 2. `src/utils/permission-checker.ts` (NEW +49 lines)
**Functions:**
- `isPathAllowed()` - Core permission validation logic
- `getPermissionDeniedMessage()` - Standardized error messaging
- Comprehensive JSDoc documentation with examples
## Technical Implementation Details
### CLI Argument Parsing
```typescript
// Parse allowed roots from command line arguments
const allowedRoots: string[] = [];
process.argv.forEach((arg, index) => {
if (arg === "--root" && process.argv[index + 1]) {
const rootPath = process.argv[index + 1];
// Resolve to absolute path and expand ~ for home directory
const expandedPath = rootPath.startsWith("~")
? join(
process.env.HOME || process.env.USERPROFILE || "",
rootPath.slice(1),
)
: rootPath;
allowedRoots.push(path.resolve(expandedPath));
}
});
// If no roots specified, allow current working directory by default
if (allowedRoots.length === 0) {
allowedRoots.push(process.cwd());
}
```
### Permission Check Pattern
```typescript
// Check if path is allowed
const repoPath = (args as any)?.path;
if (repoPath && !isPathAllowed(repoPath, allowedRoots)) {
return formatMCPResponse({
success: false,
error: {
code: "PERMISSION_DENIED",
message: getPermissionDeniedMessage(repoPath, allowedRoots),
resolution:
"Request access to this directory by starting the server with --root argument, or use a path within allowed roots.",
},
metadata: {
toolVersion: packageJson.version,
executionTime: 0,
timestamp: new Date().toISOString(),
},
});
}
```
### Security Algorithm
The `isPathAllowed()` function uses `path.relative()` to detect directory traversal:
1. Resolve requested path to absolute path
2. For each allowed root:
- Resolve root to absolute path
- Calculate relative path from root to requested path
- If relative path doesn't start with `..` and isn't absolute, access is granted
3. Return `false` if no roots allow access
This prevents attacks like:
- `/project/../../../etc/passwd` - blocked (relative path starts with `..`)
- `/etc/passwd` when root is `/project` - blocked (not within root)
## Testing Results
### Build Status
✅ TypeScript compilation successful with no errors
### Test Suite
✅ **127/127 tests passing (100%)**
**Key Test Coverage:**
- Tool validation and error handling
- Memory system integration
- Knowledge graph operations
- Functional end-to-end workflows
- Integration tests
- Edge case handling
**No Regressions:**
- All existing tests continue to pass
- No breaking changes to tool APIs
- Backward compatible implementation
## Security Improvements
### Before Phase 2
- ❌ Server could access any file on the system
- ❌ No permission boundaries
- ❌ Users must provide full absolute paths
- ❌ No visibility into allowed directories
### After Phase 2
- ✅ Access restricted to explicitly allowed roots
- ✅ Directory traversal attacks prevented
- ✅ Users can use relative paths within roots
- ✅ Clients can query allowed directories via ListRoots
- ✅ Clear, actionable error messages when access denied
- ✅ Default to CWD for safe local development
## User Experience Improvements
### Discovery Without Full Paths
Users can now explore repositories without knowing exact file locations:
```
User: "Analyze my project"
Claude: Uses read_directory to discover project structure
Claude: Finds package.json, analyzes dependencies, generates docs
```
### Clear Error Messages
When access is denied, users receive helpful guidance:
```
Access denied: Path "/private/data" is outside allowed roots.
Allowed roots: /Users/user/projects
Resolution: Request access to this directory by starting the server
with --root argument, or use a path within allowed roots.
```
### Flexible Configuration
Server can be started with multiple allowed roots:
```bash
# Single root
npx documcp --root /Users/user/projects
# Multiple roots
npx documcp --root /Users/user/projects --root /Users/user/workspace
# Default (current directory)
npx documcp
```
## Usage Examples
### Starting Server with Roots
```bash
# Allow access to specific project
npx documcp --root /Users/user/my-project
# Allow access to multiple directories
npx documcp --root ~/projects --root ~/workspace
# Use home directory expansion
npx documcp --root ~/code
# Default to current directory
npx documcp
```
### read_directory Tool Usage
```typescript
// Discover files in allowed root
{
"name": "read_directory",
"arguments": {
"path": "/Users/user/projects/my-app"
}
}
// Response
{
"success": true,
"data": {
"path": "/Users/user/projects/my-app",
"files": ["package.json", "README.md", "tsconfig.json"],
"directories": ["src", "tests", "docs"],
"totalFiles": 3,
"totalDirectories": 3
}
}
```
### ListRoots Request
```typescript
// Request
{
"method": "roots/list"
}
// Response
{
"roots": [
{"uri": "file:///Users/user/projects", "name": "projects"}
]
}
```
## Alignment with MCP Best Practices
✅ **Roots Protocol Compliance**
- Implements `roots.listChanged` capability
- Provides `ListRoots` handler
- Uses standardized file:// URI format
✅ **Security First**
- Path validation using battle-tested algorithms
- Directory traversal prevention
- Principle of least privilege (explicit allow-list)
✅ **User-Centric Design**
- Clear error messages with actionable resolutions
- Flexible CLI configuration
- Safe defaults (CWD)
✅ **Autonomous Operation**
- `read_directory` enables file discovery
- No need for users to specify full paths
- Tools can explore within allowed roots
## Integration with Phase 1
Phase 2 builds on Phase 1's foundation:
**Phase 1 (Progress & Logging):**
- Added visibility into long-running operations
- Tools report progress at logical checkpoints
**Phase 2 (Roots & Permissions):**
- Adds security boundaries and permission checks
- Progress notifications can now include permission validation steps
- Example: "Validating path permissions..." → "Analyzing repository..."
**Combined Benefits:**
- Users see both progress AND permission enforcement
- Clear feedback when operations are blocked by permissions
- Transparent, secure, and user-friendly experience
## Performance Impact
✅ **Negligible Overhead**
- Permission checks: O(n) where n = number of allowed roots (typically 1-5)
- `path.resolve()` and `path.relative()` are highly optimized native operations
- No measurable impact on tool execution time
- All tests pass with no performance degradation
## Troubleshooting Guide
### Issue: "Access denied" errors
**Cause:** Requested path is outside allowed roots
**Solution:** Start server with `--root` flag for the desired directory
### Issue: ListRoots returns empty array
**Cause:** No roots specified and CWD not writable
**Solution:** Explicitly specify roots with `--root` flag
### Issue: ~ expansion not working
**Cause:** Server doesn't have HOME or USERPROFILE environment variable
**Solution:** Use absolute paths instead of ~ shorthand
## Next Steps (Phase 3)
Phase 3 will implement:
1. **HTTP Transport** - Remote server deployment with HTTP/HTTPS
2. **Transport Selection** - Environment-based stdio vs. HTTP choice
3. **Sampling Support** - LLM-powered content generation for creative tasks
4. **Configuration Management** - Environment variables for all settings
## Conclusion
Phase 2 successfully implements the Roots Permission System, bringing DocuMCP into full compliance with MCP security best practices. The implementation:
- ✅ Enforces strict access control without compromising usability
- ✅ Enables autonomous file discovery within allowed roots
- ✅ Provides clear, actionable feedback for permission violations
- ✅ Maintains 100% backward compatibility
- ✅ Passes all 127 tests with no regressions
- ✅ Adds minimal performance overhead
- ✅ Follows MCP protocol standards
**Total Changes:**
- 1 new file created (`permission-checker.ts`)
- 1 existing file modified (`index.ts`)
- 169 net lines added
- 6 new capabilities added (roots, ListRoots, read_directory, 5 tool permission checks)
**Quality Metrics:**
- Build: ✅ Successful
- Tests: ✅ 127/127 passing (100%)
- Regressions: ✅ None
- Performance: ✅ No measurable impact
- Security: ✅ Significantly improved
```
--------------------------------------------------------------------------------
/docs/reference/prompt-templates.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.963Z"
last_validated: "2025-12-09T19:41:38.593Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# Prompt Templates
DocuMCP provides a comprehensive set of prompt templates to help you interact effectively with the system. These templates are designed to get optimal results from DocuMCP's AI-powered documentation tools.
## Quick Reference
### Complete Workflow Templates
**Full Documentation Deployment:**
```
analyze my repository, recommend the best static site generator, set up Diataxis documentation structure, and deploy to GitHub Pages
```
**Documentation Audit:**
```
analyze my existing documentation for gaps, validate content accuracy, and provide recommendations for improvement
```
**Quick Setup:**
```
analyze my [LANGUAGE] project and set up documentation with the most suitable static site generator
```
## Repository Analysis Templates
### Basic Analysis
```
analyze my repository for documentation needs
```
### Specific Project Types
```
analyze my TypeScript library for API documentation requirements
analyze my Python package for comprehensive documentation needs
analyze my React application for user guide documentation
analyze my CLI tool for usage documentation
```
### Deep Analysis
```
perform deep analysis of my repository including dependency analysis, complexity assessment, and team collaboration patterns
```
### Focused Analysis
```
analyze my repository focusing on [SPECIFIC_AREA]
# Examples:
# - API documentation opportunities
# - user onboarding needs
# - developer experience gaps
# - deployment documentation requirements
```
## SSG Recommendation Templates
### Basic Recommendation
```
recommend the best static site generator for my project based on the analysis
```
### Preference-Based Recommendations
```
recommend a static site generator for my project with preferences for [ECOSYSTEM] and [PRIORITY]
# Ecosystem options: javascript, python, ruby, go, any
# Priority options: simplicity, features, performance
```
### Comparison Requests
```
compare static site generators for my [PROJECT_TYPE] with focus on [CRITERIA]
# Project types: library, application, tool, documentation
# Criteria: ease of use, customization, performance, community support
```
### Specific Requirements
```
recommend SSG for my project that supports:
- TypeScript integration
- API documentation generation
- Search functionality
- Custom theming
- Multi-language support
```
## Configuration Generation Templates
### Basic Configuration
```
generate [SSG_NAME] configuration for my project
# Examples:
# - generate Docusaurus configuration for my project
# - generate Hugo configuration for my project
# - generate MkDocs configuration for my project
```
### Detailed Configuration
```
generate comprehensive [SSG_NAME] configuration with:
- GitHub integration
- Custom domain setup
- Analytics integration
- SEO optimization
- Performance optimizations
```
### Production-Ready Setup
```
generate production-ready [SSG_NAME] configuration with security best practices and performance optimization
```
## Documentation Structure Templates
### Basic Structure
```
set up Diataxis documentation structure for my project
```
### SSG-Specific Structure
```
create [SSG_NAME] documentation structure following Diataxis principles with example content
```
### Content Population
```
set up documentation structure and populate it with project-specific content based on my code analysis
```
### Advanced Structure
```
create comprehensive documentation structure with:
- Diataxis organization
- Project-specific content
- Code examples from my repository
- API documentation templates
- Deployment guides
```
## Deployment Templates
### Basic GitHub Pages Deployment
```
deploy my documentation to GitHub Pages
```
### Complete Deployment Workflow
```
set up automated GitHub Pages deployment with:
- Build optimization
- Security best practices
- Performance monitoring
- Deployment verification
```
### Custom Domain Deployment
```
deploy to GitHub Pages with custom domain [DOMAIN_NAME] and SSL certificate
```
### Multi-Environment Deployment
```
set up documentation deployment with staging and production environments
```
## Content Management Templates
### Content Validation
```
validate all my documentation content for accuracy, broken links, and completeness
```
### Gap Analysis
```
analyze my documentation for missing content and provide recommendations for improvement
```
### Content Updates
```
update my existing documentation based on recent code changes and current best practices
```
### Quality Assurance
```
perform comprehensive quality check on my documentation including:
- Link validation
- Code example testing
- Content accuracy verification
- SEO optimization assessment
```
## Troubleshooting Templates
### General Troubleshooting
```
diagnose and fix issues with my documentation deployment
```
### Specific Problem Solving
```
troubleshoot [SPECIFIC_ISSUE] with my documentation setup
# Examples:
# - GitHub Pages deployment failures
# - build errors with my static site generator
# - broken links in my documentation
# - performance issues with my documentation site
```
### Verification and Testing
```
verify my documentation deployment is working correctly and identify any issues
```
## Memory and Learning Templates
### Memory Recall
```
show me insights from similar projects and successful documentation patterns
```
### Learning from History
```
based on previous analyses, what are the best practices for my type of project?
```
### Pattern Recognition
```
analyze patterns in my documentation workflow and suggest optimizations
```
## Advanced Workflow Templates
### Multi-Step Workflows
**Research and Planning:**
```
1. analyze my repository comprehensively
2. research best practices for my project type
3. recommend optimal documentation strategy
4. create implementation plan
```
**Implementation and Validation:**
```
1. set up recommended documentation structure
2. populate with project-specific content
3. validate all content and links
4. deploy to GitHub Pages
5. verify deployment success
```
**Maintenance and Optimization:**
```
1. audit existing documentation for gaps
2. update content based on code changes
3. optimize for performance and SEO
4. monitor deployment health
```
### Conditional Workflows
```
if my project is a [TYPE], then:
- focus on [SPECIFIC_DOCUMENTATION_NEEDS]
- use [RECOMMENDED_SSG]
- emphasize [CONTENT_PRIORITIES]
```
## Context-Aware Templates
### Project-Specific Context
```
for my [PROJECT_TYPE] written in [LANGUAGE] with [FRAMEWORK]:
- analyze documentation needs
- recommend appropriate tools
- create tailored content structure
```
### Team-Based Context
```
for a [TEAM_SIZE] team working on [PROJECT_DESCRIPTION]:
- set up collaborative documentation workflow
- implement review and approval processes
- create contribution guidelines
```
### Audience-Specific Context
```
create documentation targeting [AUDIENCE]:
- developers (API docs, technical guides)
- end users (tutorials, how-to guides)
- contributors (development setup, guidelines)
- administrators (deployment, configuration)
```
## Template Customization
### Variables and Placeholders
Use these placeholders in templates:
| Placeholder | Description | Examples |
| ---------------- | --------------------- | --------------------------------- |
| `[PROJECT_TYPE]` | Type of project | library, application, tool |
| `[LANGUAGE]` | Programming language | TypeScript, Python, Go |
| `[SSG_NAME]` | Static site generator | Docusaurus, Hugo, MkDocs |
| `[DOMAIN_NAME]` | Custom domain | docs.example.com |
| `[FRAMEWORK]` | Framework used | React, Vue, Django |
| `[TEAM_SIZE]` | Team size | small, medium, large |
| `[ECOSYSTEM]` | Package ecosystem | javascript, python, ruby |
| `[PRIORITY]` | Priority focus | simplicity, features, performance |
### Creating Custom Templates
```
create custom template for [SPECIFIC_USE_CASE]:
- define requirements
- specify desired outcomes
- include success criteria
- provide examples
```
## Best Practices for Prompting
### Effective Prompt Structure
1. **Be Specific:** Include relevant details about your project
2. **Set Context:** Mention your experience level and constraints
3. **Define Success:** Explain what a good outcome looks like
4. **Ask for Explanation:** Request reasoning behind recommendations
### Example of Well-Structured Prompt
```
I have a TypeScript library for data visualization with 50+ contributors.
I need comprehensive documentation that includes:
- API reference for all public methods
- Interactive examples with code samples
- Getting started guide for developers
- Contribution guidelines for the community
Please analyze my repository, recommend the best approach, and set up a
documentation system that can handle our scale and complexity.
```
### Common Pitfalls to Avoid
- **Too vague:** "help with documentation"
- **Missing context:** Not mentioning project type or requirements
- **No constraints:** Not specifying limitations or preferences
- **Single-step thinking:** Not considering the full workflow
## Integration with Development Workflow
### Git Hooks Integration
```
set up pre-commit hooks to:
- validate documentation changes
- check for broken links
- ensure content quality
- update generated content
```
### CI/CD Integration
```
create GitHub Actions workflow that:
- validates documentation on every PR
- deploys docs on main branch updates
- runs quality checks automatically
- notifies team of issues
```
### IDE Integration
```
configure development environment for:
- live documentation preview
- automated link checking
- content validation
- template generation
```
## Troubleshooting Prompts
### When Things Don't Work
**Analysis Issues:**
```
my repository analysis returned incomplete results, please retry with deep analysis and explain what might have caused the issue
```
**Recommendation Problems:**
```
the SSG recommendation doesn't match my needs because [REASON], please provide alternative recommendations with different priorities
```
**Deployment Failures:**
```
my GitHub Pages deployment failed with [ERROR_MESSAGE], please diagnose the issue and provide a fix
```
**Content Issues:**
```
my generated documentation has [PROBLEM], please update the content and ensure it meets [REQUIREMENTS]
```
For more troubleshooting help, see the [Troubleshooting Guide](../how-to/troubleshooting.md).
## Template Categories Summary
| Category | Purpose | Key Templates |
| ------------------- | ---------------------- | ---------------------------------- |
| **Analysis** | Understanding projects | Repository analysis, gap detection |
| **Recommendation** | Tool selection | SSG comparison, feature matching |
| **Configuration** | Setup and config | Production configs, optimization |
| **Structure** | Content organization | Diataxis setup, content population |
| **Deployment** | Going live | GitHub Pages, custom domains |
| **Validation** | Quality assurance | Link checking, content validation |
| **Troubleshooting** | Problem solving | Diagnosis, issue resolution |
| **Workflow** | Process automation | Multi-step procedures, CI/CD |
These templates provide a solid foundation for effective interaction with DocuMCP. Customize them based on your specific needs and project requirements.
```
--------------------------------------------------------------------------------
/tests/integration/kg-documentation-workflow.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Integration Tests for Knowledge Graph Documentation Workflow
* Tests end-to-end workflow from repository analysis to documentation tracking
*/
import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
import { promises as fs } from "fs";
import path from "path";
import { tmpdir } from "os";
import { analyzeRepository } from "../../src/tools/analyze-repository.js";
import {
initializeKnowledgeGraph,
getKnowledgeGraph,
saveKnowledgeGraph,
} from "../../src/memory/kg-integration.js";
describe("KG Documentation Workflow Integration", () => {
let testDir: string;
beforeEach(async () => {
testDir = path.join(tmpdir(), `documcp-integration-${Date.now()}`);
await fs.mkdir(testDir, { recursive: true });
// Initialize KG with test storage
const storageDir = path.join(testDir, ".documcp/memory");
await initializeKnowledgeGraph(storageDir);
});
afterEach(async () => {
try {
await fs.rm(testDir, { recursive: true, force: true });
} catch {
// Ignore cleanup errors
}
});
it("should complete full workflow: analyze → create entities → link relationships", async () => {
// Setup: Create a test repository structure
const srcDir = path.join(testDir, "src");
const docsDir = path.join(testDir, "docs");
await fs.mkdir(srcDir, { recursive: true });
await fs.mkdir(docsDir, { recursive: true });
// Create source code
await fs.writeFile(
path.join(srcDir, "auth.ts"),
`
export class AuthService {
async login(username: string, password: string) {
return { token: "abc123" };
}
async logout(token: string) {
return true;
}
}
export function validateToken(token: string) {
return token.length > 0;
}
`,
"utf-8",
);
// Create documentation
await fs.writeFile(
path.join(docsDir, "api.md"),
`
# Authentication API
## Login
Use the \`login()\` method from \`AuthService\` class in \`src/auth.ts\`:
\`\`\`typescript
const auth = new AuthService();
const result = await auth.login(username, password);
\`\`\`
## Logout
Call \`logout()\` with the authentication token:
\`\`\`typescript
await auth.logout(token);
\`\`\`
## Token Validation
Use \`validateToken()\` function to validate tokens.
`,
"utf-8",
);
await fs.writeFile(
path.join(testDir, "README.md"),
"# Test Project",
"utf-8",
);
await fs.writeFile(
path.join(testDir, "package.json"),
JSON.stringify({ name: "test-project", version: "1.0.0" }),
"utf-8",
);
// Act: Run repository analysis
const analysisResult = await analyzeRepository({
path: testDir,
depth: "standard",
});
// Assert: Analysis completed (may have errors due to test environment)
expect(analysisResult.content).toBeDefined();
expect(analysisResult.content.length).toBeGreaterThan(0);
// If analysis succeeded, verify structure
if (!analysisResult.isError) {
const analysis = JSON.parse(analysisResult.content[0].text);
if (analysis.success) {
expect(analysis.data.structure.hasDocs).toBe(true);
}
}
// Wait for KG operations to complete
await new Promise((resolve) => setTimeout(resolve, 100));
// Verify: Check knowledge graph entities
const kg = await getKnowledgeGraph();
const allNodes = await kg.getAllNodes();
const allEdges = await kg.getAllEdges();
// Should have project, code files, and documentation sections
const projectNodes = allNodes.filter((n) => n.type === "project");
const codeFileNodes = allNodes.filter((n) => n.type === "code_file");
const docSectionNodes = allNodes.filter(
(n) => n.type === "documentation_section",
);
expect(projectNodes.length).toBeGreaterThan(0);
expect(codeFileNodes.length).toBeGreaterThan(0);
expect(docSectionNodes.length).toBeGreaterThan(0);
// Verify code file details
const authFile = codeFileNodes.find((n) =>
n.properties.path.includes("auth.ts"),
);
expect(authFile).toBeDefined();
expect(authFile?.properties.language).toBe("typescript");
expect(authFile?.properties.classes).toContain("AuthService");
expect(authFile?.properties.functions).toContain("validateToken");
// Verify documentation sections
const apiDoc = docSectionNodes.find((n) =>
n.properties.filePath.includes("api.md"),
);
expect(apiDoc).toBeDefined();
expect(apiDoc?.properties.hasCodeExamples).toBe(true);
expect(apiDoc?.properties.referencedFunctions.length).toBeGreaterThan(0);
// Verify relationships
const referencesEdges = allEdges.filter((e) => e.type === "references");
const documentsEdges = allEdges.filter((e) => e.type === "documents");
expect(referencesEdges.length).toBeGreaterThan(0);
expect(documentsEdges.length).toBeGreaterThan(0);
// Verify specific relationship: api.md references auth.ts
const apiToAuthEdge = referencesEdges.find(
(e) => e.source === apiDoc?.id && e.target === authFile?.id,
);
expect(apiToAuthEdge).toBeDefined();
expect(apiToAuthEdge?.properties.referenceType).toBe("api-reference");
});
it("should detect outdated documentation when code changes", async () => {
// Setup: Create initial code and docs
const srcDir = path.join(testDir, "src");
const docsDir = path.join(testDir, "docs");
await fs.mkdir(srcDir, { recursive: true });
await fs.mkdir(docsDir, { recursive: true });
await fs.writeFile(
path.join(srcDir, "user.ts"),
"export function getUser() {}",
"utf-8",
);
await fs.writeFile(
path.join(docsDir, "guide.md"),
"Call `getUser()` from `src/user.ts`",
"utf-8",
);
await fs.writeFile(path.join(testDir, "README.md"), "# Test", "utf-8");
await fs.writeFile(path.join(testDir, "package.json"), "{}", "utf-8");
// First analysis
await analyzeRepository({ path: testDir, depth: "standard" });
await new Promise((resolve) => setTimeout(resolve, 100));
// Simulate code change
await new Promise((resolve) => setTimeout(resolve, 100)); // Ensure different timestamp
await fs.writeFile(
path.join(srcDir, "user.ts"),
"export function getUser(id: string) {} // CHANGED",
"utf-8",
);
// Second analysis
await analyzeRepository({ path: testDir, depth: "standard" });
await new Promise((resolve) => setTimeout(resolve, 100));
// Verify: Check that system handled multiple analyses
// In a real scenario, outdated_for edges would be created
// For this test, just verify no crashes occurred
const kg = await getKnowledgeGraph();
const allNodes = await kg.getAllNodes();
// Should have created some nodes from both analyses
expect(allNodes.length).toBeGreaterThan(0);
});
it("should handle projects with no documentation gracefully", async () => {
// Setup: Code-only project
const srcDir = path.join(testDir, "src");
await fs.mkdir(srcDir, { recursive: true });
await fs.writeFile(
path.join(srcDir, "index.ts"),
"export function main() {}",
"utf-8",
);
await fs.writeFile(path.join(testDir, "package.json"), "{}", "utf-8");
// Act
await analyzeRepository({ path: testDir, depth: "standard" });
await new Promise((resolve) => setTimeout(resolve, 100));
// Verify: Should still create code entities, just no doc entities
const kg = await getKnowledgeGraph();
const allNodes = await kg.getAllNodes();
const codeFileNodes = allNodes.filter((n) => n.type === "code_file");
const docSectionNodes = allNodes.filter(
(n) => n.type === "documentation_section",
);
expect(codeFileNodes.length).toBeGreaterThan(0);
expect(docSectionNodes.length).toBe(0);
});
it("should handle multi-file projects correctly", async () => {
// Setup: Multiple source files
const srcDir = path.join(testDir, "src");
await fs.mkdir(path.join(srcDir, "auth"), { recursive: true });
await fs.mkdir(path.join(srcDir, "db"), { recursive: true });
await fs.writeFile(
path.join(srcDir, "auth", "login.ts"),
"export function login() {}",
"utf-8",
);
await fs.writeFile(
path.join(srcDir, "auth", "logout.ts"),
"export function logout() {}",
"utf-8",
);
await fs.writeFile(
path.join(srcDir, "db", "query.ts"),
"export function query() {}",
"utf-8",
);
await fs.writeFile(path.join(testDir, "package.json"), "{}", "utf-8");
// Act
await analyzeRepository({ path: testDir, depth: "standard" });
await new Promise((resolve) => setTimeout(resolve, 100));
// Verify
const kg = await getKnowledgeGraph();
const codeFileNodes = (await kg.getAllNodes()).filter(
(n) => n.type === "code_file",
);
expect(codeFileNodes.length).toBe(3);
const paths = codeFileNodes.map((n) => n.properties.path);
expect(paths).toContain("src/auth/login.ts");
expect(paths).toContain("src/auth/logout.ts");
expect(paths).toContain("src/db/query.ts");
});
it("should persist knowledge graph to storage", async () => {
// Setup
const srcDir = path.join(testDir, "src");
await fs.mkdir(srcDir, { recursive: true });
await fs.writeFile(
path.join(srcDir, "test.ts"),
"export function test() {}",
"utf-8",
);
await fs.writeFile(path.join(testDir, "package.json"), "{}", "utf-8");
// Act
await analyzeRepository({ path: testDir, depth: "standard" });
await new Promise((resolve) => setTimeout(resolve, 100));
// Save KG
await saveKnowledgeGraph();
// Verify storage files exist
const storageDir = path.join(testDir, ".documcp/memory");
const entitiesFile = path.join(
storageDir,
"knowledge-graph-entities.jsonl",
);
const relationshipsFile = path.join(
storageDir,
"knowledge-graph-relationships.jsonl",
);
const entitiesExist = await fs
.access(entitiesFile)
.then(() => true)
.catch(() => false);
const relationshipsExist = await fs
.access(relationshipsFile)
.then(() => true)
.catch(() => false);
expect(entitiesExist).toBe(true);
expect(relationshipsExist).toBe(true);
// Verify content
const entitiesContent = await fs.readFile(entitiesFile, "utf-8");
expect(entitiesContent).toContain("code_file");
});
it("should calculate coverage metrics for documentation", async () => {
// Setup: 3 functions, docs covering 2 of them
const srcDir = path.join(testDir, "src");
const docsDir = path.join(testDir, "docs");
await fs.mkdir(srcDir, { recursive: true });
await fs.mkdir(docsDir, { recursive: true });
await fs.writeFile(
path.join(srcDir, "api.ts"),
`
export function create() {}
export function read() {}
export function update() {} // Not documented
`,
"utf-8",
);
await fs.writeFile(
path.join(docsDir, "api.md"),
`
# API Reference
- \`create()\`: Creates a resource
- \`read()\`: Reads a resource
`,
"utf-8",
);
await fs.writeFile(path.join(testDir, "README.md"), "# Test", "utf-8");
await fs.writeFile(path.join(testDir, "package.json"), "{}", "utf-8");
// Act
await analyzeRepository({ path: testDir, depth: "standard" });
await new Promise((resolve) => setTimeout(resolve, 100));
// Verify coverage
const kg = await getKnowledgeGraph();
const documentsEdges = (await kg.getAllEdges()).filter(
(e) => e.type === "documents",
);
expect(documentsEdges.length).toBeGreaterThan(0);
const coverage = documentsEdges[0].properties.coverage;
expect(["partial", "complete", "comprehensive"]).toContain(coverage);
// 2/3 = 66% should be "complete"
expect(coverage).toBe("complete");
});
});
```
--------------------------------------------------------------------------------
/src/utils/artifact-detector.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Agent Artifact Detection System
*
* Detects, classifies, and provides recommendations for artifacts
* generated by AI coding agents during workflows.
*/
import { promises as fs } from "fs";
import path from "path";
import { globby } from "globby";
export interface AgentArtifact {
path: string;
type:
| "file"
| "directory"
| "inline-comment"
| "block-comment"
| "code-block";
category: "planning" | "debug" | "temporary" | "state" | "documentation";
confidence: number; // 0-1 how sure we are this is agent-generated
recommendation: "delete" | "review" | "keep" | "archive";
context?: string; // surrounding content for review
detectedBy: string; // which pattern matched
}
export interface ArtifactScanResult {
scannedFiles: number;
artifacts: AgentArtifact[];
summary: {
totalArtifacts: number;
byCategory: Record<string, number>;
byRecommendation: Record<string, number>;
};
}
export interface ArtifactCleanupConfig {
// Detection settings
patterns: {
files: string[]; // glob patterns for artifact files
directories: string[]; // agent state directories
inlineMarkers: string[]; // comment markers to detect
blockPatterns: RegExp[]; // multi-line patterns
};
// Behavior settings
autoDeleteThreshold: number; // confidence threshold for auto-delete
preserveGitIgnored: boolean; // skip .gitignored artifacts
archiveBeforeDelete: boolean; // safety backup
// Exclusions
excludePaths: string[];
excludePatterns: string[];
}
/**
* Default configuration for artifact detection
*/
export const DEFAULT_CONFIG: ArtifactCleanupConfig = {
patterns: {
files: [
"TODO.md",
"TODOS.md",
"PLAN.md",
"PLANNING.md",
"NOTES.md",
"SCRATCH.md",
"AGENT-*.md",
"*.agent.md",
],
directories: [
".claude",
".cursor",
".aider",
".copilot",
".codeium",
".agent-workspace",
],
inlineMarkers: [
"// @agent-temp",
"// TODO(agent):",
"# AGENT-NOTE:",
"<!-- agent:ephemeral -->",
"// FIXME(claude):",
"// FIXME(cursor):",
"// FIXME(copilot):",
"// TODO(claude):",
"// TODO(cursor):",
"// TODO(copilot):",
],
blockPatterns: [
/\/\*\s*AGENT[-_](?:START|BEGIN)[\s\S]*?AGENT[-_](?:END|FINISH)\s*\*\//gi,
/<!--\s*agent:ephemeral\s*-->[\s\S]*?<!--\s*\/agent:ephemeral\s*-->/gi,
/\/\/\s*@agent-temp-start[\s\S]*?\/\/\s*@agent-temp-end/gi,
],
},
autoDeleteThreshold: 0.9,
preserveGitIgnored: true,
archiveBeforeDelete: true,
excludePaths: ["node_modules", ".git", "dist", "build", ".documcp"],
excludePatterns: ["*.lock", "package-lock.json", "yarn.lock"],
};
/**
* Main Artifact Detector class
*/
export class ArtifactDetector {
private config: ArtifactCleanupConfig;
private projectPath: string;
constructor(projectPath: string, config?: Partial<ArtifactCleanupConfig>) {
this.projectPath = projectPath;
this.config = {
...DEFAULT_CONFIG,
...config,
patterns: {
...DEFAULT_CONFIG.patterns,
...(config?.patterns || {}),
},
};
}
/**
* Scan for agent artifacts in the project
*/
async scan(): Promise<ArtifactScanResult> {
const artifacts: AgentArtifact[] = [];
let scannedFiles = 0;
// Detect file-based artifacts
const fileArtifacts = await this.detectFileArtifacts();
artifacts.push(...fileArtifacts);
// Detect directory artifacts
const dirArtifacts = await this.detectDirectoryArtifacts();
artifacts.push(...dirArtifacts);
// Detect inline and block artifacts in files
const inlineArtifacts = await this.detectInlineArtifacts();
artifacts.push(...inlineArtifacts.artifacts);
scannedFiles = inlineArtifacts.scannedFiles;
// Generate summary
const summary = this.generateSummary(artifacts);
return {
scannedFiles,
artifacts,
summary,
};
}
/**
* Detect file-based artifacts (e.g., TODO.md, PLAN.md)
*/
private async detectFileArtifacts(): Promise<AgentArtifact[]> {
const artifacts: AgentArtifact[] = [];
// Build glob patterns with exclusions
const patterns = this.config.patterns.files.map((pattern) =>
path.join(this.projectPath, "**", pattern),
);
try {
const files = await globby(patterns, {
ignore: this.config.excludePaths.map((p) =>
path.join(this.projectPath, p, "**"),
),
absolute: true,
onlyFiles: true,
});
for (const filePath of files) {
const relativePath = path.relative(this.projectPath, filePath);
const fileName = path.basename(filePath);
// Determine category and confidence based on file name
const { category, confidence, detectedBy } =
this.categorizeFile(fileName);
artifacts.push({
path: relativePath,
type: "file",
category,
confidence,
recommendation: this.getRecommendation(confidence, category),
detectedBy,
});
}
} catch (error) {
// Silently handle globby errors (e.g., permission denied)
console.error(`Error scanning files: ${error}`);
}
return artifacts;
}
/**
* Detect directory-based artifacts (e.g., .claude/, .cursor/)
*/
private async detectDirectoryArtifacts(): Promise<AgentArtifact[]> {
const artifacts: AgentArtifact[] = [];
for (const dirName of this.config.patterns.directories) {
const dirPath = path.join(this.projectPath, dirName);
try {
const stats = await fs.stat(dirPath);
if (stats.isDirectory()) {
artifacts.push({
path: dirName,
type: "directory",
category: "state",
confidence: 0.95,
recommendation: "archive",
detectedBy: `Directory pattern: ${dirName}`,
});
}
} catch {
// Directory doesn't exist, skip
}
}
return artifacts;
}
/**
* Detect inline comments and block artifacts in code files
*/
private async detectInlineArtifacts(): Promise<{
artifacts: AgentArtifact[];
scannedFiles: number;
}> {
const artifacts: AgentArtifact[] = [];
let scannedFiles = 0;
// Scan common code file types
const patterns = [
"**/*.ts",
"**/*.js",
"**/*.tsx",
"**/*.jsx",
"**/*.py",
"**/*.rb",
"**/*.go",
"**/*.rs",
"**/*.java",
"**/*.md",
"**/*.html",
"**/*.css",
"**/*.scss",
].map((p) => path.join(this.projectPath, p));
try {
const files = await globby(patterns, {
ignore: this.config.excludePaths.map((p) =>
path.join(this.projectPath, p, "**"),
),
absolute: true,
onlyFiles: true,
});
for (const filePath of files) {
scannedFiles++;
const content = await fs.readFile(filePath, "utf-8");
const relativePath = path.relative(this.projectPath, filePath);
// Check for inline markers
const inlineArtifacts = this.detectInlineMarkers(content, relativePath);
artifacts.push(...inlineArtifacts);
// Check for block patterns
const blockArtifacts = this.detectBlockPatterns(content, relativePath);
artifacts.push(...blockArtifacts);
}
} catch (error) {
console.error(`Error scanning inline artifacts: ${error}`);
}
return { artifacts, scannedFiles };
}
/**
* Detect inline comment markers
*/
private detectInlineMarkers(
content: string,
filePath: string,
): AgentArtifact[] {
const artifacts: AgentArtifact[] = [];
const lines = content.split("\n");
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
for (const marker of this.config.patterns.inlineMarkers) {
if (line.includes(marker)) {
// Get context (2 lines before and after)
const contextStart = Math.max(0, i - 2);
const contextEnd = Math.min(lines.length, i + 3);
const context = lines.slice(contextStart, contextEnd).join("\n");
artifacts.push({
path: `${filePath}:${i + 1}`,
type: "inline-comment",
category: this.categorizeMarker(marker),
confidence: 0.85,
recommendation: "review",
context,
detectedBy: `Inline marker: ${marker}`,
});
}
}
}
return artifacts;
}
/**
* Detect block patterns
*/
private detectBlockPatterns(
content: string,
filePath: string,
): AgentArtifact[] {
const artifacts: AgentArtifact[] = [];
for (const pattern of this.config.patterns.blockPatterns) {
const matches = content.matchAll(pattern);
for (const match of matches) {
if (match[0]) {
// Get first 200 chars as context
const context = match[0].substring(0, 200);
artifacts.push({
path: filePath,
type: "block-comment",
category: "temporary",
confidence: 0.9,
recommendation: "delete",
context,
detectedBy: `Block pattern: ${pattern.source.substring(0, 50)}...`,
});
}
}
}
return artifacts;
}
/**
* Categorize a file based on its name
*/
private categorizeFile(fileName: string): {
category: AgentArtifact["category"];
confidence: number;
detectedBy: string;
} {
const upperName = fileName.toUpperCase();
if (
upperName.includes("TODO") ||
upperName.includes("PLAN") ||
upperName.includes("SCRATCH")
) {
return {
category: "planning",
confidence: 0.95,
detectedBy: `File name pattern: ${fileName}`,
};
}
if (upperName.includes("NOTES") || upperName.includes("AGENT")) {
return {
category: "documentation",
confidence: 0.9,
detectedBy: `File name pattern: ${fileName}`,
};
}
return {
category: "temporary",
confidence: 0.8,
detectedBy: `File name pattern: ${fileName}`,
};
}
/**
* Categorize a marker
*/
private categorizeMarker(marker: string): AgentArtifact["category"] {
if (marker.includes("TODO") || marker.includes("FIXME")) {
return "planning";
}
if (marker.includes("temp") || marker.includes("ephemeral")) {
return "temporary";
}
if (marker.includes("NOTE")) {
return "documentation";
}
return "debug";
}
/**
* Get recommendation based on confidence and category
*/
private getRecommendation(
confidence: number,
category: AgentArtifact["category"],
): AgentArtifact["recommendation"] {
if (confidence >= this.config.autoDeleteThreshold) {
if (category === "temporary" || category === "debug") {
return "delete";
}
return "archive";
}
if (confidence >= 0.7) {
return "review";
}
return "keep";
}
/**
* Generate summary statistics
*/
private generateSummary(
artifacts: AgentArtifact[],
): ArtifactScanResult["summary"] {
const byCategory: Record<string, number> = {};
const byRecommendation: Record<string, number> = {};
for (const artifact of artifacts) {
byCategory[artifact.category] = (byCategory[artifact.category] || 0) + 1;
byRecommendation[artifact.recommendation] =
(byRecommendation[artifact.recommendation] || 0) + 1;
}
return {
totalArtifacts: artifacts.length,
byCategory,
byRecommendation,
};
}
}
/**
* Detect agent artifacts in a project
*/
export async function detectArtifacts(
projectPath: string,
config?: Partial<ArtifactCleanupConfig>,
): Promise<ArtifactScanResult> {
const detector = new ArtifactDetector(projectPath, config);
return detector.scan();
}
```
--------------------------------------------------------------------------------
/tests/memory/knowledge-graph.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Basic unit tests for Knowledge Graph System
* Tests basic instantiation and core functionality
* Part of Issue #54 - Core Memory System Unit Tests
*/
import { promises as fs } from "fs";
import path from "path";
import os from "os";
import { MemoryManager } from "../../src/memory/manager.js";
import {
KnowledgeGraph,
GraphNode,
GraphEdge,
} from "../../src/memory/knowledge-graph.js";
describe("KnowledgeGraph", () => {
let tempDir: string;
let memoryManager: MemoryManager;
let graph: KnowledgeGraph;
beforeEach(async () => {
// Create unique temp directory for each test
tempDir = path.join(
os.tmpdir(),
`memory-graph-test-${Date.now()}-${Math.random()
.toString(36)
.substr(2, 9)}`,
);
await fs.mkdir(tempDir, { recursive: true });
// Create memory manager for knowledge graph
memoryManager = new MemoryManager(tempDir);
await memoryManager.initialize();
graph = new KnowledgeGraph(memoryManager);
await graph.initialize();
});
afterEach(async () => {
// Cleanup temp directory
try {
await fs.rm(tempDir, { recursive: true, force: true });
} catch (error) {
// Ignore cleanup errors
}
});
describe("Basic Graph Operations", () => {
test("should create knowledge graph instance", () => {
expect(graph).toBeDefined();
expect(graph).toBeInstanceOf(KnowledgeGraph);
});
test("should add nodes to the graph", () => {
const projectNode: Omit<GraphNode, "lastUpdated"> = {
id: "project:test-project",
type: "project",
label: "Test Project",
properties: {
language: "typescript",
framework: "react",
},
weight: 1.0,
};
const addedNode = graph.addNode(projectNode);
expect(addedNode).toBeDefined();
expect(addedNode.id).toBe("project:test-project");
expect(addedNode.type).toBe("project");
expect(addedNode.lastUpdated).toBeDefined();
});
test("should add edges to the graph", () => {
// First add nodes
const projectNode = graph.addNode({
id: "project:web-app",
type: "project",
label: "Web App",
properties: { language: "typescript" },
weight: 1.0,
});
const techNode = graph.addNode({
id: "tech:react",
type: "technology",
label: "React",
properties: { category: "framework" },
weight: 1.0,
});
// Add edge
const edge: Omit<GraphEdge, "id" | "lastUpdated"> = {
source: projectNode.id,
target: techNode.id,
type: "uses",
weight: 1.0,
confidence: 0.9,
properties: { importance: "high" },
};
const addedEdge = graph.addEdge(edge);
expect(addedEdge).toBeDefined();
expect(addedEdge.source).toBe(projectNode.id);
expect(addedEdge.target).toBe(techNode.id);
expect(addedEdge.id).toBeDefined();
});
test("should get all nodes", async () => {
// Add some nodes
graph.addNode({
id: "project:test1",
type: "project",
label: "Test 1",
properties: {},
weight: 1.0,
});
graph.addNode({
id: "tech:vue",
type: "technology",
label: "Vue",
properties: {},
weight: 1.0,
});
const nodes = await graph.getAllNodes();
expect(Array.isArray(nodes)).toBe(true);
expect(nodes.length).toBe(2);
});
test("should get all edges", async () => {
// Add nodes and edges
const node1 = graph.addNode({
id: "project:test2",
type: "project",
label: "Test 2",
properties: {},
weight: 1.0,
});
const node2 = graph.addNode({
id: "tech:angular",
type: "technology",
label: "Angular",
properties: {},
weight: 1.0,
});
graph.addEdge({
source: node1.id,
target: node2.id,
type: "uses",
weight: 1.0,
confidence: 0.8,
properties: {},
});
const edges = await graph.getAllEdges();
expect(Array.isArray(edges)).toBe(true);
expect(edges.length).toBe(1);
});
});
describe("Graph Queries", () => {
test("should query nodes by type", () => {
// Add multiple nodes of different types
graph.addNode({
id: "project:project-a",
type: "project",
label: "Project A",
properties: {},
weight: 1.0,
});
graph.addNode({
id: "project:project-b",
type: "project",
label: "Project B",
properties: {},
weight: 1.0,
});
graph.addNode({
id: "tech:vue",
type: "technology",
label: "Vue",
properties: { category: "framework" },
weight: 1.0,
});
const results = graph.query({
nodeTypes: ["project"],
});
expect(results).toBeDefined();
expect(Array.isArray(results.nodes)).toBe(true);
expect(results.nodes.length).toBe(2);
expect(results.nodes.every((node) => node.type === "project")).toBe(true);
});
test("should find connections for a node", async () => {
// Add nodes and create connections
const projectNode = graph.addNode({
id: "project:connected-test",
type: "project",
label: "Connected Test",
properties: {},
weight: 1.0,
});
const techNode = graph.addNode({
id: "tech:express",
type: "technology",
label: "Express",
properties: {},
weight: 1.0,
});
graph.addEdge({
source: projectNode.id,
target: techNode.id,
type: "uses",
weight: 1.0,
confidence: 0.9,
properties: {},
});
const connections = await graph.getConnections(projectNode.id);
expect(Array.isArray(connections)).toBe(true);
expect(connections.length).toBe(1);
expect(connections[0]).toBe(techNode.id);
});
test("should find paths between nodes", () => {
// Add nodes and create a path
const projectNode = graph.addNode({
id: "project:path-test",
type: "project",
label: "Path Test Project",
properties: {},
weight: 1.0,
});
const techNode = graph.addNode({
id: "tech:nodejs",
type: "technology",
label: "Node.js",
properties: {},
weight: 1.0,
});
graph.addEdge({
source: projectNode.id,
target: techNode.id,
type: "uses",
weight: 1.0,
confidence: 0.9,
properties: {},
});
const path = graph.findPath(projectNode.id, techNode.id);
expect(path).toBeDefined();
expect(path?.nodes.length).toBe(2);
expect(path?.edges.length).toBe(1);
});
});
describe("Graph Analysis", () => {
test("should build from memory entries", async () => {
// Add some test memory entries first
await memoryManager.remember(
"analysis",
{
language: { primary: "python" },
framework: { name: "django" },
},
{
projectId: "analysis-project",
},
);
await memoryManager.remember(
"recommendation",
{
recommended: "mkdocs",
confidence: 0.9,
},
{
projectId: "analysis-project",
},
);
// Build graph from memories
await graph.buildFromMemories();
const nodes = await graph.getAllNodes();
// The buildFromMemories method might be implemented differently
// Just verify it doesn't throw and returns an array
expect(Array.isArray(nodes)).toBe(true);
// The graph might start empty, which is okay for this basic test
if (nodes.length > 0) {
// Optionally check node types if any were created
const nodeTypes = [...new Set(nodes.map((n) => n.type))];
expect(nodeTypes.length).toBeGreaterThan(0);
}
});
test("should generate graph-based recommendations", async () => {
// Add some memory data first
await memoryManager.remember(
"analysis",
{
language: { primary: "javascript" },
framework: { name: "react" },
},
{
projectId: "rec-test-project",
},
);
await graph.buildFromMemories();
const projectFeatures = {
language: "javascript",
framework: "react",
};
const recommendations = await graph.getGraphBasedRecommendation(
projectFeatures,
["docusaurus", "gatsby"],
);
expect(Array.isArray(recommendations)).toBe(true);
// Even if no recommendations found, should return empty array
});
test("should provide graph statistics", async () => {
// Add some nodes
graph.addNode({
id: "project:stats-test",
type: "project",
label: "Stats Test",
properties: {},
weight: 1.0,
});
graph.addNode({
id: "tech:webpack",
type: "technology",
label: "Webpack",
properties: {},
weight: 1.0,
});
const stats = await graph.getStatistics();
expect(stats).toBeDefined();
expect(typeof stats.nodeCount).toBe("number");
expect(typeof stats.edgeCount).toBe("number");
expect(typeof stats.nodesByType).toBe("object");
expect(typeof stats.averageConnectivity).toBe("number");
expect(Array.isArray(stats.mostConnectedNodes)).toBe(true);
});
});
describe("Error Handling", () => {
test("should handle removing non-existent nodes", async () => {
const removed = await graph.removeNode("non-existent-node");
expect(removed).toBe(false);
});
test("should handle concurrent graph operations", () => {
// Create multiple nodes concurrently
const nodes = Array.from({ length: 10 }, (_, i) =>
graph.addNode({
id: `project:concurrent-${i}`,
type: "project",
label: `Concurrent Project ${i}`,
properties: { index: i },
weight: 1.0,
}),
);
expect(nodes).toHaveLength(10);
expect(nodes.every((node) => typeof node.id === "string")).toBe(true);
});
test("should handle invalid query parameters", () => {
const results = graph.query({
nodeTypes: ["non-existent-type"],
});
expect(results).toBeDefined();
expect(Array.isArray(results.nodes)).toBe(true);
expect(results.nodes.length).toBe(0);
});
test("should handle empty graph operations", async () => {
// Test operations on empty graph
const path = graph.findPath("non-existent-1", "non-existent-2");
expect(path).toBeNull();
const connections = await graph.getConnections("non-existent-node");
expect(Array.isArray(connections)).toBe(true);
expect(connections.length).toBe(0);
});
});
describe("Persistence and Memory Integration", () => {
test("should save and load from memory", async () => {
// Add some data to the graph
graph.addNode({
id: "project:persistence-test",
type: "project",
label: "Persistence Test",
properties: {},
weight: 1.0,
});
// Save to memory
await graph.saveToMemory();
// Create new graph and load
const newGraph = new KnowledgeGraph(memoryManager);
await newGraph.loadFromMemory();
const nodes = await newGraph.getAllNodes();
expect(nodes.length).toBeGreaterThanOrEqual(0);
});
test("should handle empty graph statistics", async () => {
const stats = await graph.getStatistics();
expect(stats).toBeDefined();
expect(typeof stats.nodeCount).toBe("number");
expect(typeof stats.edgeCount).toBe("number");
expect(stats.nodeCount).toBe(0); // Empty graph initially
expect(stats.edgeCount).toBe(0);
});
});
});
```
--------------------------------------------------------------------------------
/docs/how-to/github-pages-deployment.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.951Z"
last_validated: "2025-12-09T19:41:38.582Z"
auto_updated: false
update_frequency: monthly
validated_against_commit: 306567b32114502c606244ad6c2930360bcd4201
---
# How to Deploy to GitHub Pages
This guide shows you how to deploy your documentation to GitHub Pages using DocuMCP's automated workflows. DocuMCP uses a dual-static-site-generator approach for optimal deployment.
## Architecture Overview
DocuMCP employs a **dual SSG strategy**:
- **Docusaurus**: Primary documentation system for development and rich content
- **Jekyll**: GitHub Pages deployment for reliable hosting
- **Docker**: Alternative testing and deployment method
## Quick Deployment
For immediate deployment:
```bash
# Prompt DocuMCP:
"deploy my documentation to GitHub Pages"
```
## Prerequisites
- Repository with documentation content
- GitHub account with repository access
- GitHub Pages enabled in repository settings
- Node.js 20.0.0+ for Docusaurus development
## Deployment Methods
### Method 1: Automated with DocuMCP (Recommended)
Use DocuMCP's intelligent deployment:
```bash
# Complete workflow:
"analyze my repository, recommend SSG, and deploy to GitHub Pages"
```
This will:
1. Analyze your project structure
2. Set up Docusaurus for development
3. Configure Jekyll for GitHub Pages deployment
4. Create GitHub Actions workflow
5. Deploy to Pages
### Method 2: Current DocuMCP Setup
DocuMCP currently uses the following deployment workflow:
#### GitHub Actions Workflow
```yaml
name: Deploy Jekyll to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.1"
bundler-cache: true
- name: Build with Jekyll
run: bundle exec jekyll build
env:
JEKYLL_ENV: production
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
path: "./_site"
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
permissions:
contents: read
pages: write
id-token: write
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
```
#### Development vs Production
- **Development**: Use Docusaurus (`cd docs && npm start`)
- **Production**: Jekyll builds and deploys to GitHub Pages
- **Testing**: Use Docker (`docker-compose -f docker-compose.docs.yml up`)
### Method 3: Manual Configuration
If you prefer manual setup:
#### Step 1: Choose Your SSG
```bash
# Get recommendation first:
"recommend static site generator for my project"
```
#### Step 2: Generate Config
```bash
# For example, with Hugo:
"generate Hugo configuration for GitHub Pages deployment"
```
#### Step 3: Deploy
```bash
"set up GitHub Pages deployment workflow for Hugo"
```
## GitHub Actions Workflow
DocuMCP generates optimized workflows for each SSG:
### Docusaurus Workflow
```yaml
name: Deploy Docusaurus
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
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
```
### Hugo Workflow
```yaml
name: Deploy Hugo
on:
push:
branches: [main]
paths: ["content/**", "config.yml", "themes/**"]
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: "latest"
extended: true
- name: Build
run: hugo --minify
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
path: "./public"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
```
### MkDocs Workflow
```yaml
name: Deploy MkDocs
on:
push:
branches: [main]
paths: ["docs/**", "mkdocs.yml"]
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Install dependencies
run: |
pip install mkdocs mkdocs-material
- name: Build
run: mkdocs build
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
path: "./site"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
```
## Repository Configuration
### GitHub Pages Settings
1. Navigate to repository **Settings**
2. Go to **Pages** section
3. Set **Source** to "GitHub Actions"
4. Save configuration
### Branch Protection
Protect your main branch:
```yaml
# .github/branch-protection.yml
protection_rules:
main:
required_status_checks:
strict: true
contexts:
- "Deploy Documentation"
enforce_admins: false
required_pull_request_reviews:
required_approving_review_count: 1
```
## Custom Domain Setup
### Add Custom Domain
1. Create `CNAME` file in your docs directory:
```
docs.yourdomain.com
```
2. Configure DNS records:
```
CNAME docs yourusername.github.io
```
3. Update DocuMCP deployment:
```bash
"deploy to GitHub Pages with custom domain docs.yourdomain.com"
```
### SSL Certificate
GitHub automatically provides SSL certificates for custom domains.
Verification:
- Check `https://docs.yourdomain.com` loads correctly
- Verify SSL certificate is valid
- Test redirect from `http://` to `https://`
## Environment Configuration
### Production Optimization
DocuMCP automatically configures:
**Build optimization:**
```yaml
- name: Build with optimization
run: |
export NODE_ENV=production
npm run build
env:
CI: true
NODE_OPTIONS: --max-old-space-size=4096
```
**Caching strategy:**
```yaml
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
```
### Environment Variables
Set up environment variables for production:
1. Go to repository **Settings**
2. Navigate to **Secrets and variables** > **Actions**
3. Add production variables:
- `HUGO_ENV=production`
- `NODE_ENV=production`
- Custom API keys (if needed)
## Deployment Verification
### Automatic Verification
DocuMCP includes verification:
```bash
"verify my GitHub Pages deployment is working correctly"
```
This checks:
- ✅ Site is accessible
- ✅ All pages load correctly
- ✅ Navigation works
- ✅ Search functionality (if enabled)
- ✅ Mobile responsiveness
- ✅ SSL certificate validity
### Manual Verification Checklist
- [ ] Homepage loads at `https://username.github.io/repository`
- [ ] All navigation links work
- [ ] Search functions properly
- [ ] Mobile layout is responsive
- [ ] Images and assets load
- [ ] Forms work (if applicable)
- [ ] Analytics tracking (if configured)
## Troubleshooting Deployment Issues
### Common Problems
**Build Fails:**
```bash
# Check workflow logs in GitHub Actions tab
# Common issues:
- Node.js version mismatch
- Missing dependencies
- Configuration errors
```
**404 Errors:**
```bash
# Fix baseURL configuration
# For Docusaurus:
baseUrl: '/repository-name/',
# For Hugo:
baseURL: 'https://username.github.io/repository-name/'
```
**Assets Not Loading:**
```bash
# Check publicPath configuration
# Ensure all asset paths are relative
```
### Debug Mode
Enable debug mode in workflows:
```yaml
- name: Debug build
run: |
npm run build -- --verbose
env:
DEBUG: true
ACTIONS_STEP_DEBUG: true
```
## Performance Optimization
### Build Performance
Optimize build times:
```yaml
- name: Cache build assets
uses: actions/cache@v4
with:
path: |
.next/cache
.docusaurus/cache
public/static
key: ${{ runner.os }}-build-${{ hashFiles('**/*.md', '**/*.js') }}
```
### Site Performance
DocuMCP automatically optimizes:
- **Image compression**: WebP format when possible
- **CSS minification**: Remove unused styles
- **JavaScript bundling**: Code splitting and tree shaking
- **Asset preloading**: Critical resources loaded first
## Monitoring and Analytics
### GitHub Actions Monitoring
Set up notifications for deployment failures:
```yaml
- name: Notify on failure
if: failure()
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Documentation Deployment Failed',
body: 'Deployment workflow failed. Check logs for details.',
labels: ['deployment', 'bug']
});
```
### Site Analytics
Add analytics to track usage:
**Google Analytics (Docusaurus):**
```javascript
// docusaurus.config.js
const config = {
presets: [
[
"classic",
{
gtag: {
trackingID: "G-XXXXXXXXXX",
anonymizeIP: true,
},
},
],
],
};
```
## Advanced Deployment Strategies
### Multi-Environment Deployment
Deploy to staging and production:
```yaml
# Deploy to staging on PR
on:
pull_request:
branches: [main]
# Deploy to production on merge
on:
push:
branches: [main]
```
### Rollback Strategy
Implement deployment rollback:
```yaml
- name: Store deployment info
run: |
echo "DEPLOYMENT_SHA=${{ github.sha }}" >> $GITHUB_ENV
echo "DEPLOYMENT_TIME=$(date)" >> $GITHUB_ENV
- name: Create rollback script
run: |
echo "#!/bin/bash" > rollback.sh
echo "git checkout ${{ env.DEPLOYMENT_SHA }}" >> rollback.sh
chmod +x rollback.sh
```
## Security Considerations
### Permissions
DocuMCP uses minimal permissions:
```yaml
permissions:
contents: read # Read repository content
pages: write # Deploy to GitHub Pages
id-token: write # OIDC authentication
```
### Secrets Management
Never commit secrets to repository:
- Use GitHub Actions secrets
- Environment variables for configuration
- OIDC tokens for authentication
## Next Steps
After successful deployment:
1. **[Monitor your site](site-monitoring.md)** for uptime and performance
2. **[Set up custom domain](custom-domains.md)** (optional)
3. **[Optimize for SEO](seo-optimization.md)**
4. **[Configure analytics](analytics-setup.md)**
## Summary
You now know how to:
✅ Deploy documentation using DocuMCP automation
✅ Configure GitHub Actions workflows
✅ Set up custom domains and SSL
✅ Verify deployments are working
✅ Troubleshoot common issues
✅ Optimize build and site performance
✅ Monitor deployments and analytics
Your documentation is now live and automatically updated!
```