This is page 12 of 20. Use http://codebase.md/tosin2013/documcp?page={x} to view the full context.
# Directory Structure
```
├── .dockerignore
├── .eslintignore
├── .eslintrc.json
├── .github
│ ├── agents
│ │ ├── documcp-ast.md
│ │ ├── documcp-deploy.md
│ │ ├── documcp-memory.md
│ │ ├── documcp-test.md
│ │ └── documcp-tool.md
│ ├── copilot-instructions.md
│ ├── dependabot.yml
│ ├── ISSUE_TEMPLATE
│ │ ├── automated-changelog.md
│ │ ├── bug_report.md
│ │ ├── bug_report.yml
│ │ ├── documentation_issue.md
│ │ ├── feature_request.md
│ │ ├── feature_request.yml
│ │ ├── npm-publishing-fix.md
│ │ └── release_improvements.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── release-drafter.yml
│ └── workflows
│ ├── auto-merge.yml
│ ├── ci.yml
│ ├── codeql.yml
│ ├── dependency-review.yml
│ ├── deploy-docs.yml
│ ├── README.md
│ ├── release-drafter.yml
│ └── release.yml
├── .gitignore
├── .husky
│ ├── commit-msg
│ └── pre-commit
├── .linkcheck.config.json
├── .markdown-link-check.json
├── .nvmrc
├── .pre-commit-config.yaml
├── .versionrc.json
├── 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
│ │ ├── 001-mcp-server-architecture.md
│ │ ├── 002-repository-analysis-engine.md
│ │ ├── 003-static-site-generator-recommendation-engine.md
│ │ ├── 004-diataxis-framework-integration.md
│ │ ├── 005-github-pages-deployment-automation.md
│ │ ├── 006-mcp-tools-api-design.md
│ │ ├── 007-mcp-prompts-and-resources-integration.md
│ │ ├── 008-intelligent-content-population-engine.md
│ │ ├── 009-content-accuracy-validation-framework.md
│ │ ├── 010-mcp-resource-pattern-redesign.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
│ ├── 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
│ │ ├── custom-domains.md
│ │ ├── documentation-freshness-tracking.md
│ │ ├── github-pages-deployment.md
│ │ ├── index.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
├── 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
│ │ ├── check-documentation-links.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
│ │ ├── 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
│ │ ├── ast-analyzer.ts
│ │ ├── code-scanner.ts
│ │ ├── content-extractor.ts
│ │ ├── drift-detector.ts
│ │ ├── freshness-tracker.ts
│ │ ├── language-parsers-simple.ts
│ │ ├── permission-checker.ts
│ │ └── sitemap-generator.ts
│ └── workflows
│ └── documentation-workflow.ts
├── test-docs-local.sh
├── tests
│ ├── api
│ │ └── mcp-responses.test.ts
│ ├── benchmarks
│ │ └── performance.test.ts
│ ├── edge-cases
│ │ └── error-handling.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-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.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
│ │ ├── 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
│ ├── ast-analyzer.test.ts
│ ├── content-extractor.test.ts
│ ├── drift-detector.test.ts
│ ├── freshness-tracker.test.ts
│ └── sitemap-generator.test.ts
├── tsconfig.json
└── typedoc.json
```
# Files
--------------------------------------------------------------------------------
/tests/integration/workflow.test.ts:
--------------------------------------------------------------------------------
```typescript
// Integration tests for complete documentation workflows
import { promises as fs } from "fs";
import path from "path";
import os from "os";
import { analyzeRepository } from "../../src/tools/analyze-repository";
import { recommendSSG } from "../../src/tools/recommend-ssg";
import { generateConfig } from "../../src/tools/generate-config";
import { setupStructure } from "../../src/tools/setup-structure";
import { deployPages } from "../../src/tools/deploy-pages";
import { verifyDeployment } from "../../src/tools/verify-deployment";
describe("Integration Testing - Complete Workflows", () => {
let tempDir: string;
let testProject: string;
beforeAll(async () => {
tempDir = path.join(os.tmpdir(), "documcp-integration-tests");
await fs.mkdir(tempDir, { recursive: true });
testProject = await createRealisticProject();
});
afterAll(async () => {
try {
await fs.rm(tempDir, { recursive: true, force: true });
} catch (error) {
console.warn("Failed to cleanup integration test directory:", error);
}
});
describe("End-to-End Documentation Workflow", () => {
it("should complete full documentation setup workflow", async () => {
const workflowDir = path.join(tempDir, "e2e-workflow");
await fs.mkdir(workflowDir, { recursive: true });
// Step 1: Analyze Repository
console.log("Step 1: Analyzing repository...");
const analysisResult = await analyzeRepository({
path: testProject,
depth: "standard",
});
expect(analysisResult.content).toBeDefined();
expect((analysisResult as any).isError).toBeFalsy();
// Extract analysis ID for next step
const analysisText = analysisResult.content.find((c) =>
c.text.includes('"id"'),
);
const analysis = JSON.parse(analysisText!.text);
const analysisId = analysis.id;
expect(analysisId).toBeDefined();
expect(analysis.dependencies.ecosystem).toBe("javascript");
// Step 2: Get SSG Recommendation
console.log("Step 2: Getting SSG recommendation...");
const recommendationResult = await recommendSSG({
analysisId: analysisId,
preferences: {
priority: "features",
ecosystem: "javascript",
},
});
expect(recommendationResult.content).toBeDefined();
const recommendationText = recommendationResult.content.find((c) =>
c.text.includes('"recommended"'),
);
const recommendation = JSON.parse(recommendationText!.text);
expect(recommendation.recommended).toBeDefined();
expect(["jekyll", "hugo", "docusaurus", "mkdocs", "eleventy"]).toContain(
recommendation.recommended,
);
// Step 3: Generate Configuration
console.log("Step 3: Generating configuration...");
const configResult = await generateConfig({
ssg: recommendation.recommended,
projectName: "Integration Test Project",
projectDescription: "End-to-end integration test",
outputPath: workflowDir,
});
expect(configResult.content).toBeDefined();
expect((configResult as any).isError).toBeFalsy();
// Verify config files were created
const files = await fs.readdir(workflowDir);
expect(files.length).toBeGreaterThan(0);
// Step 4: Setup Documentation Structure
console.log("Step 4: Setting up documentation structure...");
const docsDir = path.join(workflowDir, "docs");
const structureResult = await setupStructure({
path: docsDir,
ssg: recommendation.recommended,
includeExamples: true,
});
expect(structureResult.content).toBeDefined();
expect((structureResult as any).isError).toBeFalsy();
// Verify Diataxis structure was created
const diataxisCategories = [
"tutorials",
"how-to",
"reference",
"explanation",
];
for (const category of diataxisCategories) {
const categoryPath = path.join(docsDir, category);
expect(
await fs
.access(categoryPath)
.then(() => true)
.catch(() => false),
).toBe(true);
}
// Step 5: Setup Deployment
console.log("Step 5: Setting up deployment...");
const deploymentResult = await deployPages({
repository: workflowDir,
ssg: recommendation.recommended,
branch: "gh-pages",
customDomain: "docs.example.com",
});
expect(deploymentResult.content).toBeDefined();
expect((deploymentResult as any).isError).toBeFalsy();
// Verify workflow and CNAME were created
const workflowPath = path.join(
workflowDir,
".github",
"workflows",
"deploy-docs.yml",
);
const cnamePath = path.join(workflowDir, "CNAME");
expect(
await fs
.access(workflowPath)
.then(() => true)
.catch(() => false),
).toBe(true);
expect(
await fs
.access(cnamePath)
.then(() => true)
.catch(() => false),
).toBe(true);
// Step 6: Verify Deployment Setup
console.log("Step 6: Verifying deployment setup...");
const verificationResult = await verifyDeployment({
repository: workflowDir,
url: "https://docs.example.com",
});
expect(verificationResult.content).toBeDefined();
// Parse the JSON response to check actual verification data
const verificationData = JSON.parse(verificationResult.content[0].text);
const passCount = verificationData.summary.passed;
const failCount = verificationData.summary.failed;
console.log("Pass count:", passCount, "Fail count:", failCount);
// Should have at least some passing checks
expect(passCount).toBeGreaterThan(0);
expect(passCount).toBeGreaterThanOrEqual(failCount);
console.log("✅ End-to-end workflow completed successfully!");
}, 30000); // 30 second timeout for full workflow
});
describe("Workflow Variations", () => {
it("should handle Python project workflow", async () => {
const pythonProject = await createPythonProject();
// Analyze Python project
const analysis = await analyzeRepository({
path: pythonProject,
depth: "standard",
});
const analysisData = JSON.parse(
analysis.content.find((c) => c.text.includes('"ecosystem"'))!.text,
);
expect(analysisData.dependencies.ecosystem).toBe("python");
// Get recommendation (likely MkDocs for Python)
const recommendation = await recommendSSG({
analysisId: analysisData.id,
});
// const recData = JSON.parse(recommendation.content.find(c => c.text.includes('"recommended"'))!.text);
// Generate MkDocs config
const configDir = path.join(tempDir, "python-workflow");
await fs.mkdir(configDir, { recursive: true });
const config = await generateConfig({
ssg: "mkdocs",
projectName: "Python Test Project",
outputPath: configDir,
});
// Verify MkDocs-specific files
expect(
await fs
.access(path.join(configDir, "mkdocs.yml"))
.then(() => true)
.catch(() => false),
).toBe(true);
expect(
await fs
.access(path.join(configDir, "requirements.txt"))
.then(() => true)
.catch(() => false),
).toBe(true);
});
it("should handle different SSG preferences", async () => {
const analysisId = "test-preferences-123";
// Test simplicity preference
const simplicityRec = await recommendSSG({
analysisId,
preferences: { priority: "simplicity" },
});
// Test performance preference
const performanceRec = await recommendSSG({
analysisId,
preferences: { priority: "performance" },
});
// Test features preference
const featuresRec = await recommendSSG({
analysisId,
preferences: { priority: "features" },
});
// All should provide valid recommendations
[simplicityRec, performanceRec, featuresRec].forEach((result) => {
expect(result.content).toBeDefined();
const rec = JSON.parse(
result.content.find((c) => c.text.includes('"recommended"'))!.text,
);
expect([
"jekyll",
"hugo",
"docusaurus",
"mkdocs",
"eleventy",
]).toContain(rec.recommended);
});
});
it("should handle deployment workflow variations", async () => {
const deploymentDir = path.join(tempDir, "deployment-variations");
await fs.mkdir(deploymentDir, { recursive: true });
// Test different SSGs
const ssgs = [
"docusaurus",
"mkdocs",
"hugo",
"jekyll",
"eleventy",
] as const;
for (const ssg of ssgs) {
const ssgDir = path.join(deploymentDir, ssg);
await fs.mkdir(ssgDir, { recursive: true });
const result = await deployPages({
repository: ssgDir,
ssg: ssg,
branch: "main",
});
expect(result.content).toBeDefined();
const workflowPath = path.join(
ssgDir,
".github",
"workflows",
"deploy-docs.yml",
);
expect(
await fs
.access(workflowPath)
.then(() => true)
.catch(() => false),
).toBe(true);
const workflowContent = await fs.readFile(workflowPath, "utf-8");
// Handle different SSG name formats
const expectedName =
ssg === "mkdocs"
? "Deploy MkDocs"
: `Deploy ${ssg.charAt(0).toUpperCase() + ssg.slice(1)}`;
expect(workflowContent).toContain(expectedName);
// Verify SSG-specific workflow content
switch (ssg) {
case "docusaurus":
expect(workflowContent).toContain("npm run build");
expect(workflowContent).toContain("id-token: write"); // OIDC compliance
break;
case "mkdocs":
expect(workflowContent).toContain("mkdocs gh-deploy");
expect(workflowContent).toContain("python");
break;
case "hugo":
expect(workflowContent).toContain("peaceiris/actions-hugo");
expect(workflowContent).toContain("hugo --minify");
break;
case "jekyll":
expect(workflowContent).toContain("bundle exec jekyll build");
expect(workflowContent).toContain("ruby");
break;
case "eleventy":
expect(workflowContent).toContain("npm run build");
break;
}
}
});
});
describe("Error Handling and Recovery", () => {
it("should handle missing repository gracefully", async () => {
const result = await analyzeRepository({
path: "/non/existent/path",
depth: "standard",
});
expect((result as any).isError).toBe(true);
expect(result.content[0].text).toContain("Error:");
});
it("should handle invalid configuration gracefully", async () => {
const invalidDir = "/invalid/write/path/that/should/fail";
const result = await generateConfig({
ssg: "docusaurus",
projectName: "Test",
outputPath: invalidDir,
});
expect((result as any).isError).toBe(true);
expect(result.content[0].text).toContain("Error:");
});
it("should handle structure setup in non-existent directory", async () => {
// This should actually work because setupStructure creates directories
const result = await setupStructure({
path: path.join(tempDir, "new-structure-dir"),
ssg: "docusaurus",
includeExamples: false,
});
expect((result as any).isError).toBeFalsy();
expect(result.content).toBeDefined();
});
it("should provide helpful error messages and resolutions", async () => {
const errorResult = await analyzeRepository({
path: "/definitely/does/not/exist",
depth: "standard",
});
expect((errorResult as any).isError).toBe(true);
const errorText = errorResult.content.map((c) => c.text).join(" ");
// Check for resolution in JSON format (lowercase) or formatted text (capitalized)
expect(errorText.toLowerCase()).toContain("resolution");
expect(errorText.toLowerCase()).toContain("ensure");
});
});
describe("Performance and Resource Management", () => {
it("should handle large repository analysis within performance bounds", async () => {
const largeRepo = await createLargeRepository();
const startTime = Date.now();
const result = await analyzeRepository({
path: largeRepo,
depth: "standard",
});
const executionTime = Date.now() - startTime;
// Should complete within reasonable time (large repo target is 60s)
expect(executionTime).toBeLessThan(60000);
expect(result.content).toBeDefined();
const analysisData = JSON.parse(
result.content.find((c) => c.text.includes('"totalFiles"'))!.text,
);
expect(analysisData.structure.totalFiles).toBeGreaterThan(1000);
}, 65000); // 65s timeout for large repo test
it("should clean up resources properly", async () => {
const tempWorkflowDir = path.join(tempDir, "resource-cleanup");
// Run multiple operations
await generateConfig({
ssg: "docusaurus",
projectName: "Cleanup Test",
outputPath: tempWorkflowDir,
});
await setupStructure({
path: path.join(tempWorkflowDir, "docs"),
ssg: "docusaurus",
includeExamples: true,
});
// Verify files were created
const files = await fs.readdir(tempWorkflowDir);
expect(files.length).toBeGreaterThan(0);
// Cleanup should work
await fs.rm(tempWorkflowDir, { recursive: true, force: true });
expect(
await fs
.access(tempWorkflowDir)
.then(() => false)
.catch(() => true),
).toBe(true);
});
});
// Helper functions
async function createRealisticProject(): Promise<string> {
const projectPath = path.join(tempDir, "realistic-project");
await fs.mkdir(projectPath, { recursive: true });
// package.json with realistic dependencies
const packageJson = {
name: "realistic-test-project",
version: "2.1.0",
description: "A realistic Node.js project for testing DocuMCP",
main: "src/index.js",
scripts: {
start: "node src/index.js",
dev: "nodemon src/index.js",
test: "jest",
build: "webpack --mode production",
lint: "eslint src/",
docs: "jsdoc src/ -d docs/",
},
dependencies: {
express: "^4.18.2",
lodash: "^4.17.21",
axios: "^1.4.0",
moment: "^2.29.4",
"body-parser": "^1.20.2",
},
devDependencies: {
jest: "^29.5.0",
nodemon: "^2.0.22",
eslint: "^8.42.0",
webpack: "^5.86.0",
jsdoc: "^4.0.2",
},
keywords: ["node", "express", "api", "web"],
author: "Test Author",
license: "MIT",
};
await fs.writeFile(
path.join(projectPath, "package.json"),
JSON.stringify(packageJson, null, 2),
);
// Source directory structure
await fs.mkdir(path.join(projectPath, "src"), { recursive: true });
await fs.mkdir(path.join(projectPath, "src", "controllers"), {
recursive: true,
});
await fs.mkdir(path.join(projectPath, "src", "models"), {
recursive: true,
});
await fs.mkdir(path.join(projectPath, "src", "routes"), {
recursive: true,
});
await fs.mkdir(path.join(projectPath, "src", "utils"), { recursive: true });
// Main application files
await fs.writeFile(
path.join(projectPath, "src", "index.js"),
`const express = require('express');
const bodyParser = require('body-parser');
const routes = require('./routes');
const app = express();
app.use(bodyParser.json());
app.use('/api', routes);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(\`Server running on port \${PORT}\`);
});`,
);
await fs.writeFile(
path.join(projectPath, "src", "routes", "index.js"),
`const express = require('express');
const router = express.Router();
router.get('/health', (req, res) => {
res.json({ status: 'OK', timestamp: new Date().toISOString() });
});
module.exports = router;`,
);
await fs.writeFile(
path.join(projectPath, "src", "controllers", "userController.js"),
`const { getUserById, createUser } = require('../models/user');
async function getUser(req, res) {
const user = await getUserById(req.params.id);
res.json(user);
}
module.exports = { getUser };`,
);
await fs.writeFile(
path.join(projectPath, "src", "models", "user.js"),
`const users = [];
function getUserById(id) {
return users.find(user => user.id === id);
}
function createUser(userData) {
const user = { id: Date.now(), ...userData };
users.push(user);
return user;
}
module.exports = { getUserById, createUser };`,
);
await fs.writeFile(
path.join(projectPath, "src", "utils", "helpers.js"),
`const _ = require('lodash');
const moment = require('moment');
function formatDate(date) {
return moment(date).format('YYYY-MM-DD HH:mm:ss');
}
function validateEmail(email) {
return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);
}
module.exports = { formatDate, validateEmail };`,
);
// Test directory
await fs.mkdir(path.join(projectPath, "tests"), { recursive: true });
await fs.writeFile(
path.join(projectPath, "tests", "app.test.js"),
`const { formatDate, validateEmail } = require('../src/utils/helpers');
describe('Helper Functions', () => {
test('formatDate should format date correctly', () => {
const date = new Date('2023-01-01');
expect(formatDate(date)).toMatch(/\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}/);
});
test('validateEmail should validate email correctly', () => {
expect(validateEmail('[email protected]')).toBe(true);
expect(validateEmail('invalid-email')).toBe(false);
});
});`,
);
// Configuration files
await fs.writeFile(
path.join(projectPath, ".eslintrc.js"),
`module.exports = {
env: { node: true, es2021: true },
extends: ['eslint:recommended'],
parserOptions: { ecmaVersion: 12, sourceType: 'module' },
rules: { 'no-unused-vars': 'warn' }
};`,
);
await fs.writeFile(
path.join(projectPath, "jest.config.js"),
`module.exports = {
testEnvironment: 'node',
collectCoverageFrom: ['src/**/*.js'],
testMatch: ['**/tests/**/*.test.js']
};`,
);
// Documentation
await fs.writeFile(
path.join(projectPath, "README.md"),
`# Realistic Test Project
A comprehensive Node.js application for testing DocuMCP functionality.
## Features
- Express.js web server
- RESTful API endpoints
- User management system
- Comprehensive test suite
- ESLint code quality
- JSDoc documentation
## Getting Started
1. Install dependencies: \`npm install\`
2. Start development server: \`npm run dev\`
3. Run tests: \`npm test\`
## API Endpoints
- \`GET /api/health\` - Health check endpoint
- \`GET /api/users/:id\` - Get user by ID
## Contributing
Please read CONTRIBUTING.md for contribution guidelines.`,
);
await fs.writeFile(
path.join(projectPath, "CONTRIBUTING.md"),
`# Contributing to Realistic Test Project
## Development Setup
1. Fork the repository
2. Clone your fork
3. Install dependencies
4. Create a feature branch
5. Make changes and test
6. Submit a pull request
## Code Style
- Follow ESLint configuration
- Write tests for new features
- Update documentation as needed`,
);
await fs.writeFile(
path.join(projectPath, "LICENSE"),
"MIT License\n\nCopyright (c) 2023 Test Author",
);
// CI/CD workflow
await fs.mkdir(path.join(projectPath, ".github", "workflows"), {
recursive: true,
});
await fs.writeFile(
path.join(projectPath, ".github", "workflows", "ci.yml"),
`name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build`,
);
return projectPath;
}
async function createPythonProject(): Promise<string> {
const projectPath = path.join(tempDir, "python-project");
await fs.mkdir(projectPath, { recursive: true });
// Python project structure
await fs.writeFile(
path.join(projectPath, "requirements.txt"),
`flask==2.3.2
requests==2.31.0
pytest==7.4.0
black==23.3.0
flake8==6.0.0`,
);
await fs.mkdir(path.join(projectPath, "src"), { recursive: true });
await fs.writeFile(
path.join(projectPath, "src", "app.py"),
`from flask import Flask, jsonify
import requests
app = Flask(__name__)
@app.route('/health')
def health():
return jsonify({'status': 'OK'})
if __name__ == '__main__':
app.run(debug=True)`,
);
await fs.mkdir(path.join(projectPath, "tests"), { recursive: true });
await fs.writeFile(
path.join(projectPath, "tests", "test_app.py"),
`import pytest
from src.app import app
def test_health():
client = app.test_client()
response = client.get('/health')
assert response.status_code == 200`,
);
await fs.writeFile(
path.join(projectPath, "README.md"),
"# Python Test Project\n\nA Flask application for testing Python project analysis.",
);
return projectPath;
}
async function createLargeRepository(): Promise<string> {
const repoPath = path.join(tempDir, "large-repository");
await fs.mkdir(repoPath, { recursive: true });
// Create a repository with 1200+ files to trigger large repo categorization
await fs.writeFile(
path.join(repoPath, "package.json"),
'{"name": "large-repo"}',
);
for (let i = 0; i < 30; i++) {
const dirPath = path.join(repoPath, `module-${i}`);
await fs.mkdir(dirPath, { recursive: true });
for (let j = 0; j < 40; j++) {
const fileName = `component-${j}.js`;
const content = `// Component ${i}-${j}
export default function Component${i}${j}() {
return <div>Component ${i}-${j}</div>;
}`;
await fs.writeFile(path.join(dirPath, fileName), content);
}
}
await fs.writeFile(
path.join(repoPath, "README.md"),
"# Large Repository\n\nThis repository has 1200+ files for performance testing.",
);
return repoPath;
}
});
```
--------------------------------------------------------------------------------
/tests/memory/freshness-kg-integration.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for Documentation Freshness Knowledge Graph Integration
*/
import { promises as fs } from "fs";
import path from "path";
import { tmpdir } from "os";
import {
storeFreshnessEvent,
updateFreshnessEvent,
getFreshnessHistory,
getStalenessInsights,
compareFreshnessAcrossProjects,
} from "../../src/memory/freshness-kg-integration.js";
import type { FreshnessScanReport } from "../../src/utils/freshness-tracker.js";
describe("Freshness Knowledge Graph Integration", () => {
let testDir: string;
beforeEach(async () => {
// Create temporary test directory
testDir = path.join(tmpdir(), `freshness-kg-test-${Date.now()}`);
await fs.mkdir(testDir, { recursive: true });
// Set storage directory to test directory
process.env.DOCUMCP_STORAGE_DIR = path.join(testDir, ".documcp/memory");
});
afterEach(async () => {
// Clean up test directory
try {
await fs.rm(testDir, { recursive: true, force: true });
} catch (error) {
// Ignore cleanup errors
}
delete process.env.DOCUMCP_STORAGE_DIR;
});
describe("storeFreshnessEvent", () => {
it("should store a freshness scan event in KG", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
const report: FreshnessScanReport = {
docsPath,
scannedAt: new Date().toISOString(),
totalFiles: 10,
filesWithMetadata: 8,
filesWithoutMetadata: 2,
freshFiles: 6,
warningFiles: 2,
staleFiles: 1,
criticalFiles: 1,
files: [
{
filePath: path.join(docsPath, "page1.md"),
relativePath: "page1.md",
hasMetadata: true,
isStale: false,
stalenessLevel: "fresh",
ageInMs: 1000 * 60 * 60 * 24, // 1 day
ageFormatted: "1 day",
},
{
filePath: path.join(docsPath, "page2.md"),
relativePath: "page2.md",
hasMetadata: true,
isStale: true,
stalenessLevel: "critical",
ageInMs: 1000 * 60 * 60 * 24 * 100, // 100 days
ageFormatted: "100 days",
},
],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
const eventId = await storeFreshnessEvent(
projectPath,
docsPath,
report,
"scan",
);
expect(eventId).toBeDefined();
expect(eventId).toContain("freshness_event:");
});
it("should store event with different event types", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
const report: FreshnessScanReport = {
docsPath,
scannedAt: new Date().toISOString(),
totalFiles: 5,
filesWithMetadata: 5,
filesWithoutMetadata: 0,
freshFiles: 5,
warningFiles: 0,
staleFiles: 0,
criticalFiles: 0,
files: [],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
const initEventId = await storeFreshnessEvent(
projectPath,
docsPath,
report,
"initialization",
);
expect(initEventId).toBeDefined();
const updateEventId = await storeFreshnessEvent(
projectPath,
docsPath,
report,
"update",
);
expect(updateEventId).toBeDefined();
const validationEventId = await storeFreshnessEvent(
projectPath,
docsPath,
report,
"validation",
);
expect(validationEventId).toBeDefined();
});
});
describe("getFreshnessHistory", () => {
it("should retrieve freshness event history", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
const report: FreshnessScanReport = {
docsPath,
scannedAt: new Date().toISOString(),
totalFiles: 5,
filesWithMetadata: 5,
filesWithoutMetadata: 0,
freshFiles: 5,
warningFiles: 0,
staleFiles: 0,
criticalFiles: 0,
files: [],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
// Store multiple events
await storeFreshnessEvent(projectPath, docsPath, report, "scan");
await new Promise((resolve) => setTimeout(resolve, 10)); // Small delay
await storeFreshnessEvent(projectPath, docsPath, report, "update");
const history = await getFreshnessHistory(projectPath, 10);
expect(history).toBeDefined();
expect(history.length).toBeGreaterThanOrEqual(0);
});
it("should return empty array for project with no history", async () => {
const projectPath = path.join(testDir, "new-project");
const history = await getFreshnessHistory(projectPath, 10);
expect(history).toEqual([]);
});
});
describe("getStalenessInsights", () => {
it("should return insights for project with no history", async () => {
const projectPath = path.join(testDir, "new-project");
const insights = await getStalenessInsights(projectPath);
expect(insights).toBeDefined();
expect(insights.totalEvents).toBe(0);
expect(insights.averageImprovementScore).toBe(0);
expect(insights.trend).toBe("stable");
expect(insights.currentStatus).toBeNull();
expect(insights.recommendations.length).toBeGreaterThan(0);
expect(insights.recommendations[0]).toContain(
"No freshness tracking history found",
);
});
it("should calculate insights from event history", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
const report: FreshnessScanReport = {
docsPath,
scannedAt: new Date().toISOString(),
totalFiles: 10,
filesWithMetadata: 10,
filesWithoutMetadata: 0,
freshFiles: 8,
warningFiles: 1,
staleFiles: 1,
criticalFiles: 0,
files: [],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
await storeFreshnessEvent(projectPath, docsPath, report, "scan");
const insights = await getStalenessInsights(projectPath);
expect(insights).toBeDefined();
expect(insights.trend).toMatch(/improving|declining|stable/);
expect(insights.recommendations).toBeDefined();
expect(Array.isArray(insights.recommendations)).toBe(true);
});
it("should detect improving trend", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
// Store older event with worse metrics
const olderReport: FreshnessScanReport = {
docsPath,
scannedAt: new Date(Date.now() - 1000 * 60 * 60 * 24).toISOString(),
totalFiles: 10,
filesWithMetadata: 10,
filesWithoutMetadata: 0,
freshFiles: 5,
warningFiles: 2,
staleFiles: 2,
criticalFiles: 1,
files: [],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
await storeFreshnessEvent(projectPath, docsPath, olderReport, "scan");
await new Promise((resolve) => setTimeout(resolve, 10));
// Store newer event with better metrics
const newerReport: FreshnessScanReport = {
...olderReport,
scannedAt: new Date().toISOString(),
freshFiles: 9,
warningFiles: 1,
staleFiles: 0,
criticalFiles: 0,
};
await storeFreshnessEvent(projectPath, docsPath, newerReport, "scan");
const insights = await getStalenessInsights(projectPath);
expect(insights.trend).toMatch(/improving|stable/);
});
it("should generate recommendations for critical files", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
const report: FreshnessScanReport = {
docsPath,
scannedAt: new Date().toISOString(),
totalFiles: 10,
filesWithMetadata: 10,
filesWithoutMetadata: 0,
freshFiles: 5,
warningFiles: 2,
staleFiles: 1,
criticalFiles: 2,
files: [],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
await storeFreshnessEvent(projectPath, docsPath, report, "scan");
const insights = await getStalenessInsights(projectPath);
expect(insights).toBeDefined();
expect(insights.recommendations).toBeDefined();
expect(Array.isArray(insights.recommendations)).toBe(true);
});
it("should recommend validation for files without metadata", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
const report: FreshnessScanReport = {
docsPath,
scannedAt: new Date().toISOString(),
totalFiles: 10,
filesWithMetadata: 7,
filesWithoutMetadata: 3,
freshFiles: 7,
warningFiles: 0,
staleFiles: 0,
criticalFiles: 0,
files: [],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
await storeFreshnessEvent(projectPath, docsPath, report, "scan");
const insights = await getStalenessInsights(projectPath);
expect(insights).toBeDefined();
expect(insights.recommendations).toBeDefined();
expect(Array.isArray(insights.recommendations)).toBe(true);
});
});
describe("compareFreshnessAcrossProjects", () => {
it("should handle project with no history", async () => {
const projectPath = path.join(testDir, "new-project");
const comparison = await compareFreshnessAcrossProjects(projectPath);
expect(comparison).toBeDefined();
expect(comparison.currentProject.path).toBe(projectPath);
expect(comparison.currentProject.improvementScore).toBe(0);
expect(comparison.similarProjects).toEqual([]);
});
it("should calculate ranking for project", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
const report: FreshnessScanReport = {
docsPath,
scannedAt: new Date().toISOString(),
totalFiles: 10,
filesWithMetadata: 10,
filesWithoutMetadata: 0,
freshFiles: 8,
warningFiles: 1,
staleFiles: 1,
criticalFiles: 0,
files: [],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
await storeFreshnessEvent(projectPath, docsPath, report, "scan");
const comparison = await compareFreshnessAcrossProjects(projectPath);
expect(comparison.ranking).toBeGreaterThan(0);
});
it("should compare with similar projects", async () => {
const projectPath1 = path.join(testDir, "project1");
const docsPath1 = path.join(projectPath1, "docs");
const projectPath2 = path.join(testDir, "project2");
const docsPath2 = path.join(projectPath2, "docs");
// Store events for both projects
const report1: FreshnessScanReport = {
docsPath: docsPath1,
scannedAt: new Date().toISOString(),
totalFiles: 10,
filesWithMetadata: 10,
filesWithoutMetadata: 0,
freshFiles: 9,
warningFiles: 1,
staleFiles: 0,
criticalFiles: 0,
files: [],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
const report2: FreshnessScanReport = {
...report1,
docsPath: docsPath2,
freshFiles: 7,
warningFiles: 2,
staleFiles: 1,
};
await storeFreshnessEvent(projectPath1, docsPath1, report1, "scan");
await storeFreshnessEvent(projectPath2, docsPath2, report2, "scan");
// The function should work even if there are no similar_to edges
// (it will just return empty similarProjects array)
const comparison = await compareFreshnessAcrossProjects(projectPath1);
expect(comparison.currentProject.path).toBe(projectPath1);
expect(comparison.similarProjects).toBeDefined();
expect(Array.isArray(comparison.similarProjects)).toBe(true);
});
});
describe("updateFreshnessEvent", () => {
it("should update a freshness event with new data", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
const report: FreshnessScanReport = {
docsPath,
scannedAt: new Date().toISOString(),
totalFiles: 10,
filesWithMetadata: 8,
filesWithoutMetadata: 2,
freshFiles: 8,
warningFiles: 0,
staleFiles: 0,
criticalFiles: 0,
files: [],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
const eventId = await storeFreshnessEvent(
projectPath,
docsPath,
report,
"scan",
);
await updateFreshnessEvent(eventId, {
filesInitialized: 2,
filesUpdated: 5,
eventType: "update",
});
// Verify the update by checking history
const history = await getFreshnessHistory(projectPath, 10);
expect(history.length).toBeGreaterThan(0);
});
it("should throw error for non-existent event", async () => {
await expect(
updateFreshnessEvent("freshness_event:nonexistent", {
filesInitialized: 1,
}),
).rejects.toThrow();
});
});
describe("Edge cases and additional coverage", () => {
it("should handle more than 10 stale files", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
// Create 15 stale files
const staleFiles = Array.from({ length: 15 }, (_, i) => ({
filePath: path.join(docsPath, `stale${i}.md`),
relativePath: `stale${i}.md`,
hasMetadata: true,
isStale: true,
stalenessLevel: "stale" as const,
ageInMs: 1000 * 60 * 60 * 24 * (40 + i), // 40+ days
ageFormatted: `${40 + i} days`,
}));
const report: FreshnessScanReport = {
docsPath,
scannedAt: new Date().toISOString(),
totalFiles: 15,
filesWithMetadata: 15,
filesWithoutMetadata: 0,
freshFiles: 0,
warningFiles: 0,
staleFiles: 15,
criticalFiles: 0,
files: staleFiles,
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
const eventId = await storeFreshnessEvent(
projectPath,
docsPath,
report,
"scan",
);
expect(eventId).toBeDefined();
const history = await getFreshnessHistory(projectPath, 1);
expect(history[0].event.mostStaleFiles.length).toBeLessThanOrEqual(10);
});
it("should recommend action for 30%+ stale files", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
const report: FreshnessScanReport = {
docsPath,
scannedAt: new Date().toISOString(),
totalFiles: 10,
filesWithMetadata: 10,
filesWithoutMetadata: 0,
freshFiles: 6,
warningFiles: 0,
staleFiles: 4, // 40% stale
criticalFiles: 0,
files: [],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
await storeFreshnessEvent(projectPath, docsPath, report, "scan");
const insights = await getStalenessInsights(projectPath);
expect(insights.recommendations).toBeDefined();
expect(insights.recommendations.length).toBeGreaterThan(0);
// Check that we get recommendations about stale files
const hasStaleRecommendation = insights.recommendations.some(
(r) => r.includes("30%") || r.includes("stale"),
);
expect(hasStaleRecommendation).toBe(true);
});
it("should detect declining trend", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
// Store older event with good metrics
const olderReport: FreshnessScanReport = {
docsPath,
scannedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString(),
totalFiles: 10,
filesWithMetadata: 10,
filesWithoutMetadata: 0,
freshFiles: 9,
warningFiles: 1,
staleFiles: 0,
criticalFiles: 0,
files: [],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
await storeFreshnessEvent(projectPath, docsPath, olderReport, "scan");
await new Promise((resolve) => setTimeout(resolve, 10));
// Store newer event with worse metrics
const newerReport: FreshnessScanReport = {
...olderReport,
scannedAt: new Date().toISOString(),
freshFiles: 5,
warningFiles: 2,
staleFiles: 2,
criticalFiles: 1,
};
await storeFreshnessEvent(projectPath, docsPath, newerReport, "scan");
const insights = await getStalenessInsights(projectPath);
expect(insights.trend).toMatch(/declining|stable/);
});
it("should identify chronically stale files", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
// Create multiple events with same critical/stale files
// Need to create enough events so files appear repeatedly
for (let i = 0; i < 6; i++) {
const report: FreshnessScanReport = {
docsPath,
scannedAt: new Date(
Date.now() - 1000 * 60 * 60 * 24 * (6 - i),
).toISOString(),
totalFiles: 10,
filesWithMetadata: 10,
filesWithoutMetadata: 0,
freshFiles: 6,
warningFiles: 0,
staleFiles: 2,
criticalFiles: 2,
files: [
{
filePath: path.join(docsPath, "always-stale.md"),
relativePath: "always-stale.md",
hasMetadata: true,
isStale: true,
stalenessLevel: "critical",
ageInMs: 1000 * 60 * 60 * 24 * 100,
ageFormatted: "100 days",
},
{
filePath: path.join(docsPath, "also-stale.md"),
relativePath: "also-stale.md",
hasMetadata: true,
isStale: true,
stalenessLevel: "critical",
ageInMs: 1000 * 60 * 60 * 24 * 95,
ageFormatted: "95 days",
},
{
filePath: path.join(docsPath, "stale-doc.md"),
relativePath: "stale-doc.md",
hasMetadata: true,
isStale: true,
stalenessLevel: "stale",
ageInMs: 1000 * 60 * 60 * 24 * 40,
ageFormatted: "40 days",
},
{
filePath: path.join(docsPath, "another-stale.md"),
relativePath: "another-stale.md",
hasMetadata: true,
isStale: true,
stalenessLevel: "stale",
ageInMs: 1000 * 60 * 60 * 24 * 35,
ageFormatted: "35 days",
},
],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
await storeFreshnessEvent(projectPath, docsPath, report, "scan");
await new Promise((resolve) => setTimeout(resolve, 10));
}
const insights = await getStalenessInsights(projectPath);
// With 6 events and files appearing in all of them,
// should trigger chronically stale recommendation
const hasChronicallyStale = insights.recommendations.some(
(r) => r.includes("chronically") || r.includes("critical"),
);
expect(hasChronicallyStale).toBe(true);
});
it("should handle files without age information", async () => {
const projectPath = path.join(testDir, "test-project");
const docsPath = path.join(projectPath, "docs");
const report: FreshnessScanReport = {
docsPath,
scannedAt: new Date().toISOString(),
totalFiles: 5,
filesWithMetadata: 3,
filesWithoutMetadata: 2,
freshFiles: 3,
warningFiles: 0,
staleFiles: 0,
criticalFiles: 0,
files: [
{
filePath: path.join(docsPath, "no-metadata.md"),
relativePath: "no-metadata.md",
hasMetadata: false,
isStale: false,
stalenessLevel: "unknown",
},
],
thresholds: {
warning: { value: 7, unit: "days" },
stale: { value: 30, unit: "days" },
critical: { value: 90, unit: "days" },
},
};
const eventId = await storeFreshnessEvent(
projectPath,
docsPath,
report,
"scan",
);
expect(eventId).toBeDefined();
const history = await getFreshnessHistory(projectPath, 1);
expect(history.length).toBeGreaterThan(0);
if (history.length > 0) {
expect(history[0].event.averageAge).toBeUndefined();
expect(history[0].event.oldestFile).toBeUndefined();
}
});
});
});
```
--------------------------------------------------------------------------------
/src/memory/multi-agent-sharing.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Multi-Agent Memory Sharing System for DocuMCP
* Implements Issue #50: Multi-Agent Memory Sharing
*
* Enables multiple DocuMCP instances to share and synchronize memory,
* creating a collaborative knowledge network for enhanced learning and recommendations.
*/
import { MemoryManager } from "./manager.js";
import { MemoryEntry } from "./storage.js";
import { EventEmitter } from "events";
import * as crypto from "crypto";
export interface AgentIdentity {
id: string;
name: string;
version: string;
capabilities: string[];
lastSeen: string;
trustLevel: "untrusted" | "low" | "medium" | "high" | "trusted";
specializations: string[];
}
export interface SharedMemory {
originalEntry: MemoryEntry;
sharingMetadata: {
sourceAgent: string;
sharedAt: string;
accessCount: number;
trustScore: number;
validatedBy: string[];
conflicts: string[];
};
transformations: Array<{
agentId: string;
transformationType:
| "anonymization"
| "aggregation"
| "enrichment"
| "validation";
appliedAt: string;
details: Record<string, any>;
}>;
}
export interface SyncRequest {
id: string;
fromAgent: string;
toAgent: string;
requestType: "full_sync" | "incremental" | "selective" | "validation";
criteria?: {
types?: string[];
tags?: string[];
timeRange?: { start: string; end: string };
minTrustLevel?: number;
};
requestedAt: string;
status: "pending" | "in_progress" | "completed" | "failed";
}
export interface ConflictResolution {
conflictId: string;
conflictType:
| "duplicate"
| "contradiction"
| "version_mismatch"
| "trust_dispute";
involvedEntries: string[];
involvedAgents: string[];
resolutionStrategy:
| "merge"
| "prioritize_trusted"
| "manual_review"
| "temporal_precedence";
resolvedAt?: string;
resolution?: {
action: string;
resultingEntry?: MemoryEntry;
metadata: Record<string, any>;
};
}
export interface CollaborativeInsight {
id: string;
type: "trend" | "pattern" | "anomaly" | "consensus" | "disagreement";
description: string;
evidence: string[];
contributingAgents: string[];
confidence: number;
generatedAt: string;
actionable: boolean;
recommendations?: string[];
}
export class MultiAgentMemorySharing extends EventEmitter {
private memoryManager: MemoryManager;
private agentId: string;
private knownAgents: Map<string, AgentIdentity>;
private sharedMemories: Map<string, SharedMemory>;
private syncRequests: Map<string, SyncRequest>;
private conflicts: Map<string, ConflictResolution>;
private collaborativeInsights: Map<string, CollaborativeInsight>;
private syncInterval: NodeJS.Timeout | null = null;
constructor(
memoryManager: MemoryManager,
agentIdentity: Partial<AgentIdentity>,
) {
super();
this.memoryManager = memoryManager;
this.agentId = agentIdentity.id || this.generateAgentId();
this.knownAgents = new Map();
this.sharedMemories = new Map();
this.syncRequests = new Map();
this.conflicts = new Map();
this.collaborativeInsights = new Map();
// Register self
this.registerAgent({
id: this.agentId,
name: agentIdentity.name || "DocuMCP Agent",
version: agentIdentity.version || "1.0.0",
capabilities: agentIdentity.capabilities || [
"analysis",
"recommendation",
"deployment",
],
lastSeen: new Date().toISOString(),
trustLevel: "trusted",
specializations: agentIdentity.specializations || [],
});
}
/**
* Initialize multi-agent sharing
*/
async initialize(): Promise<void> {
await this.loadSharedMemories();
await this.loadKnownAgents();
await this.loadPendingSyncRequests();
// Start periodic sync
this.startPeriodicSync();
this.emit("initialized", { agentId: this.agentId });
}
/**
* Register a new agent in the network
*/
async registerAgent(agent: AgentIdentity): Promise<void> {
this.knownAgents.set(agent.id, {
...agent,
lastSeen: new Date().toISOString(),
});
await this.persistAgentRegistry();
this.emit("agent-registered", agent);
}
/**
* Share a memory entry with other agents
*/
async shareMemory(
memoryId: string,
targetAgents?: string[],
options?: {
anonymize?: boolean;
requireValidation?: boolean;
trustLevel?: number;
},
): Promise<SharedMemory> {
const memory = await this.memoryManager.recall(memoryId);
if (!memory) {
throw new Error(`Memory ${memoryId} not found`);
}
// Create shared memory wrapper
const sharedMemory: SharedMemory = {
originalEntry: this.anonymizeIfRequired(memory, options?.anonymize),
sharingMetadata: {
sourceAgent: this.agentId,
sharedAt: new Date().toISOString(),
accessCount: 0,
trustScore: this.calculateInitialTrustScore(memory),
validatedBy: [],
conflicts: [],
},
transformations: [],
};
// Apply anonymization transformation if required
if (options?.anonymize) {
sharedMemory.transformations.push({
agentId: this.agentId,
transformationType: "anonymization",
appliedAt: new Date().toISOString(),
details: { level: "standard", preserveStructure: true },
});
}
this.sharedMemories.set(memoryId, sharedMemory);
// Create sync requests for target agents
if (targetAgents) {
for (const targetAgent of targetAgents) {
await this.createSyncRequest(targetAgent, "selective", {
memoryIds: [memoryId],
});
}
} else {
// Broadcast to all trusted agents
await this.broadcastToTrustedAgents(sharedMemory);
}
await this.persistSharedMemories();
this.emit("memory-shared", { memoryId, sharedMemory });
return sharedMemory;
}
/**
* Receive shared memory from another agent
*/
async receiveSharedMemory(
sharedMemory: SharedMemory,
sourceAgent: string,
): Promise<{
accepted: boolean;
conflicts?: ConflictResolution[];
integrationResult?: string;
}> {
// Validate source agent trust level
const sourceAgentInfo = this.knownAgents.get(sourceAgent);
if (!sourceAgentInfo || sourceAgentInfo.trustLevel === "untrusted") {
return { accepted: false };
}
// Check for conflicts with existing memories
const conflicts = await this.detectConflicts(sharedMemory);
if (conflicts.length > 0) {
// Store conflicts for resolution
for (const conflict of conflicts) {
this.conflicts.set(conflict.conflictId, conflict);
await this.resolveConflict(conflict);
}
this.emit("conflict-detected", { conflicts, sharedMemory });
}
// Integrate the shared memory
const integrationResult = await this.integrateSharedMemory(
sharedMemory,
sourceAgent,
);
// Update sharing metadata
sharedMemory.sharingMetadata.accessCount++;
this.emit("memory-received", {
sharedMemory,
sourceAgent,
integrationResult,
});
return {
accepted: true,
conflicts: conflicts.length > 0 ? conflicts : undefined,
integrationResult,
};
}
/**
* Request synchronization with another agent
*/
async requestSync(
targetAgent: string,
syncType: SyncRequest["requestType"] = "incremental",
criteria?: SyncRequest["criteria"],
): Promise<SyncRequest> {
const syncRequest: SyncRequest = {
id: this.generateSyncId(),
fromAgent: this.agentId,
toAgent: targetAgent,
requestType: syncType,
criteria,
requestedAt: new Date().toISOString(),
status: "pending",
};
this.syncRequests.set(syncRequest.id, syncRequest);
await this.persistSyncRequests();
this.emit("sync-requested", syncRequest);
return syncRequest;
}
/**
* Process incoming sync request
*/
async processSyncRequest(syncRequest: SyncRequest): Promise<{
approved: boolean;
memories?: SharedMemory[];
reason?: string;
}> {
// Validate requesting agent
const requestingAgent = this.knownAgents.get(syncRequest.fromAgent);
if (!requestingAgent || requestingAgent.trustLevel === "untrusted") {
return { approved: false, reason: "Untrusted agent" };
}
// Update request status
syncRequest.status = "in_progress";
this.syncRequests.set(syncRequest.id, syncRequest);
try {
// Get memories based on sync type and criteria
const memories = await this.getMemoriesForSync(syncRequest);
syncRequest.status = "completed";
this.emit("sync-completed", {
syncRequest,
memoriesCount: memories.length,
});
return { approved: true, memories };
} catch (error) {
syncRequest.status = "failed";
this.emit("sync-failed", { syncRequest, error });
return {
approved: false,
reason: error instanceof Error ? error.message : "Unknown error",
};
}
}
/**
* Generate collaborative insights from shared memories
*/
async generateCollaborativeInsights(): Promise<CollaborativeInsight[]> {
const insights: CollaborativeInsight[] = [];
// Analyze trends across agents
const trends = await this.analyzeTrends();
insights.push(...trends);
// Find consensus patterns
const consensus = await this.findConsensusPatterns();
insights.push(...consensus);
// Identify disagreements that need attention
const disagreements = await this.identifyDisagreements();
insights.push(...disagreements);
// Detect anomalies
const anomalies = await this.detectAnomalies();
insights.push(...anomalies);
// Store insights
for (const insight of insights) {
this.collaborativeInsights.set(insight.id, insight);
}
await this.persistCollaborativeInsights();
this.emit("insights-generated", { count: insights.length });
return insights;
}
/**
* Validate shared memory against local knowledge
*/
async validateSharedMemory(sharedMemory: SharedMemory): Promise<{
isValid: boolean;
confidence: number;
issues: string[];
recommendations: string[];
}> {
const issues: string[] = [];
const recommendations: string[] = [];
let confidence = 1.0;
// Check data consistency
if (!this.validateDataStructure(sharedMemory.originalEntry)) {
issues.push("Invalid data structure");
confidence *= 0.7;
}
// Cross-validate with local memories
const similarMemories = await this.findSimilarLocalMemories(
sharedMemory.originalEntry,
);
if (similarMemories.length > 0) {
const consistencyScore = this.calculateConsistency(
sharedMemory.originalEntry,
similarMemories,
);
if (consistencyScore < 0.8) {
issues.push("Inconsistent with local knowledge");
confidence *= consistencyScore;
}
}
// Check source agent reliability
const sourceAgent = this.knownAgents.get(
sharedMemory.sharingMetadata.sourceAgent,
);
if (sourceAgent) {
if (sourceAgent.trustLevel === "low") {
confidence *= 0.8;
recommendations.push("Verify with additional sources");
} else if (
sourceAgent.trustLevel === "high" ||
sourceAgent.trustLevel === "trusted"
) {
confidence *= 1.1;
}
}
// Validate transformations
for (const transformation of sharedMemory.transformations) {
if (transformation.transformationType === "anonymization") {
// Check if anonymization preserved essential information
if (!this.validateAnonymization(transformation)) {
issues.push("Anonymization may have removed critical information");
confidence *= 0.9;
}
}
}
return {
isValid: issues.length === 0 || confidence > 0.6,
confidence: Math.min(confidence, 1.0),
issues,
recommendations,
};
}
/**
* Get network statistics
*/
getNetworkStatistics(): {
connectedAgents: number;
sharedMemories: number;
activeSyncs: number;
resolvedConflicts: number;
trustDistribution: Record<string, number>;
collaborativeInsights: number;
networkHealth: number;
} {
const trustDistribution: Record<string, number> = {};
for (const agent of this.knownAgents.values()) {
trustDistribution[agent.trustLevel] =
(trustDistribution[agent.trustLevel] || 0) + 1;
}
const activeSyncs = Array.from(this.syncRequests.values()).filter(
(req) => req.status === "pending" || req.status === "in_progress",
).length;
const resolvedConflicts = Array.from(this.conflicts.values()).filter(
(conflict) => conflict.resolvedAt,
).length;
// Calculate network health (0-1)
const trustedAgents = Array.from(this.knownAgents.values()).filter(
(agent) => agent.trustLevel === "high" || agent.trustLevel === "trusted",
).length;
const totalAgents = this.knownAgents.size;
const networkHealth = totalAgents > 0 ? trustedAgents / totalAgents : 0;
return {
connectedAgents: this.knownAgents.size,
sharedMemories: this.sharedMemories.size,
activeSyncs,
resolvedConflicts,
trustDistribution,
collaborativeInsights: this.collaborativeInsights.size,
networkHealth,
};
}
// Private helper methods
private generateAgentId(): string {
return `agent_${crypto.randomBytes(8).toString("hex")}`;
}
private generateSyncId(): string {
return `sync_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`;
}
private anonymizeIfRequired(
memory: MemoryEntry,
anonymize?: boolean,
): MemoryEntry {
if (!anonymize) return memory;
// Create anonymized copy
const anonymized = JSON.parse(JSON.stringify(memory));
// Remove/hash sensitive information
if (anonymized.metadata.repository) {
anonymized.metadata.repository = this.hashSensitiveData(
anonymized.metadata.repository,
);
}
if (anonymized.metadata.projectId) {
anonymized.metadata.projectId = this.hashSensitiveData(
anonymized.metadata.projectId,
);
}
// Remove file paths and specific identifiers
if (anonymized.data.files) {
delete anonymized.data.files;
}
return anonymized;
}
private hashSensitiveData(data: string): string {
return crypto
.createHash("sha256")
.update(data)
.digest("hex")
.substring(0, 16);
}
private calculateInitialTrustScore(memory: MemoryEntry): number {
let score = 0.5; // Base score
// Boost for successful outcomes
if (memory.data.status === "success") score += 0.2;
// Boost for rich metadata
if (memory.metadata.tags && memory.metadata.tags.length > 2) score += 0.1;
// Boost for recent data
const daysSince =
(Date.now() - new Date(memory.timestamp).getTime()) /
(1000 * 60 * 60 * 24);
if (daysSince <= 30) score += 0.1;
// Boost for complete data
if (memory.checksum) score += 0.1;
return Math.min(score, 1.0);
}
private async detectConflicts(
sharedMemory: SharedMemory,
): Promise<ConflictResolution[]> {
const conflicts: ConflictResolution[] = [];
// Check for duplicates
const similarLocal = await this.findSimilarLocalMemories(
sharedMemory.originalEntry,
);
for (const similar of similarLocal) {
if (this.isLikelyDuplicate(sharedMemory.originalEntry, similar)) {
conflicts.push({
conflictId: `conflict_${Date.now()}_${crypto
.randomBytes(4)
.toString("hex")}`,
conflictType: "duplicate",
involvedEntries: [sharedMemory.originalEntry.id, similar.id],
involvedAgents: [
sharedMemory.sharingMetadata.sourceAgent,
this.agentId,
],
resolutionStrategy: "merge",
});
}
}
return conflicts;
}
private async resolveConflict(conflict: ConflictResolution): Promise<void> {
// Implement conflict resolution based on strategy
switch (conflict.resolutionStrategy) {
case "merge":
await this.mergeConflictingEntries(conflict);
break;
case "prioritize_trusted":
await this.prioritizeTrustedSource(conflict);
break;
case "temporal_precedence":
await this.useTemporalPrecedence(conflict);
break;
default:
// Mark for manual review
conflict.resolutionStrategy = "manual_review";
}
conflict.resolvedAt = new Date().toISOString();
this.conflicts.set(conflict.conflictId, conflict);
}
private async integrateSharedMemory(
sharedMemory: SharedMemory,
sourceAgent: string,
): Promise<string> {
// Add transformation for integration
sharedMemory.transformations.push({
agentId: this.agentId,
transformationType: "enrichment",
appliedAt: new Date().toISOString(),
details: { integratedFrom: sourceAgent },
});
// Store in local memory with special metadata
const enrichedEntry = {
...sharedMemory.originalEntry,
metadata: {
...sharedMemory.originalEntry.metadata,
sharedFrom: sourceAgent,
integratedAt: new Date().toISOString(),
tags: [
...(sharedMemory.originalEntry.metadata.tags || []),
"shared",
"collaborative",
],
},
};
await this.memoryManager.remember(
enrichedEntry.type,
enrichedEntry.data,
enrichedEntry.metadata,
);
return "integrated_successfully";
}
private async getMemoriesForSync(
syncRequest: SyncRequest,
): Promise<SharedMemory[]> {
const allMemories = await this.memoryManager.search("", {
sortBy: "timestamp",
});
let filteredMemories = allMemories;
// Apply criteria filtering
if (syncRequest.criteria) {
if (syncRequest.criteria.types) {
filteredMemories = filteredMemories.filter((m) =>
syncRequest.criteria!.types!.includes(m.type),
);
}
if (syncRequest.criteria.tags) {
filteredMemories = filteredMemories.filter(
(m) =>
m.metadata.tags?.some((tag) =>
syncRequest.criteria!.tags!.includes(tag),
),
);
}
if (syncRequest.criteria.timeRange) {
const start = new Date(syncRequest.criteria.timeRange.start);
const end = new Date(syncRequest.criteria.timeRange.end);
filteredMemories = filteredMemories.filter((m) => {
const memTime = new Date(m.timestamp);
return memTime >= start && memTime <= end;
});
}
}
// Convert to shared memories
return filteredMemories.map((memory) => ({
originalEntry: memory,
sharingMetadata: {
sourceAgent: this.agentId,
sharedAt: new Date().toISOString(),
accessCount: 0,
trustScore: this.calculateInitialTrustScore(memory),
validatedBy: [],
conflicts: [],
},
transformations: [],
}));
}
private async analyzeTrends(): Promise<CollaborativeInsight[]> {
// Analyze shared memories to identify trends
return []; // Placeholder implementation
}
private async findConsensusPatterns(): Promise<CollaborativeInsight[]> {
// Find patterns where multiple agents agree
return []; // Placeholder implementation
}
private async identifyDisagreements(): Promise<CollaborativeInsight[]> {
// Find areas where agents disagree
return []; // Placeholder implementation
}
private async detectAnomalies(): Promise<CollaborativeInsight[]> {
// Detect unusual patterns in shared data
return []; // Placeholder implementation
}
private validateDataStructure(entry: MemoryEntry): boolean {
return Boolean(entry.id && entry.timestamp && entry.type && entry.data);
}
private async findSimilarLocalMemories(
entry: MemoryEntry,
): Promise<MemoryEntry[]> {
// Find similar memories in local storage
return this.memoryManager.search(entry.metadata.projectId || "", {
sortBy: "timestamp",
});
}
private calculateConsistency(
_entry: MemoryEntry,
_similar: MemoryEntry[],
): number {
// Calculate consistency score (placeholder)
return 0.8;
}
private validateAnonymization(_transformation: any): boolean {
// Validate anonymization transformation (placeholder)
return true;
}
private isLikelyDuplicate(entry1: MemoryEntry, entry2: MemoryEntry): boolean {
// Simple duplicate detection
return (
entry1.type === entry2.type &&
entry1.metadata.projectId === entry2.metadata.projectId &&
Math.abs(
new Date(entry1.timestamp).getTime() -
new Date(entry2.timestamp).getTime(),
) < 60000
); // 1 minute
}
private async mergeConflictingEntries(
_conflict: ConflictResolution,
): Promise<void> {
// Merge conflicting entries (placeholder)
}
private async prioritizeTrustedSource(
_conflict: ConflictResolution,
): Promise<void> {
// Prioritize trusted source (placeholder)
}
private async useTemporalPrecedence(
_conflict: ConflictResolution,
): Promise<void> {
// Use temporal precedence (placeholder)
}
private async broadcastToTrustedAgents(
_sharedMemory: SharedMemory,
): Promise<void> {
// Broadcast to trusted agents (placeholder)
}
private startPeriodicSync(): void {
this.syncInterval = setInterval(
async () => {
await this.performPeriodicSync();
},
5 * 60 * 1000,
); // Every 5 minutes
}
private async performPeriodicSync(): Promise<void> {
// Perform periodic synchronization with trusted agents
}
private async loadSharedMemories(): Promise<void> {
// Load shared memories from persistence
}
private async loadKnownAgents(): Promise<void> {
// Load known agents from persistence
}
private async loadPendingSyncRequests(): Promise<void> {
// Load pending sync requests from persistence
}
private async persistSharedMemories(): Promise<void> {
// Persist shared memories
}
private async persistAgentRegistry(): Promise<void> {
// Persist agent registry
}
private async persistSyncRequests(): Promise<void> {
// Persist sync requests
}
private async persistCollaborativeInsights(): Promise<void> {
// Persist collaborative insights
}
private async createSyncRequest(
_targetAgent: string,
_type: SyncRequest["requestType"],
_options: any,
): Promise<void> {
// Create sync request (placeholder)
}
/**
* Cleanup and shutdown
*/
async shutdown(): Promise<void> {
if (this.syncInterval) {
clearInterval(this.syncInterval);
}
await this.persistSharedMemories();
await this.persistAgentRegistry();
await this.persistSyncRequests();
await this.persistCollaborativeInsights();
this.emit("shutdown", { agentId: this.agentId });
}
}
export default MultiAgentMemorySharing;
```
--------------------------------------------------------------------------------
/docs/how-to/usage-examples.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.957Z"
last_validated: "2025-11-20T00:46:21.957Z"
auto_updated: false
update_frequency: monthly
---
# DocuMCP Usage Examples
This guide provides comprehensive usage examples for DocuMCP functions, organized by common use cases and scenarios.
## 🎯 Repository Analysis Examples
### Basic Repository Analysis
```typescript
import { analyzeRepository } from "./dist/tools/analyze-repository.js";
// Analyze a simple project
const analysis = await analyzeRepository({
path: "/path/to/my-project",
depth: "standard",
});
console.log(`Found ${analysis.data.structure.totalFiles} files`);
console.log(
`Primary language: ${analysis.data.recommendations.primaryLanguage}`,
);
```
### Advanced Repository Analysis
```typescript
// Deep analysis with historical context
const deepAnalysis = await analyzeRepository({
path: "/path/to/complex-project",
depth: "deep",
});
// Access detailed information
const { structure, dependencies, documentation } = deepAnalysis.data;
console.log(`Languages: ${Object.keys(structure.languages).join(", ")}`);
console.log(`Has tests: ${structure.hasTests}`);
console.log(`Has CI: ${structure.hasCI}`);
console.log(`Documentation complexity: ${documentation.estimatedComplexity}`);
```
## 🔧 SSG Recommendation Examples
### Basic SSG Recommendation
```typescript
import { recommendSSG } from "./dist/tools/recommend-ssg.js";
// Get recommendation based on analysis
const recommendation = await recommendSSG({
analysisId: "analysis_abc123_def456",
userId: "developer123",
});
console.log(`Recommended SSG: ${recommendation.data.recommended}`);
console.log(`Confidence: ${recommendation.data.confidence * 100}%`);
console.log(`Reasoning: ${recommendation.data.reasoning.join(", ")}`);
```
### Personalized SSG Recommendation
```typescript
// With user preferences
const personalized = await recommendSSG({
analysisId: "analysis_abc123_def456",
userId: "developer123",
preferences: {
priority: "performance",
ecosystem: "javascript",
},
});
// Compare alternatives
recommendation.data.alternatives.forEach((alt) => {
console.log(`${alt.name}: ${alt.score} (${alt.pros.join(", ")})`);
});
```
### Enterprise SSG Recommendation
```typescript
// Enterprise-focused recommendation
const enterprise = await recommendSSG({
analysisId: "analysis_abc123_def456",
userId: "enterprise_user",
preferences: {
priority: "simplicity",
ecosystem: "any",
},
});
// Check historical data
if (recommendation.data.historicalData) {
const { similarProjectCount, successRates } =
recommendation.data.historicalData;
console.log(`Based on ${similarProjectCount} similar projects`);
console.log(`Success rates: ${JSON.stringify(successRates, null, 2)}`);
}
```
## 📁 Documentation Structure Examples
### Basic Structure Setup
```typescript
import { setupStructure } from "./dist/tools/setup-structure.js";
// Set up Docusaurus structure with Diataxis framework
const structure = await setupStructure({
path: "./docs",
ssg: "docusaurus",
includeExamples: true,
});
console.log(`Created ${structure.data.directoriesCreated.length} directories`);
console.log(`Created ${structure.data.filesCreated.length} files`);
console.log(
`Diataxis structure: ${Object.keys(structure.data.diataxisStructure).join(
", ",
)}`,
);
```
### Minimal Structure Setup
```typescript
// Minimal structure for existing projects
const minimal = await setupStructure({
path: "./site",
ssg: "hugo",
includeExamples: false,
});
// Check what was created
minimal.data.directoriesCreated.forEach((dir) => {
console.log(`Created directory: ${dir}`);
});
```
### Custom Structure Setup
```typescript
// Custom structure with specific categories
const custom = await setupStructure({
path: "./custom-docs",
ssg: "mkdocs",
includeExamples: true,
});
// Access structure details
const { diataxisStructure, ssgSpecificFiles } = custom.data;
console.log(
`Diataxis categories: ${Object.keys(diataxisStructure).join(", ")}`,
);
```
## ⚙️ Configuration Generation Examples
### Docusaurus Configuration
```typescript
import { generateConfig } from "documcp";
// Generate Docusaurus configuration
const config = await generateConfig({
ssg: "docusaurus",
projectName: "My Awesome Project",
projectDescription: "A comprehensive documentation project",
outputPath: "./docs",
});
console.log(`Generated ${config.data.filesCreated.length} configuration files`);
config.data.filesCreated.forEach((file) => {
console.log(`Created: ${file}`);
});
```
### Hugo Configuration
```typescript
// Generate Hugo configuration
const hugoConfig = await generateConfig({
ssg: "hugo",
projectName: "My Hugo Site",
outputPath: "./site",
});
// Check configuration details
const { ssg, projectName, filesCreated } = hugoConfig.data;
console.log(`Generated ${ssg} configuration for ${projectName}`);
```
### Multi-SSG Configuration
```typescript
// Generate configurations for multiple SSGs
const ssgs = ["docusaurus", "hugo", "mkdocs"];
for (const ssg of ssgs) {
const config = await generateConfig({
ssg: ssg as any,
projectName: "Multi-SSG Project",
outputPath: `./docs-${ssg}`,
});
console.log(
`Generated ${ssg} configuration: ${config.data.filesCreated.length} files`,
);
}
```
## 📝 Content Population Examples
### Basic Content Population
```typescript
import { handlePopulateDiataxisContent } from "documcp";
// Populate documentation content
const population = await handlePopulateDiataxisContent({
analysisId: "analysis_abc123_def456",
docsPath: "./docs",
populationLevel: "comprehensive",
});
console.log(
`Generated ${population.data.contentGenerated.length} content files`,
);
console.log(
`Extracted ${population.data.contentExtracted.length} content pieces`,
);
```
### Focused Content Population
```typescript
// Populate with specific focus areas
const focused = await handlePopulateDiataxisContent({
analysisId: "analysis_abc123_def456",
docsPath: "./docs",
populationLevel: "intelligent",
focusAreas: ["api", "examples", "tutorials"],
});
// Check what was generated
focused.data.contentGenerated.forEach((content) => {
console.log(`Generated: ${content.category}/${content.filename}`);
});
```
### Technology-Focused Population
```typescript
// Populate with technology focus
const techFocused = await handlePopulateDiataxisContent({
analysisId: "analysis_abc123_def456",
docsPath: "./docs",
technologyFocus: ["React", "TypeScript", "Node.js"],
});
// Access generated content
techFocused.data.contentGenerated.forEach((content) => {
if (content.technology) {
console.log(
`Technology-specific content: ${content.filename} (${content.technology})`,
);
}
});
```
## 🚀 Deployment Examples
### Basic GitHub Pages Deployment
```typescript
import { handleDeployPages } from "documcp";
// Deploy to GitHub Pages
const deployment = await handleDeployPages({
repository: "user/repository",
ssg: "docusaurus",
});
console.log(`Deployment URL: ${deployment.data.url}`);
console.log(`Status: ${deployment.data.status}`);
```
### Custom Domain Deployment
```typescript
// Deploy with custom domain
const customDomain = await handleDeployPages({
repository: "user/repository",
ssg: "docusaurus",
customDomain: "docs.example.com",
});
// Check deployment details
const { url, status, configuration } = customDomain.data;
console.log(`Deployed to: ${url}`);
console.log(`Custom domain: ${configuration.customDomain}`);
```
### Branch-Specific Deployment
```typescript
// Deploy to specific branch
const branchDeployment = await handleDeployPages({
repository: "user/repository",
ssg: "docusaurus",
branch: "gh-pages",
});
// Monitor deployment
if (branchDeployment.data.status === "success") {
console.log(`Successfully deployed to ${branchDeployment.data.branch}`);
} else {
console.log(`Deployment failed: ${branchDeployment.data.error}`);
}
```
## 🧠 Memory System Examples
### Memory Initialization
```typescript
import { initializeMemory } from "documcp/memory";
// Initialize memory system
const memory = await initializeMemory("./custom-memory-storage");
console.log("Memory system initialized");
console.log(`Storage directory: ${memory.storageDir}`);
```
### Storing Analysis Data
```typescript
import { rememberAnalysis } from "documcp/memory";
// Store analysis data
const memoryId = await rememberAnalysis("/path/to/project", {
id: "analysis_123",
structure: { totalFiles: 150, languages: { ".ts": 100 } },
dependencies: { ecosystem: "javascript", packages: ["react"] },
});
console.log(`Stored analysis with ID: ${memoryId}`);
```
### Retrieving Project Insights
```typescript
import { getProjectInsights } from "documcp/memory";
// Get project insights
const insights = await getProjectInsights("project_abc123");
insights.forEach((insight) => {
console.log(`💡 ${insight}`);
});
```
### Finding Similar Projects
```typescript
import { getSimilarProjects } from "documcp/memory";
// Find similar projects
const similar = await getSimilarProjects(analysisData, 5);
console.log(`Found ${similar.length} similar projects:`);
similar.forEach((project) => {
console.log(
`- ${project.metadata.projectId} (${project.similarity}% similar)`,
);
});
```
## 📊 README Analysis Examples
### Basic README Analysis
```typescript
import { analyzeReadme } from "documcp";
// Analyze README
const analysis = await analyzeReadme({
project_path: "/path/to/project",
});
const { analysis: readmeAnalysis } = analysis.data;
console.log(`README Score: ${readmeAnalysis.overallScore}/100`);
console.log(
`Current length: ${readmeAnalysis.lengthAnalysis.currentLines} lines`,
);
console.log(
`Target length: ${readmeAnalysis.lengthAnalysis.targetLines} lines`,
);
```
### Community-Focused Analysis
```typescript
// Analyze for community contributors
const communityAnalysis = await analyzeReadme({
project_path: "/path/to/project",
target_audience: "community_contributors",
optimization_level: "moderate",
});
const { communityReadiness } = communityAnalysis.data.analysis;
console.log(`Has contributing guide: ${communityReadiness.hasContributing}`);
console.log(`Has code of conduct: ${communityReadiness.hasCodeOfConduct}`);
console.log(`Badge count: ${communityReadiness.badgeCount}`);
```
### Enterprise README Analysis
```typescript
// Analyze for enterprise users
const enterpriseAnalysis = await analyzeReadme({
project_path: "/path/to/project",
target_audience: "enterprise_users",
optimization_level: "aggressive",
max_length_target: 200,
});
const { optimizationOpportunities } = enterpriseAnalysis.data.analysis;
optimizationOpportunities.forEach((opportunity) => {
console.log(
`${opportunity.type}: ${opportunity.description} (${opportunity.priority})`,
);
});
```
## 🔧 README Optimization Examples
### Basic README Optimization
```typescript
import { optimizeReadme } from "documcp";
// Optimize README
const optimization = await optimizeReadme({
readme_path: "./README.md",
strategy: "community_focused",
});
const { optimization: result } = optimization.data;
console.log(
`Reduced from ${result.originalLength} to ${result.optimizedLength} lines`,
);
console.log(`Reduction: ${result.reductionPercentage}%`);
```
### Enterprise README Optimization
```typescript
// Optimize for enterprise
const enterpriseOptimization = await optimizeReadme({
readme_path: "./README.md",
strategy: "enterprise_focused",
max_length: 200,
preserve_existing: true,
});
// Check restructuring changes
enterpriseOptimization.data.optimization.restructuringChanges.forEach(
(change) => {
console.log(`${change.type}: ${change.section} - ${change.description}`);
},
);
```
### README Template Generation
```typescript
import { generateReadmeTemplate } from "documcp";
// Generate README template
const template = await generateReadmeTemplate({
projectName: "MyAwesomeProject",
description: "A powerful utility library",
templateType: "library",
author: "Your Name",
license: "MIT",
includeBadges: true,
outputPath: "./README.md",
});
console.log(`Generated ${template.metadata.estimatedLength} line README`);
console.log(`Sections included: ${template.metadata.sectionsIncluded}`);
```
## 🔗 Link Checking Examples
### Basic Link Checking
```typescript
import { checkDocumentationLinks } from "documcp";
// Check documentation links
const linkCheck = await checkDocumentationLinks({
documentation_path: "./docs",
check_external_links: true,
check_internal_links: true,
check_anchor_links: true,
});
console.log(`Total links checked: ${linkCheck.data.totalLinks}`);
console.log(`Broken links: ${linkCheck.data.brokenLinks}`);
console.log(`Success rate: ${linkCheck.data.successRate}%`);
```
### Comprehensive Link Checking
```typescript
// Comprehensive link checking
const comprehensive = await checkDocumentationLinks({
documentation_path: "./docs",
check_external_links: true,
check_internal_links: true,
check_anchor_links: true,
timeout_ms: 10000,
max_concurrent_checks: 10,
fail_on_broken_links: false,
});
// Check specific link types
comprehensive.data.linkTypes.forEach((linkType) => {
console.log(
`${linkType.type}: ${linkType.total} total, ${linkType.broken} broken`,
);
});
```
## 🧪 Local Testing Examples
### Local Documentation Testing
```typescript
import { handleTestLocalDeployment } from "documcp";
// Test local deployment
const test = await handleTestLocalDeployment({
repositoryPath: "/path/to/project",
ssg: "docusaurus",
port: 3000,
timeout: 60,
});
console.log(`Test status: ${test.data.status}`);
console.log(`Local URL: ${test.data.localUrl}`);
console.log(`Build time: ${test.data.buildTime}ms`);
```
### Docker Testing
```typescript
// Test with Docker
const dockerTest = await handleTestLocalDeployment({
repositoryPath: "/path/to/project",
ssg: "hugo",
port: 8080,
timeout: 120,
});
if (dockerTest.data.status === "success") {
console.log(`Docker test successful: ${dockerTest.data.localUrl}`);
} else {
console.log(`Docker test failed: ${dockerTest.data.error}`);
}
```
## 🔄 Workflow Examples
### Complete Documentation Workflow
```typescript
// Complete workflow from analysis to deployment
async function completeDocumentationWorkflow(projectPath: string) {
try {
// 1. Analyze repository
const analysis = await analyzeRepository({
path: projectPath,
depth: "standard",
});
const analysisId = analysis.data.id;
console.log(`Analysis complete: ${analysisId}`);
// 2. Get SSG recommendation
const recommendation = await recommendSSG({
analysisId: analysisId,
userId: "developer123",
});
const ssg = recommendation.data.recommended;
console.log(`Recommended SSG: ${ssg}`);
// 3. Set up structure
const structure = await setupStructure({
path: "./docs",
ssg: ssg,
includeExamples: true,
});
console.log(
`Structure created: ${structure.data.directoriesCreated.length} directories`,
);
// 4. Generate configuration
const config = await generateConfig({
ssg: ssg,
projectName: "My Project",
outputPath: "./docs",
});
console.log(
`Configuration generated: ${config.data.filesCreated.length} files`,
);
// 5. Populate content
const content = await handlePopulateDiataxisContent({
analysisId: analysisId,
docsPath: "./docs",
populationLevel: "comprehensive",
});
console.log(
`Content populated: ${content.data.contentGenerated.length} files`,
);
// 6. Deploy to GitHub Pages
const deployment = await handleDeployPages({
repository: "user/repository",
ssg: ssg,
});
console.log(`Deployed to: ${deployment.data.url}`);
return {
analysisId,
ssg,
deploymentUrl: deployment.data.url,
};
} catch (error) {
console.error("Workflow failed:", error);
throw error;
}
}
// Usage
completeDocumentationWorkflow("/path/to/project")
.then((result) => {
console.log("Workflow completed successfully:", result);
})
.catch((error) => {
console.error("Workflow failed:", error);
});
```
### Batch Processing Example
```typescript
// Process multiple repositories
async function batchProcessRepositories(repositories: string[]) {
const results = [];
for (const repoPath of repositories) {
try {
console.log(`Processing: ${repoPath}`);
const analysis = await analyzeRepository({
path: repoPath,
depth: "quick",
});
const recommendation = await recommendSSG({
analysisId: analysis.data.id,
});
results.push({
path: repoPath,
analysisId: analysis.data.id,
recommendedSSG: recommendation.data.recommended,
confidence: recommendation.data.confidence,
});
} catch (error) {
console.error(`Failed to process ${repoPath}:`, error);
results.push({
path: repoPath,
error: error.message,
});
}
}
return results;
}
// Usage
const repositories = [
"/path/to/project1",
"/path/to/project2",
"/path/to/project3",
];
batchProcessRepositories(repositories).then((results) => {
console.log("Batch processing complete:", results);
});
```
## 🎯 Error Handling Examples
### Comprehensive Error Handling
```typescript
async function robustAnalysis(projectPath: string) {
try {
const analysis = await analyzeRepository({
path: projectPath,
depth: "standard",
});
if (analysis.success) {
return analysis.data;
} else {
throw new Error(analysis.error.message);
}
} catch (error) {
if (error.code === "EACCES") {
console.error("Permission denied. Check file permissions.");
} else if (error.code === "ENOENT") {
console.error("Directory not found. Check the path.");
} else if (error.message.includes("analysis failed")) {
console.error("Analysis failed. Check repository structure.");
} else {
console.error("Unexpected error:", error);
}
throw error;
}
}
```
### Retry Logic Example
```typescript
async function retryAnalysis(projectPath: string, maxRetries: number = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`Attempt ${attempt} of ${maxRetries}`);
const analysis = await analyzeRepository({
path: projectPath,
depth: "standard",
});
return analysis.data;
} catch (error) {
lastError = error;
console.log(`Attempt ${attempt} failed:`, error.message);
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
console.log(`Retrying in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
throw new Error(
`Analysis failed after ${maxRetries} attempts: ${lastError.message}`,
);
}
```
## 🔌 MCP Integration Examples
### Complete MCP Workflow
```typescript
// Real-world example: Complete documentation deployment via MCP
import { analyzeRepository } from "./dist/tools/analyze-repository.js";
import { recommendSSG } from "./dist/tools/recommend-ssg.js";
import { generateConfig } from "./dist/tools/generate-config.js";
import { setupStructure } from "./dist/tools/setup-structure.js";
import { deployPages } from "./dist/tools/deploy-pages.js";
async function completeMCPWorkflow(projectPath: string, githubRepo: string) {
console.log("🔍 Starting DocuMCP workflow...");
// Step 1: Analyze repository
const analysis = await analyzeRepository({
path: projectPath,
depth: "comprehensive",
});
console.log(
`📊 Analysis complete: ${analysis.data.structure.totalFiles} files analyzed`,
);
console.log(`🏗️ Project type: ${analysis.data.recommendations.projectType}`);
console.log(
`💻 Primary language: ${analysis.data.recommendations.primaryLanguage}`,
);
// Step 2: Get SSG recommendation
const ssgRecommendation = await recommendSSG({
analysisId: analysis.id,
preferences: {
ecosystem: "javascript",
priority: "features",
},
});
console.log(`🎯 Recommended SSG: ${ssgRecommendation.data.recommended}`);
console.log(
`✅ Confidence: ${Math.round(ssgRecommendation.data.confidence * 100)}%`,
);
// Step 3: Generate configuration
const config = await generateConfig({
ssg: ssgRecommendation.data.recommended,
projectName: analysis.data.structure.projectName,
outputPath: "./docs",
});
console.log(
`⚙️ Configuration generated: ${config.data.filesGenerated.length} files`,
);
// Step 4: Setup Diataxis structure
const structure = await setupStructure({
path: "./docs",
ssg: ssgRecommendation.data.recommended,
includeExamples: true,
});
console.log(
`📁 Structure created: ${structure.data.directoriesCreated.length} directories`,
);
console.log(`📄 Files created: ${structure.data.filesCreated.length} files`);
// Step 5: Deploy to GitHub Pages
const deployment = await deployPages({
repository: githubRepo,
ssg: ssgRecommendation.data.recommended,
projectPath: projectPath,
});
console.log(`🚀 Deployment initiated: ${deployment.data.workflowUrl}`);
console.log(`🌐 Site URL: ${deployment.data.siteUrl}`);
return {
analysis: analysis.data,
recommendation: ssgRecommendation.data,
deployment: deployment.data,
};
}
// Usage
completeMCPWorkflow("/path/to/your/project", "username/repository-name")
.then((result) => {
console.log("🎉 DocuMCP workflow completed successfully!");
console.log(`📊 Final result:`, result);
})
.catch((error) => {
console.error("❌ Workflow failed:", error.message);
});
```
### MCP Memory Integration
```typescript
// Example showing DocuMCP's memory system for learning user preferences
import { analyzeRepository } from "./dist/tools/analyze-repository.js";
import { recommendSSG } from "./dist/tools/recommend-ssg.js";
async function intelligentRecommendation(projectPath: string, userId: string) {
// DocuMCP automatically learns from previous successful deployments
const analysis = await analyzeRepository({
path: projectPath,
depth: "standard",
});
// Memory system provides personalized recommendations
const recommendation = await recommendSSG({
analysisId: analysis.id,
userId: userId, // Memory system tracks user preferences
});
console.log(
`🧠 Memory-enhanced recommendation: ${recommendation.data.recommended}`,
);
console.log(
`📈 Based on ${
recommendation.data.memoryInsights?.similarProjects || 0
} similar projects`,
);
console.log(
`🎯 Success rate: ${Math.round(
(recommendation.data.memoryInsights?.successRate || 0) * 100,
)}%`,
);
return recommendation;
}
```
These examples demonstrate the comprehensive capabilities of DocuMCP and provide practical patterns for common use cases. Use them as starting points and adapt them to your specific needs.
```
--------------------------------------------------------------------------------
/src/tools/detect-gaps.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
import { promises as fs } from "fs";
import path from "path";
import { MCPToolResponse, formatMCPResponse } from "../types/api.js";
import { analyzeRepository } from "./analyze-repository.js";
import { handleValidateDiataxisContent } from "./validate-content.js";
import { CodeScanner, CodeAnalysisResult } from "../utils/code-scanner.js";
const inputSchema = z.object({
repositoryPath: z.string().describe("Path to the repository to analyze"),
documentationPath: z
.string()
.optional()
.describe("Path to existing documentation (if any)"),
analysisId: z
.string()
.optional()
.describe("Optional existing analysis ID to reuse"),
depth: z
.enum(["quick", "standard", "comprehensive"])
.optional()
.default("standard"),
});
interface DocumentationGap {
category: "tutorials" | "how-to" | "reference" | "explanation" | "general";
gapType:
| "missing_section"
| "incomplete_content"
| "outdated_info"
| "missing_examples"
| "poor_structure";
description: string;
priority: "critical" | "high" | "medium" | "low";
recommendation: string;
suggestedContent?: string;
relatedFiles?: string[];
estimatedEffort: "minimal" | "moderate" | "substantial";
}
interface GapAnalysisResult {
repositoryPath: string;
documentationPath?: string;
analysisId: string;
overallScore: number;
gaps: DocumentationGap[];
strengths: string[];
recommendations: {
immediate: string[];
shortTerm: string[];
longTerm: string[];
};
missingStructure: {
directories: string[];
files: string[];
};
contentCoverage: {
tutorials: number;
howTo: number;
reference: number;
explanation: number;
};
}
export async function detectDocumentationGaps(
args: unknown,
): Promise<{ content: any[] }> {
const startTime = Date.now();
const {
repositoryPath,
documentationPath,
analysisId: existingAnalysisId,
depth,
} = inputSchema.parse(args);
try {
// Step 1: Get or perform repository analysis
let analysisId = existingAnalysisId;
let repositoryAnalysis: any;
if (!analysisId) {
const analysisResult = await analyzeRepository({
path: repositoryPath,
depth,
});
if (analysisResult.content && analysisResult.content[0]) {
// The analyze_repository tool returns the analysis data directly as JSON text
repositoryAnalysis = JSON.parse(analysisResult.content[0].text);
// Check if the analysis was successful
if (repositoryAnalysis.success === false) {
throw new Error("Repository analysis failed");
}
analysisId = repositoryAnalysis.id; // Use the 'id' field from the analysis
} else {
throw new Error("Repository analysis failed - no content returned");
}
} else {
// Try to retrieve existing analysis (simplified for this implementation)
// In a full implementation, this would retrieve from persistent storage
}
// Step 2: Perform deep code analysis
const codeScanner = new CodeScanner(repositoryPath);
const codeAnalysis = await codeScanner.analyzeRepository();
// Step 3: Analyze existing documentation structure
const documentationAnalysis = await analyzeExistingDocumentation(
documentationPath || path.join(repositoryPath, "docs"),
);
// Step 4: Perform content validation if documentation exists
let validationResult: any = null;
if (documentationAnalysis.exists && documentationPath) {
try {
const validation = await handleValidateDiataxisContent({
contentPath: documentationPath,
analysisId: analysisId,
validationType: "all",
includeCodeValidation: true,
confidence: "moderate",
});
if (
validation &&
(validation as any).content &&
(validation as any).content[0]
) {
const validationData = JSON.parse(
(validation as any).content[0].text,
);
if (validationData.success) {
validationResult = validationData.data;
}
}
} catch (error) {
// Validation errors are non-fatal - continue without validation data
console.warn(
"Content validation failed, continuing without validation data:",
error,
);
}
}
// Step 5: Identify gaps based on project and code analysis
const gaps = identifyDocumentationGaps(
repositoryAnalysis,
documentationAnalysis,
validationResult,
codeAnalysis,
);
// Step 6: Generate recommendations
const recommendations = generateRecommendations(
gaps,
repositoryAnalysis,
codeAnalysis,
);
// Step 7: Calculate coverage scores
const contentCoverage = calculateContentCoverage(
documentationAnalysis,
gaps,
);
const gapAnalysis: GapAnalysisResult = {
repositoryPath,
documentationPath,
analysisId: analysisId || "unknown",
overallScore: calculateOverallScore(gaps, contentCoverage),
gaps,
strengths: identifyStrengths(documentationAnalysis, validationResult),
recommendations,
missingStructure: identifyMissingStructure(documentationAnalysis),
contentCoverage,
};
const response: MCPToolResponse<typeof gapAnalysis> = {
success: true,
data: gapAnalysis,
metadata: {
toolVersion: "1.0.0",
executionTime: Date.now() - startTime,
timestamp: new Date().toISOString(),
},
recommendations: [
{
type:
gapAnalysis.overallScore < 60
? "critical"
: gapAnalysis.overallScore < 80
? "warning"
: "info",
title: "Documentation Gap Analysis Complete",
description: `Found ${gaps.length} gaps. Overall documentation score: ${gapAnalysis.overallScore}%`,
},
],
nextSteps: recommendations.immediate.map((rec) => ({
action: rec,
toolRequired: getRecommendedTool(rec),
description: rec,
priority: "high" as const,
})),
};
return formatMCPResponse(response);
} catch (error) {
const errorResponse: MCPToolResponse = {
success: false,
error: {
code: "GAP_DETECTION_FAILED",
message: `Failed to detect documentation gaps: ${error}`,
resolution: "Ensure repository and documentation paths are accessible",
},
metadata: {
toolVersion: "1.0.0",
executionTime: Date.now() - startTime,
timestamp: new Date().toISOString(),
},
};
return formatMCPResponse(errorResponse);
}
}
async function analyzeExistingDocumentation(docsPath: string) {
try {
const stats = await fs.stat(docsPath);
if (!stats.isDirectory()) {
return { exists: false, structure: {}, files: [] };
}
const structure = {
tutorials: { exists: false, files: [] as string[] },
"how-to": { exists: false, files: [] as string[] },
reference: { exists: false, files: [] as string[] },
explanation: { exists: false, files: [] as string[] },
};
const allFiles: string[] = [];
// Check for Diataxis structure
for (const [category] of Object.entries(structure)) {
const categoryPath = path.join(docsPath, category);
try {
const categoryStats = await fs.stat(categoryPath);
if (categoryStats.isDirectory()) {
structure[category as keyof typeof structure].exists = true;
const files = await fs.readdir(categoryPath);
const mdFiles = files.filter(
(f) => f.endsWith(".md") || f.endsWith(".mdx"),
);
structure[category as keyof typeof structure].files = mdFiles;
allFiles.push(...mdFiles.map((f) => path.join(category, f)));
}
} catch {
// Category doesn't exist
}
}
// Also check root level files
const rootFiles = await fs.readdir(docsPath);
const rootMdFiles = rootFiles.filter(
(f) => f.endsWith(".md") || f.endsWith(".mdx"),
);
allFiles.push(...rootMdFiles);
return {
exists: true,
structure,
files: allFiles,
hasRootIndex:
rootFiles.includes("index.md") || rootFiles.includes("README.md"),
};
} catch {
return { exists: false, structure: {}, files: [] };
}
}
function identifyDocumentationGaps(
repoAnalysis: any,
docsAnalysis: any,
validationResult: any,
codeAnalysis: CodeAnalysisResult,
): DocumentationGap[] {
const gaps: DocumentationGap[] = [];
// Check for missing Diataxis structure
if (!docsAnalysis.exists) {
gaps.push({
category: "general",
gapType: "missing_section",
description: "No documentation directory found",
priority: "critical",
recommendation:
"Create documentation structure using setup_structure tool",
estimatedEffort: "moderate",
});
return gaps; // If no docs exist, return early
}
const diataxisCategories = [
"tutorials",
"how-to",
"reference",
"explanation",
];
for (const category of diataxisCategories) {
if (!docsAnalysis.structure[category]?.exists) {
gaps.push({
category: category as any,
gapType: "missing_section",
description: `Missing ${category} documentation section`,
priority:
category === "tutorials" || category === "reference"
? "high"
: "medium",
recommendation: `Create ${category} directory and add relevant content`,
estimatedEffort: "moderate",
});
} else if (docsAnalysis.structure[category].files.length === 0) {
gaps.push({
category: category as any,
gapType: "incomplete_content",
description: `${category} section exists but has no content`,
priority: "high",
recommendation: `Add content to ${category} section using populate_diataxis_content tool`,
estimatedEffort: "substantial",
});
}
}
// Code-based gaps using actual code analysis
// Check for API documentation gaps based on actual endpoints found
if (
codeAnalysis.apiEndpoints.length > 0 &&
!hasApiDocumentation(docsAnalysis)
) {
gaps.push({
category: "reference",
gapType: "missing_section",
description: `Found ${codeAnalysis.apiEndpoints.length} API endpoints but no API documentation`,
priority: "critical",
recommendation:
"Create API reference documentation for discovered endpoints",
estimatedEffort: "substantial",
relatedFiles: [
...new Set(codeAnalysis.apiEndpoints.map((ep) => ep.filePath)),
],
});
}
// Check for undocumented API endpoints
const undocumentedEndpoints = codeAnalysis.apiEndpoints.filter(
(ep) => !ep.hasDocumentation,
);
if (undocumentedEndpoints.length > 0) {
gaps.push({
category: "reference",
gapType: "missing_examples",
description: `${undocumentedEndpoints.length} API endpoints lack inline documentation`,
priority: "high",
recommendation: "Add JSDoc comments to API endpoint handlers",
estimatedEffort: "moderate",
relatedFiles: [
...new Set(undocumentedEndpoints.map((ep) => ep.filePath)),
],
});
}
// Check for class/interface documentation
const undocumentedClasses = codeAnalysis.classes.filter(
(cls) => cls.exported && !cls.hasJSDoc,
);
if (undocumentedClasses.length > 0) {
gaps.push({
category: "reference",
gapType: "incomplete_content",
description: `${undocumentedClasses.length} exported classes lack documentation`,
priority: "medium",
recommendation:
"Add JSDoc comments to exported classes and create API reference",
estimatedEffort: "moderate",
relatedFiles: [
...new Set(undocumentedClasses.map((cls) => cls.filePath)),
],
});
}
// Check for interface documentation
const undocumentedInterfaces = codeAnalysis.interfaces.filter(
(iface) => iface.exported && !iface.hasJSDoc,
);
if (undocumentedInterfaces.length > 0) {
gaps.push({
category: "reference",
gapType: "incomplete_content",
description: `${undocumentedInterfaces.length} exported interfaces lack documentation`,
priority: "medium",
recommendation:
"Add JSDoc comments to exported interfaces and create type documentation",
estimatedEffort: "moderate",
relatedFiles: [
...new Set(undocumentedInterfaces.map((iface) => iface.filePath)),
],
});
}
// Check for function documentation
const undocumentedFunctions = codeAnalysis.functions.filter(
(func) => func.exported && !func.hasJSDoc,
);
if (undocumentedFunctions.length > 0) {
gaps.push({
category: "reference",
gapType: "incomplete_content",
description: `${undocumentedFunctions.length} exported functions lack documentation`,
priority: "medium",
recommendation:
"Add JSDoc comments to exported functions and create API reference",
estimatedEffort: "substantial",
relatedFiles: [
...new Set(undocumentedFunctions.map((func) => func.filePath)),
],
});
}
// Framework-specific documentation gaps
if (
codeAnalysis.frameworks.includes("React") &&
!hasFrameworkDocumentation(docsAnalysis, "react")
) {
gaps.push({
category: "how-to",
gapType: "missing_section",
description:
"React framework detected but no React-specific documentation found",
priority: "medium",
recommendation: "Create React component usage and development guides",
estimatedEffort: "moderate",
});
}
if (
codeAnalysis.frameworks.includes("Express") &&
!hasFrameworkDocumentation(docsAnalysis, "express")
) {
gaps.push({
category: "how-to",
gapType: "missing_section",
description:
"Express framework detected but no Express-specific documentation found",
priority: "medium",
recommendation: "Create Express server setup and API development guides",
estimatedEffort: "moderate",
});
}
// Test documentation gaps
if (codeAnalysis.hasTests && !hasTestingDocumentation(docsAnalysis)) {
gaps.push({
category: "how-to",
gapType: "missing_section",
description: "Test files found but no testing documentation",
priority: "medium",
recommendation: "Create testing setup and contribution guides",
estimatedEffort: "moderate",
relatedFiles: codeAnalysis.testFiles,
});
}
// Technology-specific gaps based on repository analysis (fallback)
if (repoAnalysis) {
// Check for setup/installation guides
if (repoAnalysis.packageManager && !hasInstallationGuide(docsAnalysis)) {
gaps.push({
category: "tutorials",
gapType: "missing_section",
description: "Package manager detected but no installation guide found",
priority: "high",
recommendation: "Create installation and setup tutorial",
estimatedEffort: "moderate",
});
}
// Check for Docker documentation
if (repoAnalysis.hasDocker && !hasDockerDocumentation(docsAnalysis)) {
gaps.push({
category: "how-to",
gapType: "missing_section",
description: "Docker configuration found but no Docker documentation",
priority: "medium",
recommendation: "Add Docker deployment and development guides",
estimatedEffort: "moderate",
});
}
// Check for CI/CD documentation
if (repoAnalysis.hasCICD && !hasCICDDocumentation(docsAnalysis)) {
gaps.push({
category: "explanation",
gapType: "missing_section",
description: "CI/CD configuration found but no related documentation",
priority: "medium",
recommendation: "Document CI/CD processes and deployment workflows",
estimatedEffort: "moderate",
});
}
}
// Add validation-based gaps
if (validationResult?.validationResults) {
for (const result of validationResult.validationResults) {
if (result.status === "fail") {
gaps.push({
category: "general",
gapType: "poor_structure",
description: result.message,
priority: "medium",
recommendation: result.recommendation || "Fix validation issue",
estimatedEffort: "minimal",
});
}
}
}
return gaps;
}
function hasApiDocumentation(docsAnalysis: any): boolean {
const allFiles = docsAnalysis.files || [];
return allFiles.some(
(file: string) =>
file.toLowerCase().includes("api") ||
file.toLowerCase().includes("endpoint") ||
file.toLowerCase().includes("swagger") ||
file.toLowerCase().includes("openapi"),
);
}
function hasInstallationGuide(docsAnalysis: any): boolean {
const allFiles = docsAnalysis.files || [];
return allFiles.some(
(file: string) =>
file.toLowerCase().includes("install") ||
file.toLowerCase().includes("setup") ||
file.toLowerCase().includes("getting-started") ||
file.toLowerCase().includes("quickstart"),
);
}
function hasDockerDocumentation(docsAnalysis: any): boolean {
const allFiles = docsAnalysis.files || [];
return allFiles.some(
(file: string) =>
file.toLowerCase().includes("docker") ||
file.toLowerCase().includes("container") ||
file.toLowerCase().includes("compose"),
);
}
function hasCICDDocumentation(docsAnalysis: any): boolean {
const allFiles = docsAnalysis.files || [];
return allFiles.some(
(file: string) =>
file.toLowerCase().includes("ci") ||
file.toLowerCase().includes("cd") ||
file.toLowerCase().includes("deploy") ||
file.toLowerCase().includes("workflow") ||
file.toLowerCase().includes("pipeline"),
);
}
function hasFrameworkDocumentation(
docsAnalysis: any,
framework: string,
): boolean {
const allFiles = docsAnalysis.files || [];
return allFiles.some(
(file: string) =>
file.toLowerCase().includes(framework.toLowerCase()) ||
file.toLowerCase().includes(`${framework.toLowerCase()}-guide`) ||
file.toLowerCase().includes(`${framework.toLowerCase()}-setup`),
);
}
function hasTestingDocumentation(docsAnalysis: any): boolean {
const allFiles = docsAnalysis.files || [];
return allFiles.some(
(file: string) =>
file.toLowerCase().includes("test") ||
file.toLowerCase().includes("testing") ||
file.toLowerCase().includes("jest") ||
file.toLowerCase().includes("spec") ||
file.toLowerCase().includes("unit-test") ||
file.toLowerCase().includes("integration-test"),
);
}
function generateRecommendations(
gaps: DocumentationGap[],
_repoAnalysis: any,
codeAnalysis: CodeAnalysisResult,
) {
const immediate: string[] = [];
const shortTerm: string[] = [];
const longTerm: string[] = [];
const criticalGaps = gaps.filter((g) => g.priority === "critical");
const highGaps = gaps.filter((g) => g.priority === "high");
const mediumGaps = gaps.filter((g) => g.priority === "medium");
// Immediate (Critical gaps)
criticalGaps.forEach((gap) => {
immediate.push(gap.recommendation);
});
// Short-term (High priority gaps)
highGaps.forEach((gap) => {
shortTerm.push(gap.recommendation);
});
// Long-term (Medium/Low priority gaps)
mediumGaps.forEach((gap) => {
longTerm.push(gap.recommendation);
});
// Add code-analysis-based recommendations
if (codeAnalysis.apiEndpoints.length > 0) {
longTerm.push(
`Consider generating OpenAPI/Swagger documentation for ${codeAnalysis.apiEndpoints.length} API endpoints`,
);
}
if (
codeAnalysis.functions.length +
codeAnalysis.classes.length +
codeAnalysis.interfaces.length >
50
) {
longTerm.push(
"Consider using automated documentation tools like TypeDoc for large codebases",
);
}
// Add general recommendations
if (immediate.length === 0 && shortTerm.length === 0) {
immediate.push(
"Documentation structure looks good - consider content enhancement",
);
}
return { immediate, shortTerm, longTerm };
}
function calculateContentCoverage(docsAnalysis: any, gaps: DocumentationGap[]) {
const categories = ["tutorials", "howTo", "reference", "explanation"];
const coverage: any = {};
categories.forEach((category) => {
const categoryKey = category === "howTo" ? "how-to" : category;
const hasContent =
docsAnalysis.structure?.[categoryKey]?.exists &&
docsAnalysis.structure[categoryKey].files.length > 0;
const hasGaps = gaps.some((g) => g.category === categoryKey);
if (hasContent && !hasGaps) {
coverage[category] = 100;
} else if (hasContent && hasGaps) {
coverage[category] = 60;
} else {
coverage[category] = 0;
}
});
return coverage;
}
function calculateOverallScore(
gaps: DocumentationGap[],
contentCoverage: any,
): number {
const coverageScore =
Object.values(contentCoverage).reduce(
(acc: number, val: any) => acc + val,
0,
) / 4;
const gapPenalty = gaps.length * 5; // Each gap reduces score by 5
const criticalPenalty =
gaps.filter((g) => g.priority === "critical").length * 15; // Critical gaps have higher penalty
return Math.max(
0,
Math.min(100, coverageScore - gapPenalty - criticalPenalty),
);
}
function identifyStrengths(docsAnalysis: any, validationResult: any): string[] {
const strengths: string[] = [];
if (docsAnalysis.hasRootIndex) {
strengths.push("Has main documentation index file");
}
const existingSections = Object.entries(docsAnalysis.structure || {})
.filter(([_, data]: [string, any]) => data.exists && data.files.length > 0)
.map(([section]) => section);
if (existingSections.length > 0) {
strengths.push(`Well-organized sections: ${existingSections.join(", ")}`);
}
if (validationResult?.overallScore > 80) {
strengths.push("High-quality existing content");
}
return strengths;
}
function identifyMissingStructure(docsAnalysis: any) {
const missingDirectories: string[] = [];
const missingFiles: string[] = [];
const expectedDirectories = [
"tutorials",
"how-to",
"reference",
"explanation",
];
expectedDirectories.forEach((dir) => {
if (!docsAnalysis.structure?.[dir]?.exists) {
missingDirectories.push(dir);
}
});
if (!docsAnalysis.hasRootIndex) {
missingFiles.push("index.md");
}
return { directories: missingDirectories, files: missingFiles };
}
function getRecommendedTool(recommendation: string): string {
if (recommendation.includes("setup_structure")) return "setup_structure";
if (recommendation.includes("populate_diataxis_content"))
return "populate_diataxis_content";
if (recommendation.includes("validate_diataxis_content"))
return "validate_diataxis_content";
if (recommendation.includes("generate_config")) return "generate_config";
if (recommendation.includes("deploy_pages")) return "deploy_pages";
return "manual";
}
```
--------------------------------------------------------------------------------
/tests/benchmarks/performance.test.ts:
--------------------------------------------------------------------------------
```typescript
import { PerformanceBenchmarker } from "../../src/benchmarks/performance";
import * as fs from "fs/promises";
import * as path from "path";
import * as os from "os";
describe("Performance Benchmarking System", () => {
let benchmarker: PerformanceBenchmarker;
let tempDir: string;
beforeEach(async () => {
benchmarker = new PerformanceBenchmarker();
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "perf-test-"));
});
afterEach(async () => {
try {
await fs.rm(tempDir, { recursive: true, force: true });
} catch (error) {
// Ignore cleanup errors
}
});
// Helper function to create test repositories
async function createTestRepo(
name: string,
fileCount: number,
): Promise<string> {
const repoPath = path.join(tempDir, name);
await fs.mkdir(repoPath, { recursive: true });
// Create package.json to make it look like a real project
await fs.writeFile(
path.join(repoPath, "package.json"),
JSON.stringify({ name, version: "1.0.0" }, null, 2),
);
// Create additional files to reach the target count
for (let i = 1; i < fileCount; i++) {
const fileName = `file${i}.js`;
await fs.writeFile(
path.join(repoPath, fileName),
`// Test file ${i}\nconsole.log('Hello from file ${i}');\n`,
);
}
return repoPath;
}
describe("Repository Size Categorization", () => {
it("should categorize small repositories correctly", async () => {
const smallRepoPath = await createTestRepo("small-repo", 25);
const result = await benchmarker.benchmarkRepository(smallRepoPath);
expect(result.repoSize).toBe("small");
expect(result.fileCount).toBe(25);
});
it("should categorize medium repositories correctly", async () => {
const mediumRepoPath = await createTestRepo("medium-repo", 250);
const result = await benchmarker.benchmarkRepository(mediumRepoPath);
expect(result.repoSize).toBe("medium");
expect(result.fileCount).toBe(250);
});
it("should categorize large repositories correctly", async () => {
const largeRepoPath = await createTestRepo("large-repo", 1200);
const result = await benchmarker.benchmarkRepository(largeRepoPath);
expect(result.repoSize).toBe("large");
expect(result.fileCount).toBe(1200);
});
});
describe("Performance Measurement", () => {
it("should measure execution time accurately", async () => {
const testRepoPath = await createTestRepo("timing-test", 10);
const result = await benchmarker.benchmarkRepository(testRepoPath);
expect(result.executionTime).toBeGreaterThanOrEqual(0);
expect(typeof result.executionTime).toBe("number");
});
it("should calculate performance ratios correctly", async () => {
const testRepoPath = await createTestRepo("ratio-test", 50);
const result = await benchmarker.benchmarkRepository(testRepoPath);
expect(result.performanceRatio).toBeGreaterThanOrEqual(0);
expect(result.performanceRatio).toBeLessThanOrEqual(100);
});
it("should track memory usage", async () => {
const testRepoPath = await createTestRepo("memory-test", 30);
const result = await benchmarker.benchmarkRepository(testRepoPath);
expect(result.details.memoryUsage).toBeDefined();
// Memory delta can be negative due to GC, just verify it's tracked
expect(typeof result.details.memoryUsage.heapUsed).toBe("number");
expect(result.details.memoryUsage.rss).toBeDefined();
expect(result.details.memoryUsage.heapTotal).toBeDefined();
});
});
describe("PERF-001 Compliance", () => {
it("should pass for small repositories under 1 second", async () => {
const testRepoPath = await createTestRepo("perf-test", 10);
const result = await benchmarker.benchmarkRepository(testRepoPath);
expect(result.passed).toBe(true);
expect(result.executionTime).toBeLessThan(1000);
});
it("should have correct performance targets", async () => {
const smallRepo = await createTestRepo("small-perf", 50);
const mediumRepo = await createTestRepo("medium-perf", 500);
const largeRepo = await createTestRepo("large-perf", 1500);
const smallResult = await benchmarker.benchmarkRepository(smallRepo);
const mediumResult = await benchmarker.benchmarkRepository(mediumRepo);
const largeResult = await benchmarker.benchmarkRepository(largeRepo);
expect(smallResult.targetTime).toBe(1000); // 1 second for small
expect(mediumResult.targetTime).toBe(10000); // 10 seconds for medium
expect(largeResult.targetTime).toBe(60000); // 60 seconds for large
});
});
describe("Benchmark Suite", () => {
it("should run multiple repository benchmarks", async () => {
const testRepos = [
{
path: await createTestRepo("suite-test-1", 25),
name: "Suite Test 1",
},
{
path: await createTestRepo("suite-test-2", 75),
name: "Suite Test 2",
},
];
const suite = await benchmarker.runBenchmarkSuite(testRepos);
expect(suite.results.length).toBe(2);
expect(suite.testName).toBeDefined();
expect(suite.overallPassed).toBeDefined();
});
it("should generate accurate summaries", async () => {
const testRepos = [
{ path: await createTestRepo("small-repo", 25), name: "Small Repo" },
{ path: await createTestRepo("medium-repo", 250), name: "Medium Repo" },
];
const suite = await benchmarker.runBenchmarkSuite(testRepos);
expect(suite.summary).toBeDefined();
const totalRepos =
suite.summary.smallRepos.count +
suite.summary.mediumRepos.count +
suite.summary.largeRepos.count;
expect(totalRepos).toBe(2);
const totalPassed =
suite.summary.smallRepos.passed +
suite.summary.mediumRepos.passed +
suite.summary.largeRepos.passed;
expect(totalPassed).toBeGreaterThanOrEqual(0);
});
});
describe("Result Export", () => {
it("should export benchmark results to JSON", async () => {
const testRepos = [
{ path: await createTestRepo("export-test", 20), name: "Export Test" },
];
const suite = await benchmarker.runBenchmarkSuite(testRepos);
const exportPath = path.join(tempDir, "benchmark-results.json");
await benchmarker.exportResults(suite, exportPath);
const exportedContent = await fs.readFile(exportPath, "utf-8");
const exportedData = JSON.parse(exportedContent);
expect(exportedData.suite).toBeDefined();
expect(exportedData.systemInfo).toBeDefined();
expect(exportedData.performanceTargets).toBeDefined();
expect(exportedData.timestamp).toBeDefined();
});
});
describe("Error Handling", () => {
it("should handle non-existent repository paths gracefully", async () => {
const nonExistentPath = path.join(tempDir, "does-not-exist");
const result = await benchmarker.benchmarkRepository(nonExistentPath);
// Should handle gracefully with 0 files
expect(result.fileCount).toBe(0);
expect(result.repoSize).toBe("small");
expect(result.executionTime).toBeGreaterThanOrEqual(0);
expect(result.passed).toBe(true); // Fast execution passes performance target
});
it("should handle permission denied scenarios gracefully", async () => {
if (process.platform === "win32") {
// Skip on Windows as permission handling is different
return;
}
const restrictedPath = path.join(tempDir, "restricted");
await fs.mkdir(restrictedPath, { recursive: true });
try {
await fs.chmod(restrictedPath, 0o000);
const result = await benchmarker.benchmarkRepository(restrictedPath);
// Should handle gracefully with 0 files
expect(result.fileCount).toBe(0);
expect(result.repoSize).toBe("small");
expect(result.executionTime).toBeGreaterThanOrEqual(0);
} finally {
// Restore permissions for cleanup
await fs.chmod(restrictedPath, 0o755);
}
});
it("should handle empty repositories", async () => {
const emptyRepoPath = path.join(tempDir, "empty-repo");
await fs.mkdir(emptyRepoPath, { recursive: true });
const result = await benchmarker.benchmarkRepository(emptyRepoPath);
expect(result.fileCount).toBe(0);
expect(result.repoSize).toBe("small");
expect(result.executionTime).toBeGreaterThanOrEqual(0);
});
it("should handle suite with all valid repositories", async () => {
const validRepo1 = await createTestRepo("valid-repo-1", 10);
const validRepo2 = await createTestRepo("valid-repo-2", 20);
const testRepos = [
{ path: validRepo1, name: "Valid Repo 1" },
{ path: validRepo2, name: "Valid Repo 2" },
];
const suite = await benchmarker.runBenchmarkSuite(testRepos);
expect(suite.results.length).toBe(2);
expect(suite.overallPassed).toBeDefined();
expect(typeof suite.averagePerformance).toBe("number");
});
it("should handle benchmark execution errors in try-catch", async () => {
// Test the error handling path by mocking analyzeRepository to throw
const originalAnalyze =
require("../../src/tools/analyze-repository").analyzeRepository;
const mockAnalyze = jest.fn().mockRejectedValue(new Error("Mock error"));
// Replace the function temporarily
require("../../src/tools/analyze-repository").analyzeRepository =
mockAnalyze;
try {
const testRepoPath = await createTestRepo("error-test", 10);
await expect(
benchmarker.benchmarkRepository(testRepoPath),
).rejects.toThrow("Mock error");
// Should still record the failed benchmark
const results = benchmarker.getResults();
expect(results.length).toBe(1);
expect(results[0].passed).toBe(false);
} finally {
// Restore original function
require("../../src/tools/analyze-repository").analyzeRepository =
originalAnalyze;
}
});
});
describe("Utility Methods", () => {
it("should reset benchmark results", async () => {
const testRepoPath = await createTestRepo("reset-test", 10);
await benchmarker.benchmarkRepository(testRepoPath);
expect(benchmarker.getResults().length).toBe(1);
benchmarker.reset();
expect(benchmarker.getResults().length).toBe(0);
});
it("should return copy of results array", async () => {
const testRepoPath = await createTestRepo("copy-test", 15);
await benchmarker.benchmarkRepository(testRepoPath);
const results1 = benchmarker.getResults();
const results2 = benchmarker.getResults();
expect(results1).toEqual(results2);
expect(results1).not.toBe(results2); // Different array instances
});
it("should handle different analysis depths", async () => {
const testRepoPath = await createTestRepo("depth-test", 20);
// Test with quick analysis
const quickResult = await benchmarker.benchmarkRepository(
testRepoPath,
"quick",
);
expect(quickResult.executionTime).toBeGreaterThanOrEqual(0);
// Test with deep analysis
const deepResult = await benchmarker.benchmarkRepository(
testRepoPath,
"deep",
);
expect(deepResult.executionTime).toBeGreaterThanOrEqual(0);
});
});
describe("Report Generation", () => {
it("should generate detailed reports without errors", async () => {
const testRepos = [
await createTestRepo("report-small", 25),
await createTestRepo("report-medium", 250),
await createTestRepo("report-large", 1200),
];
const results: any[] = [];
for (const repoPath of testRepos) {
const result = await benchmarker.benchmarkRepository(repoPath);
results.push(result);
}
const suite = benchmarker.generateSuite("Report Test", results);
// Capture console output
const originalLog = console.log;
const logOutput: string[] = [];
console.log = (...args) => {
logOutput.push(args.join(" "));
};
try {
benchmarker.printDetailedReport(suite);
expect(logOutput.length).toBeGreaterThan(0);
expect(
logOutput.some((line) =>
line.includes("Performance Benchmark Report"),
),
).toBe(true);
} finally {
console.log = originalLog;
}
});
it("should handle empty suite reports", async () => {
const emptySuite = benchmarker.generateSuite("Empty Suite", []);
// Should not throw when generating report for empty suite
expect(() => benchmarker.printDetailedReport(emptySuite)).not.toThrow();
});
it("should calculate correct averages for mixed results", async () => {
const repo1 = await createTestRepo("avg-test-1", 10);
const repo2 = await createTestRepo("avg-test-2", 20);
const repo3 = await createTestRepo("avg-test-3", 30);
const results = [
await benchmarker.benchmarkRepository(repo1),
await benchmarker.benchmarkRepository(repo2),
await benchmarker.benchmarkRepository(repo3),
];
const suite = benchmarker.generateSuite("Average Test", results);
expect(suite.averagePerformance).toBeGreaterThanOrEqual(0);
expect(suite.averagePerformance).toBeLessThanOrEqual(100);
expect(typeof suite.averagePerformance).toBe("number");
});
});
describe("Memory Usage Tracking", () => {
it("should track memory usage differences", async () => {
const testRepoPath = await createTestRepo("memory-tracking", 100);
const result = await benchmarker.benchmarkRepository(testRepoPath);
expect(result.details.memoryUsage).toBeDefined();
// Memory differences can be negative due to garbage collection
expect(typeof result.details.memoryUsage.heapUsed).toBe("number");
expect(typeof result.details.memoryUsage.heapTotal).toBe("number");
expect(typeof result.details.memoryUsage.rss).toBe("number");
});
it("should handle memory tracking in error scenarios", async () => {
const emptyRepoPath = path.join(tempDir, "empty-memory-test");
await fs.mkdir(emptyRepoPath, { recursive: true });
const result = await benchmarker.benchmarkRepository(emptyRepoPath);
// Even in error scenarios, memory tracking should work
expect(result.details.memoryUsage).toBeDefined();
expect(typeof result.details.memoryUsage.heapUsed).toBe("number");
});
});
describe("Edge Cases", () => {
it("should handle repositories with special characters in paths", async () => {
const specialCharRepo = path.join(tempDir, "repo with spaces & symbols!");
await fs.mkdir(specialCharRepo, { recursive: true });
await fs.writeFile(
path.join(specialCharRepo, "test.js"),
'console.log("test");',
);
const result = await benchmarker.benchmarkRepository(specialCharRepo);
expect(result.fileCount).toBe(1);
expect(result.executionTime).toBeGreaterThanOrEqual(0);
});
it("should handle very deep directory structures", async () => {
const deepRepoPath = path.join(tempDir, "deep-repo");
let currentPath = deepRepoPath;
// Create a deep directory structure
for (let i = 0; i < 10; i++) {
currentPath = path.join(currentPath, `level-${i}`);
await fs.mkdir(currentPath, { recursive: true });
await fs.writeFile(
path.join(currentPath, `file-${i}.js`),
`// Level ${i}`,
);
}
const result = await benchmarker.benchmarkRepository(deepRepoPath);
expect(result.fileCount).toBe(10);
expect(result.executionTime).toBeGreaterThanOrEqual(0);
});
it("should handle concurrent benchmarking", async () => {
const repo1 = await createTestRepo("concurrent-1", 15);
const repo2 = await createTestRepo("concurrent-2", 25);
const repo3 = await createTestRepo("concurrent-3", 35);
// Run benchmarks concurrently
const promises = [
benchmarker.benchmarkRepository(repo1),
benchmarker.benchmarkRepository(repo2),
benchmarker.benchmarkRepository(repo3),
];
const results = await Promise.all(promises);
expect(results.length).toBe(3);
results.forEach((result) => {
expect(result.executionTime).toBeGreaterThanOrEqual(0);
expect(result.fileCount).toBeGreaterThan(0);
});
});
it("should handle extremely deep recursion limit", async () => {
const deepRepoPath = path.join(tempDir, "extremely-deep");
let currentPath = deepRepoPath;
// Create a structure deeper than the 10-level limit
for (let i = 0; i < 15; i++) {
currentPath = path.join(currentPath, `level-${i}`);
await fs.mkdir(currentPath, { recursive: true });
await fs.writeFile(
path.join(currentPath, `file-${i}.js`),
`// Level ${i}`,
);
}
const result = await benchmarker.benchmarkRepository(deepRepoPath);
// Should stop at recursion limit, so fewer than 15 files
expect(result.fileCount).toBeLessThanOrEqual(10);
expect(result.executionTime).toBeGreaterThanOrEqual(0);
});
it("should skip node_modules and vendor directories", async () => {
const repoPath = await createTestRepo("skip-dirs", 5);
// Add node_modules and vendor directories
const nodeModulesPath = path.join(repoPath, "node_modules");
const vendorPath = path.join(repoPath, "vendor");
await fs.mkdir(nodeModulesPath, { recursive: true });
await fs.mkdir(vendorPath, { recursive: true });
// Add files that should be skipped
await fs.writeFile(
path.join(nodeModulesPath, "package.js"),
"module.exports = {};",
);
await fs.writeFile(path.join(vendorPath, "library.js"), "var lib = {};");
const result = await benchmarker.benchmarkRepository(repoPath);
// Should only count the original 5 files, not the ones in node_modules/vendor
expect(result.fileCount).toBe(5);
});
it("should skip hidden files except .github", async () => {
const repoPath = await createTestRepo("hidden-files", 3);
// Add hidden files and .github directory
await fs.writeFile(path.join(repoPath, ".hidden"), "hidden content");
await fs.writeFile(path.join(repoPath, ".env"), "SECRET=value");
const githubPath = path.join(repoPath, ".github");
await fs.mkdir(githubPath, { recursive: true });
await fs.writeFile(path.join(githubPath, "workflow.yml"), "name: CI");
const result = await benchmarker.benchmarkRepository(repoPath);
// Should count original 3 files + 1 .github file, but not other hidden files
expect(result.fileCount).toBe(4);
});
});
describe("Factory Function", () => {
it("should create benchmarker instance via factory", () => {
const { createBenchmarker } = require("../../src/benchmarks/performance");
const factoryBenchmarker = createBenchmarker();
expect(factoryBenchmarker).toBeInstanceOf(PerformanceBenchmarker);
expect(factoryBenchmarker.getResults()).toEqual([]);
});
});
describe("Export Results Error Handling", () => {
it("should handle export to invalid path gracefully", async () => {
const testRepos = [
{
path: await createTestRepo("export-error-test", 10),
name: "Export Error Test",
},
];
const suite = await benchmarker.runBenchmarkSuite(testRepos);
const invalidPath = path.join(
"/invalid/nonexistent/path",
"results.json",
);
await expect(
benchmarker.exportResults(suite, invalidPath),
).rejects.toThrow();
});
it("should export complete system information", async () => {
const testRepos = [
{
path: await createTestRepo("system-info-test", 5),
name: "System Info Test",
},
];
const suite = await benchmarker.runBenchmarkSuite(testRepos);
const exportPath = path.join(tempDir, "system-info-results.json");
await benchmarker.exportResults(suite, exportPath);
const exportedContent = await fs.readFile(exportPath, "utf-8");
const exportedData = JSON.parse(exportedContent);
expect(exportedData.systemInfo.node).toBe(process.version);
expect(exportedData.systemInfo.platform).toBe(process.platform);
expect(exportedData.systemInfo.arch).toBe(process.arch);
expect(exportedData.systemInfo.memoryUsage).toBeDefined();
expect(exportedData.performanceTargets).toEqual({
small: 1000,
medium: 10000,
large: 60000,
});
});
});
describe("Detailed Report Coverage", () => {
it("should print detailed reports with all categories", async () => {
// Create repos of all sizes to test all report sections
const smallRepo = await createTestRepo("report-small", 25);
const mediumRepo = await createTestRepo("report-medium", 250);
const largeRepo = await createTestRepo("report-large", 1200);
const results = [
await benchmarker.benchmarkRepository(smallRepo),
await benchmarker.benchmarkRepository(mediumRepo),
await benchmarker.benchmarkRepository(largeRepo),
];
const suite = benchmarker.generateSuite("Complete Report Test", results);
// Capture console output
const originalLog = console.log;
const logOutput: string[] = [];
console.log = (...args) => {
logOutput.push(args.join(" "));
};
try {
benchmarker.printDetailedReport(suite);
// Verify all report sections are present
const fullOutput = logOutput.join("\n");
expect(fullOutput).toContain("Performance Benchmark Report");
expect(fullOutput).toContain("Overall Status:");
expect(fullOutput).toContain("Average Performance:");
expect(fullOutput).toContain("Small (<100 files)");
expect(fullOutput).toContain("Medium (100-1000 files)");
expect(fullOutput).toContain("Large (1000+ files)");
expect(fullOutput).toContain("Detailed Results:");
expect(fullOutput).toContain("Memory:");
} finally {
console.log = originalLog;
}
});
it("should handle report generation with no results in some categories", async () => {
// Only create small repos to test empty category handling
const results = [
await benchmarker.benchmarkRepository(
await createTestRepo("small-only-1", 10),
),
await benchmarker.benchmarkRepository(
await createTestRepo("small-only-2", 20),
),
];
const suite = benchmarker.generateSuite("Small Only Test", results);
const originalLog = console.log;
const logOutput: string[] = [];
console.log = (...args) => {
logOutput.push(args.join(" "));
};
try {
benchmarker.printDetailedReport(suite);
const fullOutput = logOutput.join("\n");
expect(fullOutput).toContain("Small (<100 files)");
// Medium and Large categories should not appear since count is 0
expect(fullOutput).not.toContain("Medium (100-1000 files):");
expect(fullOutput).not.toContain("Large (1000+ files):");
} finally {
console.log = originalLog;
}
});
});
});
```
--------------------------------------------------------------------------------
/docs/research/research-questions-2025-01-14.md:
--------------------------------------------------------------------------------
```markdown
---
documcp:
last_updated: "2025-11-20T00:46:21.969Z"
last_validated: "2025-11-20T00:46:21.969Z"
auto_updated: false
update_frequency: monthly
---
# DocuMCP Implementation Research Questions
**Generated**: January 14, 2025
**Project**: DocuMCP - Intelligent MCP Server for GitHub Pages Documentation Deployment
**Phase**: Pre-Implementation Research
**Context**: Comprehensive validation of ADR decisions and implementation planning
---
## Research Overview
This document contains systematic research questions organized by architectural domain, based on the 6 ADRs established for DocuMCP. Each section includes priority ratings, validation criteria, and expected outcomes to guide effective pre-implementation research.
### Research Objectives
1. **Validate technical feasibility** of ADR decisions
2. **Identify implementation risks** and mitigation strategies
3. **Research best practices** for MCP server development
4. **Investigate SSG ecosystem** integration patterns
5. **Explore Diataxis framework** implementation approaches
### Research Constraints
- TypeScript/Node.js ecosystem limitations
- MCP specification compliance requirements
- GitHub Pages deployment constraints
- Performance and scalability requirements
---
## Domain 1: MCP Server Architecture Research (ADR-001)
### Priority: HIGH - Foundation Critical
#### Core Architecture Questions
**Q1.1: TypeScript MCP SDK Performance Characteristics**
- **Question**: What are the performance benchmarks and limitations of the TypeScript MCP SDK under heavy concurrent usage?
- **Priority**: CRITICAL
- **Research Method**: Performance testing, benchmark analysis
- **Success Criteria**: Documented performance profiles for different load scenarios
- **Timeline**: Week 1
- **Dependencies**: None
**Q1.2: Node.js Memory Management for Repository Analysis**
- **Question**: How can we optimize Node.js memory usage when analyzing large repositories (>10GB)?
- **Priority**: HIGH
- **Research Method**: Memory profiling, stress testing
- **Success Criteria**: Memory optimization strategies with <2GB footprint for 10GB repos
- **Timeline**: Week 1-2
- **Dependencies**: Q1.1
**Q1.3: MCP Tool Orchestration Patterns**
- **Question**: What are the most effective patterns for orchestrating complex multi-tool workflows in MCP?
- **Priority**: HIGH
- **Research Method**: Pattern analysis, prototype development
- **Success Criteria**: Documented orchestration patterns with examples
- **Timeline**: Week 2
- **Dependencies**: Q1.1
**Q1.4: Stateless Session Context Management**
- **Question**: How can we efficiently maintain temporary context across tool calls while preserving stateless architecture?
- **Priority**: MEDIUM
- **Research Method**: Architecture research, implementation prototyping
- **Success Criteria**: Context management strategy that doesn't violate MCP principles
- **Timeline**: Week 2-3
- **Dependencies**: Q1.3
**Q1.5: Error Recovery and Fault Tolerance**
- **Question**: What are the best practices for implementing robust error recovery in MCP servers?
- **Priority**: HIGH
- **Research Method**: Error pattern analysis, resilience testing
- **Success Criteria**: Comprehensive error handling framework
- **Timeline**: Week 3
- **Dependencies**: Q1.1, Q1.3
#### Integration and Deployment Questions
**Q1.6: GitHub Copilot Integration Patterns**
- **Question**: What are the optimal integration patterns for MCP servers with GitHub Copilot?
- **Priority**: MEDIUM
- **Research Method**: Integration testing, user experience research
- **Success Criteria**: Documented integration best practices
- **Timeline**: Week 3-4
- **Dependencies**: Q1.3
**Q1.7: Development Environment Setup**
- **Question**: What tooling and development practices optimize TypeScript MCP server development?
- **Priority**: LOW
- **Research Method**: Tool evaluation, workflow analysis
- **Success Criteria**: Development environment recommendations
- **Timeline**: Week 4
- **Dependencies**: None
---
## Domain 2: Repository Analysis Engine Research (ADR-002)
### Priority: HIGH - Intelligence Foundation
#### Analysis Algorithm Questions
**Q2.1: Multi-layered Analysis Performance**
- **Question**: How can we optimize the performance of parallel multi-layered repository analysis?
- **Priority**: CRITICAL
- **Research Method**: Algorithm optimization, parallel processing research
- **Success Criteria**: Analysis completion <30 seconds for typical repositories
- **Timeline**: Week 1-2
- **Dependencies**: Q1.2
**Q2.2: Language Ecosystem Detection Accuracy**
- **Question**: What are the most reliable methods for detecting and analyzing language ecosystems in repositories?
- **Priority**: HIGH
- **Research Method**: Accuracy testing across diverse repositories
- **Success Criteria**: >95% accuracy for major language ecosystems
- **Timeline**: Week 2
- **Dependencies**: None
**Q2.3: Content Analysis Natural Language Processing**
- **Question**: What NLP techniques are most effective for analyzing documentation quality and gaps?
- **Priority**: MEDIUM
- **Research Method**: NLP library evaluation, accuracy testing
- **Success Criteria**: Reliable content quality assessment methodology
- **Timeline**: Week 3
- **Dependencies**: Q2.1
**Q2.4: Complexity Scoring Algorithm Validation**
- **Question**: How can we validate and calibrate the project complexity scoring algorithm?
- **Priority**: MEDIUM
- **Research Method**: Validation against known project types, expert review
- **Success Criteria**: Complexity scores correlate with manual expert assessment
- **Timeline**: Week 3-4
- **Dependencies**: Q2.1, Q2.2
**Q2.5: Incremental Analysis Capabilities**
- **Question**: How can we implement incremental analysis for repositories that change over time?
- **Priority**: LOW
- **Research Method**: Differential analysis research, caching strategies
- **Success Criteria**: Incremental analysis reduces re-analysis time by >80%
- **Timeline**: Week 4+
- **Dependencies**: Q2.1
#### Scalability and Performance Questions
**Q2.6: Large Repository Handling**
- **Question**: What strategies ensure reliable analysis of enterprise-scale repositories (>100GB)?
- **Priority**: MEDIUM
- **Research Method**: Scalability testing, streaming analysis research
- **Success Criteria**: Successful analysis of repositories up to 100GB
- **Timeline**: Week 2-3
- **Dependencies**: Q1.2, Q2.1
**Q2.7: Analysis Caching Strategies**
- **Question**: What caching strategies provide optimal performance for repository analysis?
- **Priority**: MEDIUM
- **Research Method**: Caching pattern research, performance testing
- **Success Criteria**: Cache hit rates >70% for repeated analysis
- **Timeline**: Week 3
- **Dependencies**: Q2.1
---
## Domain 3: SSG Recommendation Engine Research (ADR-003)
### Priority: HIGH - Core Intelligence
#### Decision Analysis Questions
**Q3.1: Multi-Criteria Decision Algorithm Validation**
- **Question**: How can we validate the accuracy of the MCDA framework for SSG recommendations?
- **Priority**: CRITICAL
- **Research Method**: Validation against expert recommendations, A/B testing
- **Success Criteria**: Algorithm recommendations match expert choices >85% of the time
- **Timeline**: Week 1-2
- **Dependencies**: Q2.4
**Q3.2: SSG Capability Profiling Methodology**
- **Question**: What methodology ensures accurate and up-to-date SSG capability profiles?
- **Priority**: HIGH
- **Research Method**: SSG feature analysis, performance benchmarking
- **Success Criteria**: Comprehensive profiles for 5 major SSGs
- **Timeline**: Week 2-3
- **Dependencies**: None
**Q3.3: Confidence Score Calibration**
- **Question**: How can we calibrate confidence scores to accurately reflect recommendation reliability?
- **Priority**: HIGH
- **Research Method**: Statistical analysis, outcome tracking
- **Success Criteria**: Confidence scores correlate with actual recommendation success
- **Timeline**: Week 3
- **Dependencies**: Q3.1
**Q3.4: Performance Modeling Accuracy**
- **Question**: How accurate are our build time and performance predictions for different SSGs?
- **Priority**: MEDIUM
- **Research Method**: Prediction validation, real-world testing
- **Success Criteria**: Performance predictions within 20% of actual results
- **Timeline**: Week 3-4
- **Dependencies**: Q3.2
**Q3.5: Dynamic Weight Adjustment**
- **Question**: Should recommendation weights be dynamically adjusted based on project characteristics?
- **Priority**: LOW
- **Research Method**: Machine learning research, adaptive algorithm development
- **Success Criteria**: Dynamic weighting improves recommendation accuracy by >10%
- **Timeline**: Week 4+
- **Dependencies**: Q3.1, Q3.3
#### Knowledge Base Maintenance Questions
**Q3.6: Automated SSG Capability Monitoring**
- **Question**: How can we automate the monitoring and updating of SSG capabilities?
- **Priority**: MEDIUM
- **Research Method**: API research, automation tool development
- **Success Criteria**: Automated detection of SSG capability changes
- **Timeline**: Week 4
- **Dependencies**: Q3.2
**Q3.7: Community Feedback Integration**
- **Question**: How can we integrate community feedback to improve recommendation accuracy?
- **Priority**: LOW
- **Research Method**: Feedback system design, data analysis methods
- **Success Criteria**: Community feedback improves recommendations measurably
- **Timeline**: Week 4+
- **Dependencies**: Q3.1
---
## Domain 4: Diataxis Framework Integration Research (ADR-004)
### Priority: MEDIUM - Quality Enhancement
#### Implementation Strategy Questions
**Q4.1: Automated Content Structure Generation**
- **Question**: What are the most effective approaches for automating Diataxis-compliant structure generation?
- **Priority**: HIGH
- **Research Method**: Template system research, automation testing
- **Success Criteria**: Automated generation of compliant structures for all supported SSGs
- **Timeline**: Week 2
- **Dependencies**: Q3.2
**Q4.2: Content Planning Intelligence**
- **Question**: How can we intelligently suggest content based on project analysis and Diataxis principles?
- **Priority**: MEDIUM
- **Research Method**: Content analysis algorithms, suggestion accuracy testing
- **Success Criteria**: Content suggestions deemed useful by documentation experts >80% of time
- **Timeline**: Week 3
- **Dependencies**: Q2.3, Q4.1
**Q4.3: SSG-Specific Diataxis Adaptations**
- **Question**: How should Diataxis implementation be adapted for each SSG's unique capabilities?
- **Priority**: MEDIUM
- **Research Method**: SSG feature analysis, adaptation strategy development
- **Success Criteria**: Optimal Diataxis implementation for each supported SSG
- **Timeline**: Week 3-4
- **Dependencies**: Q3.2, Q4.1
**Q4.4: Navigation Generation Algorithms**
- **Question**: What algorithms generate the most intuitive navigation for Diataxis-organized content?
- **Priority**: MEDIUM
- **Research Method**: UX research, navigation pattern analysis
- **Success Criteria**: Navigation usability scores >90% in user testing
- **Timeline**: Week 4
- **Dependencies**: Q4.1, Q4.3
#### Quality Assurance Questions
**Q4.5: Diataxis Compliance Validation**
- **Question**: How can we automatically validate Diataxis compliance in generated structures?
- **Priority**: MEDIUM
- **Research Method**: Validation algorithm development, compliance testing
- **Success Criteria**: Automated compliance checking with >95% accuracy
- **Timeline**: Week 3
- **Dependencies**: Q4.1
**Q4.6: Content Quality Metrics**
- **Question**: What metrics best measure the quality of Diataxis-organized documentation?
- **Priority**: LOW
- **Research Method**: Quality metric research, correlation analysis
- **Success Criteria**: Validated quality metrics that predict user satisfaction
- **Timeline**: Week 4+
- **Dependencies**: Q4.2, Q4.5
---
## Domain 5: GitHub Pages Deployment Research (ADR-005)
### Priority: HIGH - Implementation Critical
#### Workflow Optimization Questions
**Q5.1: SSG-Specific Workflow Performance**
- **Question**: What are the optimal GitHub Actions configurations for each supported SSG?
- **Priority**: CRITICAL
- **Research Method**: Workflow benchmarking, optimization testing
- **Success Criteria**: Optimized workflows reduce build times by >30%
- **Timeline**: Week 1-2
- **Dependencies**: Q3.2
**Q5.2: Advanced Caching Strategies**
- **Question**: What caching strategies provide maximum build performance in GitHub Actions?
- **Priority**: HIGH
- **Research Method**: Caching pattern research, performance testing
- **Success Criteria**: Cache strategies reduce build times by >50% for incremental changes
- **Timeline**: Week 2
- **Dependencies**: Q5.1
**Q5.3: Build Failure Diagnosis and Recovery**
- **Question**: How can we implement intelligent build failure diagnosis and automatic recovery?
- **Priority**: HIGH
- **Research Method**: Error pattern analysis, recovery strategy development
- **Success Criteria**: Automatic recovery for >70% of common build failures
- **Timeline**: Week 3
- **Dependencies**: Q5.1
**Q5.4: Multi-Environment Deployment Strategies**
- **Question**: What strategies support deployment to multiple environments (staging, production)?
- **Priority**: MEDIUM
- **Research Method**: Deployment pattern research, environment management
- **Success Criteria**: Seamless multi-environment deployment capabilities
- **Timeline**: Week 4
- **Dependencies**: Q5.1, Q5.2
#### Security and Compliance Questions
**Q5.5: Workflow Security Best Practices**
- **Question**: What security best practices should be enforced in generated GitHub Actions workflows?
- **Priority**: HIGH
- **Research Method**: Security research, vulnerability analysis
- **Success Criteria**: Security-hardened workflows with minimal attack surface
- **Timeline**: Week 2-3
- **Dependencies**: Q5.1
**Q5.6: Dependency Vulnerability Management**
- **Question**: How can we automatically manage and update vulnerable dependencies in workflows?
- **Priority**: MEDIUM
- **Research Method**: Dependency scanning research, automation development
- **Success Criteria**: Automated vulnerability detection and resolution
- **Timeline**: Week 3
- **Dependencies**: Q5.5
**Q5.7: Secrets and Environment Management**
- **Question**: What are the best practices for managing secrets and environment variables in automated deployments?
- **Priority**: MEDIUM
- **Research Method**: Security pattern research, credential management
- **Success Criteria**: Secure secrets management without user complexity
- **Timeline**: Week 3
- **Dependencies**: Q5.5
#### Monitoring and Troubleshooting Questions
**Q5.8: Deployment Health Monitoring**
- **Question**: How can we implement comprehensive health monitoring for deployed documentation sites?
- **Priority**: MEDIUM
- **Research Method**: Monitoring tool research, health check development
- **Success Criteria**: Comprehensive health monitoring with actionable alerts
- **Timeline**: Week 4
- **Dependencies**: Q5.1
**Q5.9: Performance Optimization Recommendations**
- **Question**: How can we provide automated performance optimization recommendations for deployed sites?
- **Priority**: LOW
- **Research Method**: Performance analysis research, optimization pattern development
- **Success Criteria**: Automated performance recommendations that improve site speed
- **Timeline**: Week 4+
- **Dependencies**: Q5.8
---
## Domain 6: MCP Tools API Research (ADR-006)
### Priority: HIGH - User Interface Critical
#### API Design and Usability Questions
**Q6.1: Tool Parameter Schema Optimization**
- **Question**: What parameter schema designs provide the best balance of flexibility and usability?
- **Priority**: HIGH
- **Research Method**: API design research, usability testing
- **Success Criteria**: Parameter schemas that are intuitive and comprehensive
- **Timeline**: Week 1-2
- **Dependencies**: None
**Q6.2: Response Format Standardization**
- **Question**: What response formats provide optimal client integration and user experience?
- **Priority**: HIGH
- **Research Method**: Format analysis, client integration testing
- **Success Criteria**: Standardized formats that simplify client development
- **Timeline**: Week 2
- **Dependencies**: Q6.1
**Q6.3: Error Handling and User Guidance**
- **Question**: How can we provide the most helpful error messages and recovery guidance?
- **Priority**: HIGH
- **Research Method**: Error analysis, user experience research
- **Success Criteria**: Error messages that enable users to resolve issues >90% of the time
- **Timeline**: Week 2-3
- **Dependencies**: Q6.1
**Q6.4: Progressive Complexity Disclosure**
- **Question**: How can we design APIs that are simple for beginners but powerful for experts?
- **Priority**: MEDIUM
- **Research Method**: API design pattern research, user journey analysis
- **Success Criteria**: APIs that scale from simple to complex use cases seamlessly
- **Timeline**: Week 3
- **Dependencies**: Q6.1, Q6.2
#### Validation and Security Questions
**Q6.5: Comprehensive Input Validation**
- **Question**: What validation strategies ensure robust security and user-friendly error reporting?
- **Priority**: HIGH
- **Research Method**: Validation framework research, security testing
- **Success Criteria**: Validation that prevents all security issues while providing clear feedback
- **Timeline**: Week 2
- **Dependencies**: Q6.1
**Q6.6: Performance and Caching Optimization**
- **Question**: How can we optimize API performance through intelligent caching and response optimization?
- **Priority**: MEDIUM
- **Research Method**: Performance testing, caching strategy research
- **Success Criteria**: API response times <1 second for all operations
- **Timeline**: Week 3
- **Dependencies**: Q6.2
#### Integration and Extension Questions
**Q6.7: Client Integration Patterns**
- **Question**: What integration patterns work best for different types of MCP clients?
- **Priority**: MEDIUM
- **Research Method**: Integration testing, client developer feedback
- **Success Criteria**: Integration patterns that simplify client development
- **Timeline**: Week 3-4
- **Dependencies**: Q6.2, Q6.4
**Q6.8: API Extension and Versioning**
- **Question**: How can we design APIs that support future extensions without breaking existing clients?
- **Priority**: LOW
- **Research Method**: Versioning strategy research, extension pattern analysis
- **Success Criteria**: Extension mechanisms that maintain backward compatibility
- **Timeline**: Week 4
- **Dependencies**: Q6.1, Q6.2
---
## Cross-Domain Integration Research
### Priority: MEDIUM - System Integration
#### End-to-End Workflow Questions
**Q7.1: Complete Workflow Orchestration**
- **Question**: How can we optimize the complete workflow from repository analysis to deployed documentation?
- **Priority**: HIGH
- **Research Method**: Workflow analysis, performance optimization
- **Success Criteria**: End-to-end workflow completion in <10 minutes for typical projects
- **Timeline**: Week 3-4
- **Dependencies**: All previous domains
**Q7.2: Error Recovery Across Tools**
- **Question**: How can we implement robust error recovery that spans multiple tool invocations?
- **Priority**: MEDIUM
- **Research Method**: Error pattern analysis, recovery strategy development
- **Success Criteria**: Graceful recovery from failures at any workflow stage
- **Timeline**: Week 4
- **Dependencies**: Q7.1
**Q7.3: Performance Monitoring and Optimization**
- **Question**: How can we monitor and optimize performance across the entire system?
- **Priority**: MEDIUM
- **Research Method**: Performance monitoring research, optimization strategies
- **Success Criteria**: System-wide performance monitoring and optimization recommendations
- **Timeline**: Week 4
- **Dependencies**: Q7.1
#### Quality Assurance and Validation
**Q7.4: Integration Testing Strategies**
- **Question**: What testing strategies ensure reliable operation across all components?
- **Priority**: MEDIUM
- **Research Method**: Testing framework research, integration test development
- **Success Criteria**: Comprehensive integration tests with >95% coverage
- **Timeline**: Week 4
- **Dependencies**: All previous domains
**Q7.5: User Acceptance Validation**
- **Question**: How can we validate that the complete system meets user needs and expectations?
- **Priority**: LOW
- **Research Method**: User research, acceptance testing
- **Success Criteria**: User satisfaction scores >85% in testing
- **Timeline**: Week 4+
- **Dependencies**: Q7.1, Q7.4
---
## Research Execution Framework
### Research Methodology
1. **Literature Review**: Systematic review of existing solutions and best practices
2. **Prototype Development**: Small-scale implementations to validate approaches
3. **Performance Testing**: Quantitative analysis of performance characteristics
4. **Expert Consultation**: Validation with domain experts and practitioners
5. **Community Research**: Analysis of community practices and feedback
### Success Criteria Framework
Each research question includes:
- **Quantitative Metrics**: Measurable success criteria
- **Qualitative Assessments**: Expert validation and user feedback
- **Risk Mitigation**: Identification of potential issues and solutions
- **Implementation Guidance**: Actionable recommendations for development
### Documentation Requirements
All research outcomes must be documented with:
- **Executive Summary**: Key findings and recommendations
- **Detailed Analysis**: Comprehensive research methodology and results
- **Implementation Recommendations**: Specific guidance for development
- **Risk Assessment**: Identified risks and mitigation strategies
- **Follow-up Actions**: Additional research or validation needed
### Timeline and Prioritization
**Week 1 Focus**: Critical path items (Q1.1, Q2.1, Q3.1, Q5.1)
**Week 2 Focus**: High priority foundational research
**Week 3 Focus**: Integration and optimization research
**Week 4 Focus**: Advanced features and system integration
### Quality Assurance
- **Peer Review**: All research findings reviewed by team members
- **Expert Validation**: Critical decisions validated by external experts
- **Prototype Validation**: Key approaches validated through working prototypes
- **Documentation Standards**: All research properly documented and archived
---
## Research Output Organization
### File Structure
```
docs/research/
├── research-questions-2025-01-14.md (this file)
├── domain-1-mcp-architecture/
├── domain-2-repository-analysis/
├── domain-3-ssg-recommendation/
├── domain-4-diataxis-integration/
├── domain-5-github-deployment/
├── domain-6-api-design/
├── cross-domain-integration/
└── research-findings-summary.md
```
### Progress Tracking
Research progress will be tracked using:
- **Weekly Status Reports**: Progress on each research domain
- **Risk Register**: Ongoing tracking of identified risks and mitigations
- **Decision Log**: Record of key decisions made based on research findings
- **Implementation Readiness Assessment**: Regular evaluation of readiness to begin development
---
**Total Research Questions**: 47 questions across 6 domains
**Critical Path Questions**: 6 questions requiring immediate attention
**High Priority Questions**: 19 questions for weeks 1-2
**Estimated Research Duration**: 4 weeks
**Success Metrics**: Quantitative criteria for each research area
This comprehensive research framework ensures systematic validation of all ADR decisions and provides the foundation for confident implementation of the DocuMCP project.
```
--------------------------------------------------------------------------------
/src/utils/drift-detector.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Documentation Drift Detection System (Phase 3)
*
* Detects when code changes invalidate existing documentation
* Provides automatic update suggestions based on code changes
*/
import { promises as fs } from "fs";
import path from "path";
import { ASTAnalyzer, ASTAnalysisResult, CodeDiff } from "./ast-analyzer.js";
export interface DriftDetectionResult {
filePath: string;
hasDrift: boolean;
severity: "none" | "low" | "medium" | "high" | "critical";
drifts: DocumentationDrift[];
suggestions: DriftSuggestion[];
impactAnalysis: ImpactAnalysis;
}
export interface DocumentationDrift {
type: "outdated" | "incorrect" | "missing" | "breaking";
affectedDocs: string[];
codeChanges: CodeDiff[];
description: string;
detectedAt: string;
severity: "low" | "medium" | "high" | "critical";
}
export interface DriftSuggestion {
docFile: string;
section: string;
currentContent: string;
suggestedContent: string;
reasoning: string;
confidence: number;
autoApplicable: boolean;
}
export interface ImpactAnalysis {
breakingChanges: number;
majorChanges: number;
minorChanges: number;
affectedDocFiles: string[];
estimatedUpdateEffort: "low" | "medium" | "high";
requiresManualReview: boolean;
}
export interface DriftSnapshot {
projectPath: string;
timestamp: string;
files: Map<string, ASTAnalysisResult>;
documentation: Map<string, DocumentationSnapshot>;
}
export interface DocumentationSnapshot {
filePath: string;
contentHash: string;
referencedCode: string[];
lastUpdated: string;
sections: DocumentationSection[];
}
export interface DocumentationSection {
title: string;
content: string;
referencedFunctions: string[];
referencedClasses: string[];
referencedTypes: string[];
codeExamples: CodeExample[];
startLine: number;
endLine: number;
}
export interface CodeExample {
language: string;
code: string;
description: string;
referencedSymbols: string[];
}
/**
* Main Drift Detector class
*/
export class DriftDetector {
private analyzer: ASTAnalyzer;
private snapshotDir: string;
private currentSnapshot: DriftSnapshot | null = null;
private previousSnapshot: DriftSnapshot | null = null;
constructor(projectPath: string, snapshotDir?: string) {
this.analyzer = new ASTAnalyzer();
this.snapshotDir =
snapshotDir || path.join(projectPath, ".documcp", "snapshots");
}
/**
* Initialize the drift detector
*/
async initialize(): Promise<void> {
await this.analyzer.initialize();
await fs.mkdir(this.snapshotDir, { recursive: true });
}
/**
* Create a snapshot of the current codebase and documentation
*/
async createSnapshot(
projectPath: string,
docsPath: string,
): Promise<DriftSnapshot> {
const files = new Map<string, ASTAnalysisResult>();
const documentation = new Map<string, DocumentationSnapshot>();
// Analyze source files
const sourceFiles = await this.findSourceFiles(projectPath);
for (const filePath of sourceFiles) {
const analysis = await this.analyzer.analyzeFile(filePath);
if (analysis) {
files.set(filePath, analysis);
}
}
// Analyze documentation files
const docFiles = await this.findDocumentationFiles(docsPath);
for (const docPath of docFiles) {
const docSnapshot = await this.analyzeDocumentation(docPath);
if (docSnapshot) {
documentation.set(docPath, docSnapshot);
}
}
const snapshot: DriftSnapshot = {
projectPath,
timestamp: new Date().toISOString(),
files,
documentation,
};
// Save snapshot
await this.saveSnapshot(snapshot);
return snapshot;
}
/**
* Detect drift between two snapshots
*/
async detectDrift(
oldSnapshot: DriftSnapshot,
newSnapshot: DriftSnapshot,
): Promise<DriftDetectionResult[]> {
const results: DriftDetectionResult[] = [];
// Compare each file
for (const [filePath, newAnalysis] of newSnapshot.files) {
const oldAnalysis = oldSnapshot.files.get(filePath);
if (!oldAnalysis) {
// New file - check if documentation is needed
continue;
}
// Detect code changes
const codeDiffs = await this.analyzer.detectDrift(
oldAnalysis,
newAnalysis,
);
if (codeDiffs.length > 0) {
// Find affected documentation
const affectedDocs = this.findAffectedDocumentation(
filePath,
codeDiffs,
newSnapshot.documentation,
);
// Report drift even if no documentation is affected
// (missing documentation is also a type of drift)
const driftResult = await this.analyzeDrift(
filePath,
codeDiffs,
affectedDocs,
oldSnapshot,
newSnapshot,
);
results.push(driftResult);
}
}
return results;
}
/**
* Analyze drift and generate suggestions
*/
private async analyzeDrift(
filePath: string,
codeDiffs: CodeDiff[],
affectedDocs: string[],
oldSnapshot: DriftSnapshot,
newSnapshot: DriftSnapshot,
): Promise<DriftDetectionResult> {
const drifts: DocumentationDrift[] = [];
const suggestions: DriftSuggestion[] = [];
// Categorize drifts by severity
const breakingChanges = codeDiffs.filter(
(d) => d.impactLevel === "breaking",
);
const majorChanges = codeDiffs.filter((d) => d.impactLevel === "major");
const minorChanges = codeDiffs.filter((d) => d.impactLevel === "minor");
// Create drift entries
for (const diff of codeDiffs) {
const drift: DocumentationDrift = {
type: this.determineDriftType(diff),
affectedDocs,
codeChanges: [diff],
description: this.generateDriftDescription(diff),
detectedAt: new Date().toISOString(),
severity: this.mapImpactToSeverity(diff.impactLevel),
};
drifts.push(drift);
// Generate suggestions for each affected doc
for (const docPath of affectedDocs) {
const docSnapshot = newSnapshot.documentation.get(docPath);
if (docSnapshot) {
const docSuggestions = await this.generateSuggestions(
diff,
docSnapshot,
newSnapshot,
);
suggestions.push(...docSuggestions);
}
}
}
const impactAnalysis: ImpactAnalysis = {
breakingChanges: breakingChanges.length,
majorChanges: majorChanges.length,
minorChanges: minorChanges.length,
affectedDocFiles: affectedDocs,
estimatedUpdateEffort: this.estimateUpdateEffort(drifts),
requiresManualReview:
breakingChanges.length > 0 || majorChanges.length > 3,
};
const severity = this.calculateOverallSeverity(drifts);
return {
filePath,
hasDrift: drifts.length > 0,
severity,
drifts,
suggestions,
impactAnalysis,
};
}
/**
* Generate update suggestions for documentation
*/
private async generateSuggestions(
diff: CodeDiff,
docSnapshot: DocumentationSnapshot,
snapshot: DriftSnapshot,
): Promise<DriftSuggestion[]> {
const suggestions: DriftSuggestion[] = [];
// Find sections that reference the changed code
for (const section of docSnapshot.sections) {
const isAffected = this.isSectionAffected(section, diff);
if (isAffected) {
const suggestion = await this.createSuggestion(
diff,
docSnapshot,
section,
snapshot,
);
if (suggestion) {
suggestions.push(suggestion);
}
}
}
return suggestions;
}
/**
* Create a specific suggestion for a documentation section
*/
private async createSuggestion(
diff: CodeDiff,
docSnapshot: DocumentationSnapshot,
section: DocumentationSection,
snapshot: DriftSnapshot,
): Promise<DriftSuggestion | null> {
let suggestedContent = section.content;
let reasoning = "";
let confidence = 0.5;
let autoApplicable = false;
switch (diff.type) {
case "removed":
reasoning = `The ${diff.category} '${diff.name}' has been removed from the codebase. This section should be updated or removed.`;
suggestedContent = this.generateRemovalSuggestion(section, diff);
confidence = 0.8;
autoApplicable = false;
break;
case "added":
reasoning = `A new ${diff.category} '${diff.name}' has been added. Consider documenting it.`;
suggestedContent = this.generateAdditionSuggestion(
section,
diff,
snapshot,
);
confidence = 0.6;
autoApplicable = false;
break;
case "modified":
reasoning = `The ${diff.category} '${diff.name}' has been modified: ${diff.details}`;
suggestedContent = this.generateModificationSuggestion(
section,
diff,
snapshot,
);
confidence = 0.7;
autoApplicable = diff.impactLevel === "patch";
break;
}
return {
docFile: docSnapshot.filePath,
section: section.title,
currentContent: section.content,
suggestedContent,
reasoning,
confidence,
autoApplicable,
};
}
/**
* Generate suggestion for removed code
*/
private generateRemovalSuggestion(
section: DocumentationSection,
diff: CodeDiff,
): string {
let content = section.content;
// Remove references to the deleted symbol
const symbolRegex = new RegExp(`\\b${diff.name}\\b`, "g");
content = content.replace(symbolRegex, `~~${diff.name}~~ (removed)`);
// Add deprecation notice
const notice = `\n\n> **Note**: The \`${diff.name}\` ${diff.category} has been removed in the latest version.\n`;
content = notice + content;
return content;
}
/**
* Generate suggestion for added code
*/
private generateAdditionSuggestion(
section: DocumentationSection,
diff: CodeDiff,
_snapshot: DriftSnapshot,
): string {
let content = section.content;
// Add new section for the added symbol
const additionNotice = `\n\n## ${diff.name}\n\nA new ${diff.category} has been added.\n\n`;
// Try to extract signature if available
if (diff.newSignature) {
content +=
additionNotice + `\`\`\`typescript\n${diff.newSignature}\n\`\`\`\n`;
} else {
content +=
additionNotice +
`> **Documentation needed**: Please document the \`${diff.name}\` ${diff.category}.\n`;
}
return content;
}
/**
* Generate suggestion for modified code
*/
private generateModificationSuggestion(
section: DocumentationSection,
diff: CodeDiff,
_snapshot: DriftSnapshot,
): string {
let content = section.content;
// Update signature references
if (diff.oldSignature && diff.newSignature) {
content = content.replace(diff.oldSignature, diff.newSignature);
}
// Add update notice
const updateNotice = `\n\n> **Updated**: ${diff.details}\n`;
content = updateNotice + content;
return content;
}
/**
* Check if a section is affected by a code change
*/
private isSectionAffected(
section: DocumentationSection,
diff: CodeDiff,
): boolean {
switch (diff.category) {
case "function":
return section.referencedFunctions.includes(diff.name);
case "class":
return section.referencedClasses.includes(diff.name);
case "interface":
case "type":
return section.referencedTypes.includes(diff.name);
default:
return false;
}
}
/**
* Find documentation files that reference changed code
*/
private findAffectedDocumentation(
filePath: string,
codeDiffs: CodeDiff[],
documentation: Map<string, DocumentationSnapshot>,
): string[] {
const affected: string[] = [];
for (const [docPath, docSnapshot] of documentation) {
// Check if doc references the changed file
if (docSnapshot.referencedCode.includes(filePath)) {
affected.push(docPath);
continue;
}
// Check if doc references changed symbols
for (const diff of codeDiffs) {
for (const section of docSnapshot.sections) {
if (this.isSectionAffected(section, diff)) {
affected.push(docPath);
break;
}
}
}
}
return [...new Set(affected)];
}
/**
* Analyze a documentation file
*/
private async analyzeDocumentation(
docPath: string,
): Promise<DocumentationSnapshot | null> {
try {
const content = await fs.readFile(docPath, "utf-8");
const crypto = await import("crypto");
const contentHash = crypto
.createHash("sha256")
.update(content)
.digest("hex");
const stats = await fs.stat(docPath);
const sections = this.extractDocSections(content);
const referencedCode = this.extractCodeReferences(content);
return {
filePath: docPath,
contentHash,
referencedCode,
lastUpdated: stats.mtime.toISOString(),
sections,
};
} catch (error) {
console.warn(`Failed to analyze documentation ${docPath}:`, error);
return null;
}
}
/**
* Extract sections from documentation
*/
private extractDocSections(content: string): DocumentationSection[] {
const sections: DocumentationSection[] = [];
const lines = content.split("\n");
let currentSection: Partial<DocumentationSection> | null = null;
let currentContent: string[] = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Detect headings
const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
if (headingMatch) {
// Save previous section
if (currentSection) {
currentSection.content = currentContent.join("\n");
currentSection.endLine = i - 1;
sections.push(currentSection as DocumentationSection);
}
const title = headingMatch[2];
const referencedFunctions: string[] = [];
const referencedClasses: string[] = [];
// Extract function name from heading if it looks like a function signature
// e.g., "## calculate(x: number): number" or "## myFunction()"
const funcMatch = title.match(/^([a-z][A-Za-z0-9_]*)\s*\(/);
if (funcMatch) {
referencedFunctions.push(funcMatch[1]);
}
// Extract class name from heading if it starts with uppercase
const classMatch = title.match(/^([A-Z][A-Za-z0-9_]*)/);
if (classMatch && !funcMatch) {
referencedClasses.push(classMatch[1]);
}
// Start new section
currentSection = {
title,
startLine: i,
referencedFunctions,
referencedClasses,
referencedTypes: [],
codeExamples: [],
};
currentContent = [];
} else if (currentSection) {
currentContent.push(line);
// Extract code examples
if (line.startsWith("```")) {
const langMatch = line.match(/```(\w+)/);
const language = langMatch ? langMatch[1] : "text";
const codeLines: string[] = [];
i++;
while (i < lines.length && !lines[i].startsWith("```")) {
codeLines.push(lines[i]);
i++;
}
const codeExample: CodeExample = {
language,
code: codeLines.join("\n"),
description: "",
referencedSymbols: this.extractSymbolsFromCode(
codeLines.join("\n"),
),
};
currentSection.codeExamples!.push(codeExample);
}
// Extract inline code references (with or without parentheses for functions)
const inlineCodeMatches = line.matchAll(
/`([A-Za-z_][A-Za-z0-9_]*)\(\)?`/g,
);
for (const match of inlineCodeMatches) {
const symbol = match[1];
// Heuristic: CamelCase = class/type, camelCase = function
if (/^[A-Z]/.test(symbol)) {
if (!currentSection.referencedClasses!.includes(symbol)) {
currentSection.referencedClasses!.push(symbol);
}
} else {
if (!currentSection.referencedFunctions!.includes(symbol)) {
currentSection.referencedFunctions!.push(symbol);
}
}
}
// Also extract identifiers without parentheses
const plainIdentifiers = line.matchAll(/`([A-Za-z_][A-Za-z0-9_]*)`/g);
for (const match of plainIdentifiers) {
const symbol = match[1];
if (/^[A-Z]/.test(symbol)) {
if (!currentSection.referencedClasses!.includes(symbol)) {
currentSection.referencedClasses!.push(symbol);
}
} else {
if (!currentSection.referencedFunctions!.includes(symbol)) {
currentSection.referencedFunctions!.push(symbol);
}
}
}
}
}
// Save last section
if (currentSection) {
currentSection.content = currentContent.join("\n");
currentSection.endLine = lines.length - 1;
sections.push(currentSection as DocumentationSection);
}
return sections;
}
/**
* Extract code file references from documentation
*/
private extractCodeReferences(content: string): string[] {
const references: string[] = [];
// Extract from markdown links
const linkMatches = content.matchAll(
/\[.*?\]\((.*?\.(ts|js|py|go|rs|java|rb).*?)\)/g,
);
for (const match of linkMatches) {
references.push(match[1]);
}
// Extract from inline code
const codeMatches = content.matchAll(
/`([^`]+\.(ts|js|py|go|rs|java|rb))`/g,
);
for (const match of codeMatches) {
references.push(match[1]);
}
return [...new Set(references)];
}
/**
* Extract symbols from code examples
*/
private extractSymbolsFromCode(code: string): string[] {
const symbols: string[] = [];
// Extract function calls
const functionMatches = code.matchAll(/\b([a-z][A-Za-z0-9_]*)\s*\(/g);
for (const match of functionMatches) {
symbols.push(match[1]);
}
// Extract class/type references
const classMatches = code.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
for (const match of classMatches) {
symbols.push(match[1]);
}
return [...new Set(symbols)];
}
/**
* Find all source files in project
*/
private async findSourceFiles(projectPath: string): Promise<string[]> {
const files: string[] = [];
const extensions = [
".ts",
".tsx",
".js",
".jsx",
".py",
".go",
".rs",
".java",
".rb",
];
const walk = async (dir: string) => {
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (
!["node_modules", "dist", "build", ".git", ".next"].includes(
entry.name,
)
) {
await walk(fullPath);
}
} else {
const ext = path.extname(entry.name);
if (extensions.includes(ext)) {
files.push(fullPath);
}
}
}
} catch (error) {
console.warn(`Failed to read directory ${dir}:`, error);
}
};
await walk(projectPath);
return files;
}
/**
* Find all documentation files
*/
private async findDocumentationFiles(docsPath: string): Promise<string[]> {
const files: string[] = [];
const walk = async (dir: string) => {
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
await walk(fullPath);
} else if (
entry.name.endsWith(".md") ||
entry.name.endsWith(".mdx")
) {
files.push(fullPath);
}
}
} catch (error) {
console.warn(`Failed to read documentation directory ${dir}:`, error);
}
};
try {
await walk(docsPath);
} catch {
// Docs path doesn't exist
}
return files;
}
/**
* Save snapshot to disk
*/
private async saveSnapshot(snapshot: DriftSnapshot): Promise<void> {
const timestamp = new Date().toISOString().replace(/:/g, "-");
const snapshotPath = path.join(
this.snapshotDir,
`snapshot-${timestamp}.json`,
);
// Convert Maps to objects for JSON serialization
const serializable = {
projectPath: snapshot.projectPath,
timestamp: snapshot.timestamp,
files: Object.fromEntries(snapshot.files),
documentation: Object.fromEntries(snapshot.documentation),
};
await fs.writeFile(snapshotPath, JSON.stringify(serializable, null, 2));
}
/**
* Load the latest snapshot
*/
async loadLatestSnapshot(): Promise<DriftSnapshot | null> {
try {
const files = await fs.readdir(this.snapshotDir);
const snapshotFiles = files
.filter((f) => f.startsWith("snapshot-"))
.sort()
.reverse();
if (snapshotFiles.length === 0) return null;
const latestPath = path.join(this.snapshotDir, snapshotFiles[0]);
const content = await fs.readFile(latestPath, "utf-8");
const data = JSON.parse(content);
return {
projectPath: data.projectPath,
timestamp: data.timestamp,
files: new Map(Object.entries(data.files)),
documentation: new Map(Object.entries(data.documentation)),
};
} catch {
return null;
}
}
// Helper methods
private determineDriftType(
diff: CodeDiff,
): "outdated" | "incorrect" | "missing" | "breaking" {
if (diff.impactLevel === "breaking") return "breaking";
if (diff.type === "removed") return "incorrect";
if (diff.type === "modified") return "outdated";
return "missing";
}
private generateDriftDescription(diff: CodeDiff): string {
const action =
diff.type === "added"
? "added"
: diff.type === "removed"
? "removed"
: "modified";
return `${diff.category} '${diff.name}' was ${action}: ${diff.details}`;
}
private mapImpactToSeverity(
impact: "breaking" | "major" | "minor" | "patch",
): "low" | "medium" | "high" | "critical" {
switch (impact) {
case "breaking":
return "critical";
case "major":
return "high";
case "minor":
return "medium";
case "patch":
return "low";
}
}
private estimateUpdateEffort(
drifts: DocumentationDrift[],
): "low" | "medium" | "high" {
const critical = drifts.filter((d) => d.severity === "critical").length;
const high = drifts.filter((d) => d.severity === "high").length;
if (critical > 0 || high > 5) return "high";
if (high > 0 || drifts.length > 10) return "medium";
return "low";
}
private calculateOverallSeverity(
drifts: DocumentationDrift[],
): "none" | "low" | "medium" | "high" | "critical" {
if (drifts.length === 0) return "none";
const hasCritical = drifts.some((d) => d.severity === "critical");
if (hasCritical) return "critical";
const hasHigh = drifts.some((d) => d.severity === "high");
if (hasHigh) return "high";
const hasMedium = drifts.some((d) => d.severity === "medium");
if (hasMedium) return "medium";
return "low";
}
}
```