#
tokens: 49423/50000 13/307 files (page 8/23)
lines: off (toggle) GitHub
raw markdown copy
This is page 8 of 23. Use http://codebase.md/tosin2013/documcp?page={x} to view the full context.

# Directory Structure

```
├── .dockerignore
├── .eslintignore
├── .eslintrc.json
├── .github
│   ├── agents
│   │   ├── documcp-ast.md
│   │   ├── documcp-deploy.md
│   │   ├── documcp-memory.md
│   │   ├── documcp-test.md
│   │   └── documcp-tool.md
│   ├── copilot-instructions.md
│   ├── dependabot.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── automated-changelog.md
│   │   ├── bug_report.md
│   │   ├── bug_report.yml
│   │   ├── documentation_issue.md
│   │   ├── feature_request.md
│   │   ├── feature_request.yml
│   │   ├── npm-publishing-fix.md
│   │   └── release_improvements.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── release-drafter.yml
│   └── workflows
│       ├── auto-merge.yml
│       ├── ci.yml
│       ├── codeql.yml
│       ├── dependency-review.yml
│       ├── deploy-docs.yml
│       ├── README.md
│       ├── release-drafter.yml
│       └── release.yml
├── .gitignore
├── .husky
│   ├── commit-msg
│   └── pre-commit
├── .linkcheck.config.json
├── .markdown-link-check.json
├── .nvmrc
├── .pre-commit-config.yaml
├── .versionrc.json
├── ARCHITECTURAL_CHANGES_SUMMARY.md
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── commitlint.config.js
├── CONTRIBUTING.md
├── docker-compose.docs.yml
├── Dockerfile.docs
├── docs
│   ├── .docusaurus
│   │   ├── docusaurus-plugin-content-docs
│   │   │   └── default
│   │   │       └── __mdx-loader-dependency.json
│   │   └── docusaurus-plugin-content-pages
│   │       └── default
│   │           └── __plugin.json
│   ├── adrs
│   │   ├── adr-0001-mcp-server-architecture.md
│   │   ├── adr-0002-repository-analysis-engine.md
│   │   ├── adr-0003-static-site-generator-recommendation-engine.md
│   │   ├── adr-0004-diataxis-framework-integration.md
│   │   ├── adr-0005-github-pages-deployment-automation.md
│   │   ├── adr-0006-mcp-tools-api-design.md
│   │   ├── adr-0007-mcp-prompts-and-resources-integration.md
│   │   ├── adr-0008-intelligent-content-population-engine.md
│   │   ├── adr-0009-content-accuracy-validation-framework.md
│   │   ├── adr-0010-mcp-resource-pattern-redesign.md
│   │   ├── adr-0011-ce-mcp-compatibility.md
│   │   ├── adr-0012-priority-scoring-system-for-documentation-drift.md
│   │   ├── adr-0013-release-pipeline-and-package-distribution.md
│   │   └── README.md
│   ├── api
│   │   ├── .nojekyll
│   │   ├── assets
│   │   │   ├── hierarchy.js
│   │   │   ├── highlight.css
│   │   │   ├── icons.js
│   │   │   ├── icons.svg
│   │   │   ├── main.js
│   │   │   ├── navigation.js
│   │   │   ├── search.js
│   │   │   └── style.css
│   │   ├── hierarchy.html
│   │   ├── index.html
│   │   ├── modules.html
│   │   └── variables
│   │       └── TOOLS.html
│   ├── assets
│   │   └── logo.svg
│   ├── CE-MCP-FINDINGS.md
│   ├── development
│   │   └── MCP_INSPECTOR_TESTING.md
│   ├── docusaurus.config.js
│   ├── explanation
│   │   ├── architecture.md
│   │   └── index.md
│   ├── guides
│   │   ├── link-validation.md
│   │   ├── playwright-integration.md
│   │   └── playwright-testing-workflow.md
│   ├── how-to
│   │   ├── analytics-setup.md
│   │   ├── change-watcher.md
│   │   ├── custom-domains.md
│   │   ├── documentation-freshness-tracking.md
│   │   ├── drift-priority-scoring.md
│   │   ├── github-pages-deployment.md
│   │   ├── index.md
│   │   ├── llm-integration.md
│   │   ├── local-testing.md
│   │   ├── performance-optimization.md
│   │   ├── prompting-guide.md
│   │   ├── repository-analysis.md
│   │   ├── seo-optimization.md
│   │   ├── site-monitoring.md
│   │   ├── troubleshooting.md
│   │   └── usage-examples.md
│   ├── index.md
│   ├── knowledge-graph.md
│   ├── package-lock.json
│   ├── package.json
│   ├── phase-2-intelligence.md
│   ├── reference
│   │   ├── api-overview.md
│   │   ├── cli.md
│   │   ├── configuration.md
│   │   ├── deploy-pages.md
│   │   ├── index.md
│   │   ├── mcp-tools.md
│   │   └── prompt-templates.md
│   ├── research
│   │   ├── cross-domain-integration
│   │   │   └── README.md
│   │   ├── domain-1-mcp-architecture
│   │   │   ├── index.md
│   │   │   └── mcp-performance-research.md
│   │   ├── domain-2-repository-analysis
│   │   │   └── README.md
│   │   ├── domain-3-ssg-recommendation
│   │   │   ├── index.md
│   │   │   └── ssg-performance-analysis.md
│   │   ├── domain-4-diataxis-integration
│   │   │   └── README.md
│   │   ├── domain-5-github-deployment
│   │   │   ├── github-pages-security-analysis.md
│   │   │   └── index.md
│   │   ├── domain-6-api-design
│   │   │   └── README.md
│   │   ├── README.md
│   │   ├── research-integration-summary-2025-01-14.md
│   │   ├── research-progress-template.md
│   │   └── research-questions-2025-01-14.md
│   ├── robots.txt
│   ├── sidebars.js
│   ├── sitemap.xml
│   ├── src
│   │   └── css
│   │       └── custom.css
│   └── tutorials
│       ├── development-setup.md
│       ├── environment-setup.md
│       ├── first-deployment.md
│       ├── getting-started.md
│       ├── index.md
│       ├── memory-workflows.md
│       └── user-onboarding.md
├── ISSUE_IMPLEMENTATION_SUMMARY.md
├── jest.config.js
├── LICENSE
├── Makefile
├── MCP_PHASE2_IMPLEMENTATION.md
├── mcp-config-example.json
├── mcp.json
├── package-lock.json
├── package.json
├── README.md
├── release.sh
├── scripts
│   └── check-package-structure.cjs
├── SECURITY.md
├── setup-precommit.sh
├── src
│   ├── benchmarks
│   │   └── performance.ts
│   ├── index.ts
│   ├── memory
│   │   ├── contextual-retrieval.ts
│   │   ├── deployment-analytics.ts
│   │   ├── enhanced-manager.ts
│   │   ├── export-import.ts
│   │   ├── freshness-kg-integration.ts
│   │   ├── index.ts
│   │   ├── integration.ts
│   │   ├── kg-code-integration.ts
│   │   ├── kg-health.ts
│   │   ├── kg-integration.ts
│   │   ├── kg-link-validator.ts
│   │   ├── kg-storage.ts
│   │   ├── knowledge-graph.ts
│   │   ├── learning.ts
│   │   ├── manager.ts
│   │   ├── multi-agent-sharing.ts
│   │   ├── pruning.ts
│   │   ├── schemas.ts
│   │   ├── storage.ts
│   │   ├── temporal-analysis.ts
│   │   ├── user-preferences.ts
│   │   └── visualization.ts
│   ├── prompts
│   │   └── technical-writer-prompts.ts
│   ├── scripts
│   │   └── benchmark.ts
│   ├── templates
│   │   └── playwright
│   │       ├── accessibility.spec.template.ts
│   │       ├── Dockerfile.template
│   │       ├── docs-e2e.workflow.template.yml
│   │       ├── link-validation.spec.template.ts
│   │       └── playwright.config.template.ts
│   ├── tools
│   │   ├── analyze-deployments.ts
│   │   ├── analyze-readme.ts
│   │   ├── analyze-repository.ts
│   │   ├── change-watcher.ts
│   │   ├── check-documentation-links.ts
│   │   ├── cleanup-agent-artifacts.ts
│   │   ├── deploy-pages.ts
│   │   ├── detect-gaps.ts
│   │   ├── evaluate-readme-health.ts
│   │   ├── generate-config.ts
│   │   ├── generate-contextual-content.ts
│   │   ├── generate-llm-context.ts
│   │   ├── generate-readme-template.ts
│   │   ├── generate-technical-writer-prompts.ts
│   │   ├── kg-health-check.ts
│   │   ├── manage-preferences.ts
│   │   ├── manage-sitemap.ts
│   │   ├── optimize-readme.ts
│   │   ├── populate-content.ts
│   │   ├── readme-best-practices.ts
│   │   ├── recommend-ssg.ts
│   │   ├── setup-playwright-tests.ts
│   │   ├── setup-structure.ts
│   │   ├── simulate-execution.ts
│   │   ├── sync-code-to-docs.ts
│   │   ├── test-local-deployment.ts
│   │   ├── track-documentation-freshness.ts
│   │   ├── update-existing-documentation.ts
│   │   ├── validate-content.ts
│   │   ├── validate-documentation-freshness.ts
│   │   ├── validate-readme-checklist.ts
│   │   └── verify-deployment.ts
│   ├── types
│   │   └── api.ts
│   ├── utils
│   │   ├── artifact-detector.ts
│   │   ├── ast-analyzer.ts
│   │   ├── change-watcher.ts
│   │   ├── code-scanner.ts
│   │   ├── content-extractor.ts
│   │   ├── drift-detector.ts
│   │   ├── execution-simulator.ts
│   │   ├── freshness-tracker.ts
│   │   ├── language-parsers-simple.ts
│   │   ├── llm-client.ts
│   │   ├── permission-checker.ts
│   │   ├── semantic-analyzer.ts
│   │   ├── sitemap-generator.ts
│   │   ├── usage-metadata.ts
│   │   └── user-feedback-integration.ts
│   └── workflows
│       └── documentation-workflow.ts
├── test-docs-local.sh
├── tests
│   ├── api
│   │   └── mcp-responses.test.ts
│   ├── benchmarks
│   │   └── performance.test.ts
│   ├── call-graph-builder.test.ts
│   ├── change-watcher-priority.integration.test.ts
│   ├── change-watcher.test.ts
│   ├── edge-cases
│   │   └── error-handling.test.ts
│   ├── execution-simulator.test.ts
│   ├── functional
│   │   └── tools.test.ts
│   ├── integration
│   │   ├── kg-documentation-workflow.test.ts
│   │   ├── knowledge-graph-workflow.test.ts
│   │   ├── mcp-readme-tools.test.ts
│   │   ├── memory-mcp-tools.test.ts
│   │   ├── readme-technical-writer.test.ts
│   │   └── workflow.test.ts
│   ├── memory
│   │   ├── contextual-retrieval.test.ts
│   │   ├── enhanced-manager.test.ts
│   │   ├── export-import.test.ts
│   │   ├── freshness-kg-integration.test.ts
│   │   ├── kg-code-integration.test.ts
│   │   ├── kg-health.test.ts
│   │   ├── kg-link-validator.test.ts
│   │   ├── kg-storage-validation.test.ts
│   │   ├── kg-storage.test.ts
│   │   ├── knowledge-graph-documentation-examples.test.ts
│   │   ├── knowledge-graph-enhanced.test.ts
│   │   ├── knowledge-graph.test.ts
│   │   ├── learning.test.ts
│   │   ├── manager-advanced.test.ts
│   │   ├── manager.test.ts
│   │   ├── mcp-resource-integration.test.ts
│   │   ├── mcp-tool-persistence.test.ts
│   │   ├── schemas-documentation-examples.test.ts
│   │   ├── schemas.test.ts
│   │   ├── storage.test.ts
│   │   ├── temporal-analysis.test.ts
│   │   └── user-preferences.test.ts
│   ├── performance
│   │   ├── memory-load-testing.test.ts
│   │   └── memory-stress-testing.test.ts
│   ├── prompts
│   │   ├── guided-workflow-prompts.test.ts
│   │   └── technical-writer-prompts.test.ts
│   ├── server.test.ts
│   ├── setup.ts
│   ├── tools
│   │   ├── all-tools.test.ts
│   │   ├── analyze-coverage.test.ts
│   │   ├── analyze-deployments.test.ts
│   │   ├── analyze-readme.test.ts
│   │   ├── analyze-repository.test.ts
│   │   ├── check-documentation-links.test.ts
│   │   ├── cleanup-agent-artifacts.test.ts
│   │   ├── deploy-pages-kg-retrieval.test.ts
│   │   ├── deploy-pages-tracking.test.ts
│   │   ├── deploy-pages.test.ts
│   │   ├── detect-gaps.test.ts
│   │   ├── evaluate-readme-health.test.ts
│   │   ├── generate-contextual-content.test.ts
│   │   ├── generate-llm-context.test.ts
│   │   ├── generate-readme-template.test.ts
│   │   ├── generate-technical-writer-prompts.test.ts
│   │   ├── kg-health-check.test.ts
│   │   ├── manage-sitemap.test.ts
│   │   ├── optimize-readme.test.ts
│   │   ├── readme-best-practices.test.ts
│   │   ├── recommend-ssg-historical.test.ts
│   │   ├── recommend-ssg-preferences.test.ts
│   │   ├── recommend-ssg.test.ts
│   │   ├── simple-coverage.test.ts
│   │   ├── sync-code-to-docs.test.ts
│   │   ├── test-local-deployment.test.ts
│   │   ├── tool-error-handling.test.ts
│   │   ├── track-documentation-freshness.test.ts
│   │   ├── validate-content.test.ts
│   │   ├── validate-documentation-freshness.test.ts
│   │   └── validate-readme-checklist.test.ts
│   ├── types
│   │   └── type-safety.test.ts
│   └── utils
│       ├── artifact-detector.test.ts
│       ├── ast-analyzer.test.ts
│       ├── content-extractor.test.ts
│       ├── drift-detector-diataxis.test.ts
│       ├── drift-detector-priority.test.ts
│       ├── drift-detector.test.ts
│       ├── freshness-tracker.test.ts
│       ├── llm-client.test.ts
│       ├── semantic-analyzer.test.ts
│       ├── sitemap-generator.test.ts
│       ├── usage-metadata.test.ts
│       └── user-feedback-integration.test.ts
├── tsconfig.json
└── typedoc.json
```

# Files

--------------------------------------------------------------------------------
/tests/memory/kg-link-validator.test.ts:
--------------------------------------------------------------------------------

```typescript
import { promises as fs } from "fs";
import path from "path";
import os from "os";
import {
  validateExternalLinks,
  validateAndStoreDocumentationLinks,
  extractLinksFromContent,
  storeLinkValidationInKG,
  getLinkValidationHistory,
} from "../../src/memory/kg-link-validator";
import { getKnowledgeGraph } from "../../src/memory/kg-integration";

describe("KG Link Validator", () => {
  let tempDir: string;
  const originalCwd = process.cwd();

  beforeEach(async () => {
    tempDir = path.join(os.tmpdir(), `kg-link-${Date.now()}`);
    await fs.mkdir(tempDir, { recursive: true });
    process.chdir(tempDir);
  });

  afterEach(async () => {
    process.chdir(originalCwd);
    try {
      await fs.rm(tempDir, { recursive: true, force: true });
    } catch {
      // Ignore cleanup errors
    }
  });

  describe("validateExternalLinks", () => {
    it("should validate valid URLs", async () => {
      const urls = ["https://www.google.com", "https://github.com"];

      const result = await validateExternalLinks(urls, {
        timeout: 5000,
      });

      expect(result.totalLinks).toBe(2);
      expect(result.results).toHaveLength(2);
      expect(result.validLinks + result.brokenLinks + result.unknownLinks).toBe(
        2,
      );
    });

    it("should detect broken links", async () => {
      const urls = ["https://this-domain-definitely-does-not-exist-12345.com"];

      const result = await validateExternalLinks(urls, {
        timeout: 3000,
      });

      expect(result.totalLinks).toBe(1);
      expect(result.results).toHaveLength(1);
      // Should be either broken or unknown
      expect(result.brokenLinks + result.unknownLinks).toBeGreaterThan(0);
    });

    it("should handle empty URL list", async () => {
      const result = await validateExternalLinks([]);

      expect(result.totalLinks).toBe(0);
      expect(result.results).toHaveLength(0);
    });

    it("should respect timeout option", async () => {
      const urls = ["https://www.google.com"];

      const startTime = Date.now();
      await validateExternalLinks(urls, {
        timeout: 1000,
      });
      const duration = Date.now() - startTime;

      // Should complete reasonably quickly
      expect(duration).toBeLessThan(10000);
    });

    it("should use default timeout when not provided", async () => {
      const urls = ["https://www.google.com"];

      const result = await validateExternalLinks(urls);

      expect(result).toBeDefined();
      expect(result.totalLinks).toBe(1);
    });

    it("should handle validation errors gracefully", async () => {
      const urls = ["https://httpstat.us/500"]; // Returns 500 error

      const result = await validateExternalLinks(urls, {
        timeout: 5000,
      });

      expect(result.totalLinks).toBe(1);
      // Should be marked as broken or unknown
      expect(result.brokenLinks + result.unknownLinks).toBeGreaterThan(0);
    });

    it("should count warning links correctly", async () => {
      const urls = ["https://httpstat.us/301"]; // Redirect

      const result = await validateExternalLinks(urls, {
        timeout: 5000,
      });

      expect(result.totalLinks).toBe(1);
      // Should handle redirects as valid (fetch follows redirects)
      expect(
        result.validLinks +
          result.brokenLinks +
          result.warningLinks +
          result.unknownLinks,
      ).toBe(1);
    });

    it("should handle network errors in validation loop", async () => {
      const urls = ["https://invalid-url-12345.test", "https://www.google.com"];

      const result = await validateExternalLinks(urls, {
        timeout: 3000,
      });

      expect(result.totalLinks).toBe(2);
      expect(result.results).toHaveLength(2);
    });

    it("should include response time in valid results", async () => {
      const urls = ["https://www.google.com"];

      const result = await validateExternalLinks(urls, {
        timeout: 5000,
      });

      expect(result.results[0].lastChecked).toBeDefined();
      if (result.results[0].status === "valid") {
        expect(result.results[0].responseTime).toBeDefined();
        expect(result.results[0].responseTime).toBeGreaterThan(0);
      }
    });

    it("should include response time in broken results", async () => {
      const urls = ["https://httpstat.us/404"];

      const result = await validateExternalLinks(urls, {
        timeout: 5000,
      });

      expect(result.results[0].lastChecked).toBeDefined();
      if (
        result.results[0].status === "broken" &&
        result.results[0].statusCode
      ) {
        expect(result.results[0].responseTime).toBeDefined();
      }
    });
  });

  describe("extractLinksFromContent", () => {
    it("should extract external links", () => {
      const content = `
        # Test
        [Google](https://www.google.com)
        [GitHub](https://github.com)
      `;

      const result = extractLinksFromContent(content);

      expect(result.externalLinks.length).toBeGreaterThan(0);
    });

    it("should extract internal links", () => {
      const content = `
        # Test
        [Page 1](./page1.md)
        [Page 2](../page2.md)
      `;

      const result = extractLinksFromContent(content);

      expect(result.internalLinks.length).toBeGreaterThan(0);
    });

    it("should handle mixed links", () => {
      const content = `
        # Test
        [External](https://example.com)
        [Internal](./page.md)
      `;

      const result = extractLinksFromContent(content);

      expect(result.externalLinks.length).toBeGreaterThan(0);
      expect(result.internalLinks.length).toBeGreaterThan(0);
    });

    it("should extract HTTP links", () => {
      const content = `[Link](http://example.com)`;

      const result = extractLinksFromContent(content);

      expect(result.externalLinks).toContain("http://example.com");
    });

    it("should extract HTML anchor links", () => {
      const content = `<a href="https://example.com">Link</a>`;

      const result = extractLinksFromContent(content);

      expect(result.externalLinks).toContain("https://example.com");
    });

    it("should extract HTML anchor links with single quotes", () => {
      const content = `<a href='https://example.com'>Link</a>`;

      const result = extractLinksFromContent(content);

      expect(result.externalLinks).toContain("https://example.com");
    });

    it("should extract internal HTML links", () => {
      const content = `<a href="./page.md">Link</a>`;

      const result = extractLinksFromContent(content);

      expect(result.internalLinks).toContain("./page.md");
    });

    it("should remove duplicate links", () => {
      const content = `
        [Link1](https://example.com)
        [Link2](https://example.com)
        [Link3](./page.md)
        [Link4](./page.md)
      `;

      const result = extractLinksFromContent(content);

      expect(result.externalLinks.length).toBe(1);
      expect(result.internalLinks.length).toBe(1);
    });

    it("should handle content with no links", () => {
      const content = "# Test\nNo links here";

      const result = extractLinksFromContent(content);

      expect(result.externalLinks).toEqual([]);
      expect(result.internalLinks).toEqual([]);
    });
  });

  describe("validateAndStoreDocumentationLinks", () => {
    it("should validate and store documentation links", async () => {
      const content =
        "# Test\n[Link](./other.md)\n[External](https://example.com)";

      const result = await validateAndStoreDocumentationLinks(
        "test-project",
        content,
      );

      expect(result).toBeDefined();
      expect(result.totalLinks).toBeGreaterThan(0);
    });

    it("should handle documentation without links", async () => {
      const content = "# Test\nNo links here";

      const result = await validateAndStoreDocumentationLinks(
        "test-project",
        content,
      );

      expect(result).toBeDefined();
      expect(result.totalLinks).toBe(0);
    });

    it("should handle content with only internal links", async () => {
      const content = "# Test\n[Page](./page.md)";

      const result = await validateAndStoreDocumentationLinks(
        "test-project",
        content,
      );

      expect(result).toBeDefined();
      // Only external links are validated
      expect(result.totalLinks).toBe(0);
    });
  });

  describe("storeLinkValidationInKG", () => {
    it("should store validation results with no broken links", async () => {
      const summary = {
        totalLinks: 5,
        validLinks: 5,
        brokenLinks: 0,
        warningLinks: 0,
        unknownLinks: 0,
        results: [],
      };

      await storeLinkValidationInKG("doc-section-1", summary);

      const kg = await getKnowledgeGraph();
      const nodes = await kg.getAllNodes();

      // Find the specific validation node for this test
      const validationNode = nodes.find(
        (n) =>
          n.type === "link_validation" &&
          n.properties.totalLinks === 5 &&
          n.properties.brokenLinks === 0,
      );
      expect(validationNode).toBeDefined();
      expect(validationNode?.properties.totalLinks).toBe(5);
      expect(validationNode?.properties.healthScore).toBe(100);
    });

    it("should store validation results with broken links", async () => {
      const summary = {
        totalLinks: 10,
        validLinks: 7,
        brokenLinks: 3,
        warningLinks: 0,
        unknownLinks: 0,
        results: [
          {
            url: "https://broken1.com",
            status: "broken" as const,
            lastChecked: new Date().toISOString(),
          },
          {
            url: "https://broken2.com",
            status: "broken" as const,
            lastChecked: new Date().toISOString(),
          },
          {
            url: "https://broken3.com",
            status: "broken" as const,
            lastChecked: new Date().toISOString(),
          },
        ],
      };

      await storeLinkValidationInKG("doc-section-2", summary);

      const kg = await getKnowledgeGraph();
      const edges = await kg.findEdges({
        source: "doc-section-2",
        type: "has_link_validation",
      });

      expect(edges.length).toBeGreaterThan(0);
    });

    it("should create requires_fix edge for broken links", async () => {
      const summary = {
        totalLinks: 10,
        validLinks: 4,
        brokenLinks: 6,
        warningLinks: 0,
        unknownLinks: 0,
        results: [
          {
            url: "https://broken.com",
            status: "broken" as const,
            lastChecked: new Date().toISOString(),
          },
        ],
      };

      await storeLinkValidationInKG("doc-section-3", summary);

      const kg = await getKnowledgeGraph();
      const allNodes = await kg.getAllNodes();
      const validationNode = allNodes.find(
        (n) => n.type === "link_validation" && n.properties.brokenLinks === 6,
      );

      expect(validationNode).toBeDefined();

      const requiresFixEdges = await kg.findEdges({
        source: validationNode!.id,
        type: "requires_fix",
      });

      expect(requiresFixEdges.length).toBeGreaterThan(0);
      expect(requiresFixEdges[0].properties.severity).toBe("high"); // > 5 broken links
    });

    it("should set medium severity for few broken links", async () => {
      const summary = {
        totalLinks: 10,
        validLinks: 8,
        brokenLinks: 2,
        warningLinks: 0,
        unknownLinks: 0,
        results: [
          {
            url: "https://broken.com",
            status: "broken" as const,
            lastChecked: new Date().toISOString(),
          },
        ],
      };

      await storeLinkValidationInKG("doc-section-4", summary);

      const kg = await getKnowledgeGraph();
      const allNodes = await kg.getAllNodes();
      const validationNode = allNodes.find(
        (n) => n.type === "link_validation" && n.properties.brokenLinks === 2,
      );

      const requiresFixEdges = await kg.findEdges({
        source: validationNode!.id,
        type: "requires_fix",
      });

      expect(requiresFixEdges[0].properties.severity).toBe("medium");
    });

    it("should calculate health score correctly", async () => {
      const summary = {
        totalLinks: 20,
        validLinks: 15,
        brokenLinks: 5,
        warningLinks: 0,
        unknownLinks: 0,
        results: [],
      };

      await storeLinkValidationInKG("doc-section-5", summary);

      const kg = await getKnowledgeGraph();
      const nodes = await kg.getAllNodes();

      const validationNode = nodes.find(
        (n) => n.type === "link_validation" && n.properties.totalLinks === 20,
      );

      expect(validationNode?.properties.healthScore).toBe(75);
    });

    it("should handle zero links with 100% health score", async () => {
      const summary = {
        totalLinks: 0,
        validLinks: 0,
        brokenLinks: 0,
        warningLinks: 0,
        unknownLinks: 0,
        results: [],
      };

      await storeLinkValidationInKG("doc-section-6", summary);

      const kg = await getKnowledgeGraph();
      const nodes = await kg.getAllNodes();

      const validationNode = nodes.find(
        (n) => n.type === "link_validation" && n.properties.totalLinks === 0,
      );

      expect(validationNode?.properties.healthScore).toBe(100);
    });
  });

  describe("getLinkValidationHistory", () => {
    it("should retrieve validation history", async () => {
      const summary1 = {
        totalLinks: 5,
        validLinks: 5,
        brokenLinks: 0,
        warningLinks: 0,
        unknownLinks: 0,
        results: [],
      };

      await storeLinkValidationInKG("doc-section-7", summary1);

      const history = await getLinkValidationHistory("doc-section-7");

      expect(history.length).toBeGreaterThan(0);
      expect(history[0].type).toBe("link_validation");
    });

    it("should return empty array for non-existent doc section", async () => {
      const history = await getLinkValidationHistory("non-existent");

      expect(history).toEqual([]);
    });

    it("should sort history by newest first", async () => {
      // Add two validations with delay to ensure different timestamps
      const summary1 = {
        totalLinks: 5,
        validLinks: 5,
        brokenLinks: 0,
        warningLinks: 0,
        unknownLinks: 0,
        results: [],
      };

      await storeLinkValidationInKG("doc-section-8", summary1);

      // Small delay to ensure different timestamp
      await new Promise((resolve) => setTimeout(resolve, 10));

      const summary2 = {
        totalLinks: 6,
        validLinks: 6,
        brokenLinks: 0,
        warningLinks: 0,
        unknownLinks: 0,
        results: [],
      };

      await storeLinkValidationInKG("doc-section-8", summary2);

      const history = await getLinkValidationHistory("doc-section-8");

      expect(history.length).toBeGreaterThan(1);
      // First item should be newest
      const firstTimestamp = new Date(
        history[0].properties.lastValidated,
      ).getTime();
      const secondTimestamp = new Date(
        history[1].properties.lastValidated,
      ).getTime();
      expect(firstTimestamp).toBeGreaterThanOrEqual(secondTimestamp);
    });
  });
});

```

--------------------------------------------------------------------------------
/tests/memory/manager-advanced.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for uncovered branches in Memory Manager
 * Covers: getRelated (lines 171-202), export (lines 381-398), import (lines 409-415)
 */

import { promises as fs } from "fs";
import path from "path";
import os from "os";
import { MemoryManager } from "../../src/memory/manager.js";
import { MemoryEntry } from "../../src/memory/storage.js";

describe("MemoryManager - Advanced Features Coverage", () => {
  let manager: MemoryManager;
  let tempDir: string;

  beforeEach(async () => {
    tempDir = path.join(
      os.tmpdir(),
      `manager-advanced-test-${Date.now()}-${Math.random()
        .toString(36)
        .substr(2, 9)}`,
    );
    await fs.mkdir(tempDir, { recursive: true });
    manager = new MemoryManager(tempDir);
    await manager.initialize();
  });

  afterEach(async () => {
    try {
      await manager.close();
      await fs.rm(tempDir, { recursive: true, force: true });
    } catch (error) {
      // Ignore cleanup errors
    }
  });

  describe("getRelated - Tag-based Relationships (lines 189-195)", () => {
    it("should find related memories by overlapping tags", async () => {
      // Create entries with overlapping tags
      const entry1 = await manager.remember(
        "analysis",
        { name: "Project A" },
        {
          projectId: "proj-001",
          tags: ["typescript", "react", "frontend"],
        },
      );

      await manager.remember(
        "analysis",
        { name: "Project B" },
        {
          projectId: "proj-002",
          tags: ["typescript", "vue", "frontend"],
        },
      );

      await manager.remember(
        "analysis",
        { name: "Project C" },
        {
          projectId: "proj-003",
          tags: ["python", "backend"],
        },
      );

      // Get related memories for entry1 (should find project B via overlapping tags)
      const related = await manager.getRelated(entry1, 10);

      expect(related.length).toBeGreaterThan(0);

      // Should include Project B (shares typescript and frontend tags)
      const relatedNames = related.map((r) => r.data.name);
      expect(relatedNames).toContain("Project B");

      // Should not include entry1 itself
      expect(relatedNames).not.toContain("Project A");
    });

    it("should find related memories by same type (lines 182-186)", async () => {
      const entry1 = await manager.remember(
        "recommendation",
        { ssg: "jekyll" },
        { projectId: "proj-001" },
      );

      await manager.remember(
        "recommendation",
        { ssg: "hugo" },
        { projectId: "proj-002" },
      );

      await manager.remember(
        "analysis",
        { type: "different" },
        { projectId: "proj-003" },
      );

      const related = await manager.getRelated(entry1, 10);

      // Should find the other recommendation, not the analysis
      expect(related.length).toBeGreaterThan(0);
      const types = related.map((r) => r.type);
      expect(types).toContain("recommendation");
    });

    it("should find related memories by same project (lines 174-179)", async () => {
      manager.setContext({ projectId: "shared-project" });

      const entry1 = await manager.remember(
        "analysis",
        { step: "step1" },
        { projectId: "shared-project" },
      );

      await manager.remember(
        "analysis",
        { step: "step2" },
        { projectId: "shared-project" },
      );

      await manager.remember(
        "analysis",
        { step: "step3" },
        { projectId: "different-project" },
      );

      const related = await manager.getRelated(entry1, 10);

      // Should find step2 from same project
      expect(related.length).toBeGreaterThan(0);
      const projectIds = related.map((r) => r.metadata.projectId);
      expect(projectIds).toContain("shared-project");
    });

    it("should deduplicate and limit related memories (lines 198-202)", async () => {
      const entry1 = await manager.remember(
        "analysis",
        { name: "Entry 1" },
        {
          projectId: "proj-001",
          tags: ["tag1", "tag2"],
        },
      );

      // Create many related entries
      for (let i = 0; i < 20; i++) {
        await manager.remember(
          "analysis",
          { name: `Entry ${i + 2}` },
          {
            projectId: "proj-001",
            tags: i < 10 ? ["tag1"] : ["tag2"],
          },
        );
      }

      // Request limit of 5
      const related = await manager.getRelated(entry1, 5);

      // Should be limited to 5 (deduplicated)
      expect(related.length).toBeLessThanOrEqual(5);

      // Should not include entry1 itself
      const names = related.map((r) => r.data.name);
      expect(names).not.toContain("Entry 1");
    });

    it("should handle entry without tags gracefully (line 189)", async () => {
      const entryNoTags = await manager.remember(
        "analysis",
        { name: "No Tags" },
        { projectId: "proj-001" },
      );

      await manager.remember(
        "analysis",
        { name: "Also No Tags" },
        { projectId: "proj-001" },
      );

      // Should still find related by project
      const related = await manager.getRelated(entryNoTags, 10);
      expect(related.length).toBeGreaterThan(0);
    });

    it("should handle entry with empty tags array (line 189)", async () => {
      const entryEmptyTags = await manager.remember(
        "analysis",
        { name: "Empty Tags" },
        {
          projectId: "proj-001",
          tags: [],
        },
      );

      await manager.remember(
        "analysis",
        { name: "Other Entry" },
        { projectId: "proj-001" },
      );

      const related = await manager.getRelated(entryEmptyTags, 10);
      expect(related.length).toBeGreaterThan(0);
    });
  });

  describe("CSV Export (lines 381-398)", () => {
    it("should export memories as CSV format", async () => {
      manager.setContext({ projectId: "csv-proj-001" });

      await manager.remember(
        "analysis",
        { test: "data1" },
        {
          repository: "github.com/test/repo1",
          ssg: "jekyll",
        },
      );

      manager.setContext({ projectId: "csv-proj-002" });

      await manager.remember(
        "recommendation",
        { test: "data2" },
        {
          repository: "github.com/test/repo2",
          ssg: "hugo",
        },
      );

      // Export as CSV
      const csvData = await manager.export("csv");

      // Verify CSV structure
      expect(csvData).toContain("id,timestamp,type,projectId,repository,ssg");
      expect(csvData).toContain("csv-proj-001");
      expect(csvData).toContain("csv-proj-002");
      expect(csvData).toContain("github.com/test/repo1");
      expect(csvData).toContain("github.com/test/repo2");
      expect(csvData).toContain("jekyll");
      expect(csvData).toContain("hugo");

      // Verify rows are comma-separated
      const lines = csvData.split("\n").filter((l) => l.trim());
      expect(lines.length).toBeGreaterThanOrEqual(3); // header + 2 rows

      // Each line should have the same number of commas
      const headerCommas = (lines[0].match(/,/g) || []).length;
      for (let i = 1; i < lines.length; i++) {
        const rowCommas = (lines[i].match(/,/g) || []).length;
        expect(rowCommas).toBe(headerCommas);
      }
    });

    it("should export memories for specific project only", async () => {
      manager.setContext({ projectId: "project-a" });
      await manager.remember("analysis", { project: "A" }, {});

      manager.setContext({ projectId: "project-b" });
      await manager.remember("analysis", { project: "B" }, {});

      // Export only project-a
      const csvData = await manager.export("csv", "project-a");

      expect(csvData).toContain("project-a");
      expect(csvData).not.toContain("project-b");
    });

    it("should handle missing metadata fields in CSV export (lines 393-395)", async () => {
      // Create entry with minimal metadata
      await manager.remember("analysis", { test: "minimal" }, {});

      const csvData = await manager.export("csv");

      // Should have empty fields for missing metadata
      const lines = csvData.split("\n");
      expect(lines.length).toBeGreaterThan(1);

      // Verify header
      expect(lines[0]).toContain("id,timestamp,type,projectId,repository,ssg");

      // Data row should have appropriate number of commas (empty fields)
      const dataRow = lines[1];
      const headerCommas = (lines[0].match(/,/g) || []).length;
      const dataCommas = (dataRow.match(/,/g) || []).length;
      expect(dataCommas).toBe(headerCommas);
    });

    it("should export as JSON by default", async () => {
      await manager.remember(
        "analysis",
        { json: "test" },
        { projectId: "json-proj" },
      );

      const jsonData = await manager.export("json");

      const parsed = JSON.parse(jsonData);
      expect(Array.isArray(parsed)).toBe(true);
      expect(parsed.length).toBeGreaterThan(0);
      expect(parsed[0].data.json).toBe("test");
    });
  });

  describe("CSV Import (lines 409-428)", () => {
    it("should import memories from CSV format", async () => {
      // Create CSV data
      const csvData = `id,timestamp,type,projectId,repository,ssg
mem-001,2024-01-01T00:00:00.000Z,analysis,proj-csv-001,github.com/test/repo1,jekyll
mem-002,2024-01-02T00:00:00.000Z,recommendation,proj-csv-002,github.com/test/repo2,hugo
mem-003,2024-01-03T00:00:00.000Z,deployment,proj-csv-003,github.com/test/repo3,mkdocs`;

      const imported = await manager.import(csvData, "csv");

      expect(imported).toBe(3);

      // Verify entries were imported
      const recalled1 = await manager.recall("mem-001");
      expect(recalled1).not.toBeNull();
      expect(recalled1?.type).toBe("analysis");
      expect(recalled1?.metadata.projectId).toBe("proj-csv-001");
      expect(recalled1?.metadata.ssg).toBe("jekyll");

      const recalled2 = await manager.recall("mem-002");
      expect(recalled2).not.toBeNull();
      expect(recalled2?.type).toBe("recommendation");
    });

    it("should skip malformed CSV rows (line 414)", async () => {
      // CSV with mismatched column counts
      const csvData = `id,timestamp,type,projectId,repository,ssg
mem-001,2024-01-01T00:00:00.000Z,analysis,proj-001,github.com/test/repo,jekyll
mem-002,2024-01-02T00:00:00.000Z,recommendation
mem-003,2024-01-03T00:00:00.000Z,deployment,proj-003,github.com/test/repo3,mkdocs`;

      const imported = await manager.import(csvData, "csv");

      // Should import 2 (skipping the malformed row)
      expect(imported).toBe(2);

      // Verify valid entries were imported
      const recalled1 = await manager.recall("mem-001");
      expect(recalled1).not.toBeNull();

      // Malformed entry should not be imported
      const recalled2 = await manager.recall("mem-002");
      expect(recalled2).toBeNull();

      const recalled3 = await manager.recall("mem-003");
      expect(recalled3).not.toBeNull();
    });

    it("should import memories from JSON format", async () => {
      const jsonData = JSON.stringify([
        {
          id: "json-001",
          timestamp: "2024-01-01T00:00:00.000Z",
          type: "analysis",
          data: { test: "json-import" },
          metadata: { projectId: "json-proj" },
        },
      ]);

      const imported = await manager.import(jsonData, "json");

      expect(imported).toBe(1);

      const recalled = await manager.recall("json-001");
      expect(recalled).not.toBeNull();
      expect(recalled?.data.test).toBe("json-import");
    });

    it("should emit import-complete event (line 437)", async () => {
      let eventEmitted = false;
      let importedCount = 0;

      manager.on("import-complete", (count) => {
        eventEmitted = true;
        importedCount = count;
      });

      const jsonData = JSON.stringify([
        {
          id: "event-001",
          timestamp: "2024-01-01T00:00:00.000Z",
          type: "analysis",
          data: {},
          metadata: {},
        },
      ]);

      await manager.import(jsonData, "json");

      expect(eventEmitted).toBe(true);
      expect(importedCount).toBe(1);
    });

    it("should handle empty CSV import gracefully", async () => {
      const csvData = `id,timestamp,type,projectId,repository,ssg`;

      const imported = await manager.import(csvData, "csv");

      expect(imported).toBe(0);
    });

    it("should handle empty JSON import gracefully", async () => {
      const jsonData = JSON.stringify([]);

      const imported = await manager.import(jsonData, "json");

      expect(imported).toBe(0);
    });
  });

  describe("Export and Import Round-trip", () => {
    it("should maintain data integrity through CSV round-trip", async () => {
      // Create test data
      manager.setContext({
        projectId: "roundtrip-proj",
        repository: "github.com/test/roundtrip",
      });
      const originalEntry = await manager.remember(
        "analysis",
        { roundtrip: "test" },
        {
          ssg: "docusaurus",
        },
      );

      // Export as CSV
      const csvData = await manager.export("csv");

      // Create new manager and import
      const tempDir2 = path.join(
        os.tmpdir(),
        `manager-roundtrip-${Date.now()}`,
      );
      await fs.mkdir(tempDir2, { recursive: true });
      const manager2 = new MemoryManager(tempDir2);
      await manager2.initialize();

      const imported = await manager2.import(csvData, "csv");
      expect(imported).toBeGreaterThan(0);

      // Verify data matches
      const recalled = await manager2.recall(originalEntry.id);
      expect(recalled).not.toBeNull();
      expect(recalled?.type).toBe(originalEntry.type);
      expect(recalled?.metadata.projectId).toBe(
        originalEntry.metadata.projectId,
      );
      expect(recalled?.metadata.ssg).toBe(originalEntry.metadata.ssg);

      await manager2.close();
      await fs.rm(tempDir2, { recursive: true, force: true });
    });

    it("should maintain data integrity through JSON round-trip", async () => {
      // Create test data with complex structure
      manager.setContext({ projectId: "json-roundtrip" });
      const originalEntry = await manager.remember(
        "analysis",
        {
          complex: "data",
          nested: { value: 123 },
          array: [1, 2, 3],
        },
        {
          tags: ["tag1", "tag2"],
        },
      );

      // Export as JSON
      const jsonData = await manager.export("json");

      // Create new manager and import
      const tempDir2 = path.join(
        os.tmpdir(),
        `manager-json-roundtrip-${Date.now()}`,
      );
      await fs.mkdir(tempDir2, { recursive: true });
      const manager2 = new MemoryManager(tempDir2);
      await manager2.initialize();

      const imported = await manager2.import(jsonData, "json");
      expect(imported).toBeGreaterThan(0);

      // Verify complex data maintained
      const recalled = await manager2.recall(originalEntry.id);
      expect(recalled).not.toBeNull();
      expect(recalled?.data).toEqual(originalEntry.data);
      expect(recalled?.metadata.tags).toEqual(originalEntry.metadata.tags);

      await manager2.close();
      await fs.rm(tempDir2, { recursive: true, force: true });
    });
  });
});

```

--------------------------------------------------------------------------------
/src/tools/evaluate-readme-health.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { promises as fs } from "fs";
import path from "path";
import { formatMCPResponse } from "../types/api.js";

// Input validation schema
const EvaluateReadmeHealthSchema = z.object({
  readme_path: z.string().min(1, "README path is required"),
  project_type: z
    .enum([
      "community_library",
      "enterprise_tool",
      "personal_project",
      "documentation",
    ])
    .optional()
    .default("community_library"),
  repository_path: z.string().optional(),
});

// Input type that matches what users actually pass (project_type is optional)
export interface EvaluateReadmeHealthInput {
  readme_path: string;
  project_type?:
    | "community_library"
    | "enterprise_tool"
    | "personal_project"
    | "documentation";
  repository_path?: string;
}

// Health score interfaces
interface HealthScoreComponent {
  name: string;
  score: number;
  maxScore: number;
  details: HealthCheckDetail[];
}

interface HealthCheckDetail {
  check: string;
  passed: boolean;
  points: number;
  maxPoints: number;
  recommendation?: string;
}

interface ReadmeHealthReport {
  overallScore: number;
  maxScore: number;
  grade: "A" | "B" | "C" | "D" | "F";
  components: {
    communityHealth: HealthScoreComponent;
    accessibility: HealthScoreComponent;
    onboarding: HealthScoreComponent;
    contentQuality: HealthScoreComponent;
  };
  recommendations: string[];
  strengths: string[];
  criticalIssues: string[];
  estimatedImprovementTime: string;
}

export async function evaluateReadmeHealth(input: EvaluateReadmeHealthInput) {
  const startTime = Date.now();
  try {
    // Validate input
    const validatedInput = EvaluateReadmeHealthSchema.parse(input);

    // Read README file
    const readmePath = path.resolve(validatedInput.readme_path);
    const readmeContent = await fs.readFile(readmePath, "utf-8");

    // Get repository context if available
    let repoContext: any = null;
    if (validatedInput.repository_path) {
      repoContext = await analyzeRepositoryContext(
        validatedInput.repository_path,
      );
    }

    // Evaluate all health components
    const communityHealth = evaluateCommunityHealth(readmeContent, repoContext);
    const accessibility = evaluateAccessibility(readmeContent);
    const onboarding = evaluateOnboarding(
      readmeContent,
      validatedInput.project_type,
    );
    const contentQuality = evaluateContentQuality(readmeContent);

    // Calculate overall score
    const totalScore =
      communityHealth.score +
      accessibility.score +
      onboarding.score +
      contentQuality.score;
    const maxTotalScore =
      communityHealth.maxScore +
      accessibility.maxScore +
      onboarding.maxScore +
      contentQuality.maxScore;
    const percentage = (totalScore / maxTotalScore) * 100;

    // Generate grade
    const grade = getGrade(percentage);

    // Generate recommendations and insights
    const recommendations = generateHealthRecommendations(
      [communityHealth, accessibility, onboarding, contentQuality],
      "general",
    );
    const strengths = identifyStrengths([
      communityHealth,
      accessibility,
      onboarding,
      contentQuality,
    ]);
    const criticalIssues = identifyCriticalIssues([
      communityHealth,
      accessibility,
      onboarding,
      contentQuality,
    ]);

    const report: ReadmeHealthReport = {
      overallScore: Math.round(percentage),
      maxScore: 100,
      grade,
      components: {
        communityHealth,
        accessibility,
        onboarding,
        contentQuality,
      },
      recommendations,
      strengths,
      criticalIssues,
      estimatedImprovementTime: estimateImprovementTime(
        recommendations.length,
        criticalIssues.length,
      ),
    };

    const response = {
      readmePath: validatedInput.readme_path,
      projectType: validatedInput.project_type,
      healthReport: report,
      summary: generateSummary(report),
      nextSteps: generateNextSteps(report),
    };

    return formatMCPResponse({
      success: true,
      data: response,
      metadata: {
        toolVersion: "1.0.0",
        executionTime: Date.now() - startTime,
        timestamp: new Date().toISOString(),
      },
    });
  } catch (error) {
    return formatMCPResponse({
      success: false,
      error: {
        code: "README_HEALTH_EVALUATION_FAILED",
        message: `Failed to evaluate README health: ${error}`,
        resolution: "Ensure README path is valid and file is readable",
      },
      metadata: {
        toolVersion: "1.0.0",
        executionTime: Date.now() - startTime,
        timestamp: new Date().toISOString(),
      },
    });
  }
}

function evaluateCommunityHealth(
  content: string,
  _repoContext: any,
): HealthScoreComponent {
  const checks: HealthCheckDetail[] = [
    {
      check: "Code of Conduct linked",
      passed: /code.of.conduct|conduct\.md|\.github\/code_of_conduct/i.test(
        content,
      ),
      points: 0,
      maxPoints: 5,
      recommendation:
        "Add a link to your Code of Conduct to establish community standards",
    },
    {
      check: "Contributing guidelines visible",
      passed: /contributing|contribute\.md|\.github\/contributing/i.test(
        content,
      ),
      points: 0,
      maxPoints: 5,
      recommendation:
        "Include contributing guidelines to help new contributors get started",
    },
    {
      check: "Issue/PR templates mentioned",
      passed:
        /issue.template|pull.request.template|\.github\/issue_template|\.github\/pull_request_template/i.test(
          content,
        ),
      points: 0,
      maxPoints: 5,
      recommendation:
        "Reference issue and PR templates to streamline contributions",
    },
    {
      check: "Security policy linked",
      passed: /security\.md|security.policy|\.github\/security/i.test(content),
      points: 0,
      maxPoints: 5,
      recommendation:
        "Add a security policy to handle vulnerability reports responsibly",
    },
    {
      check: "Support channels provided",
      passed: /support|help|discord|slack|discussions|forum|community/i.test(
        content,
      ),
      points: 0,
      maxPoints: 5,
      recommendation: "Provide clear support channels for users seeking help",
    },
  ];

  // Award points for passed checks
  checks.forEach((check) => {
    if (check.passed) {
      check.points = check.maxPoints;
    }
  });

  const totalScore = checks.reduce((sum, check) => sum + check.points, 0);
  const maxScore = checks.reduce((sum, check) => sum + check.maxPoints, 0);

  return {
    name: "Community Health",
    score: totalScore,
    maxScore,
    details: checks,
  };
}

function evaluateAccessibility(content: string): HealthScoreComponent {
  const lines = content.split("\n");
  const headings = lines.filter((line) => line.trim().startsWith("#"));
  const images = content.match(/!\[.*?\]\(.*?\)/g) || [];

  const checks: HealthCheckDetail[] = [
    {
      check: "Scannable structure with proper spacing",
      passed: content.includes("\n\n") && lines.length > 10,
      points: 0,
      maxPoints: 5,
      recommendation: "Use proper spacing and breaks to make content scannable",
    },
    {
      check: "Clear heading hierarchy",
      passed: headings.length >= 3 && headings.some((h) => h.startsWith("##")),
      points: 0,
      maxPoints: 5,
      recommendation:
        "Use proper heading hierarchy (H1, H2, H3) to structure content",
    },
    {
      check: "Alt text for images",
      passed:
        images.length === 0 || images.every((img) => !img.includes("![](")),
      points: 0,
      maxPoints: 5,
      recommendation:
        "Add descriptive alt text for all images for screen readers",
    },
    {
      check: "Inclusive language",
      passed: !/\b(guys|blacklist|whitelist|master|slave)\b/i.test(content),
      points: 0,
      maxPoints: 5,
      recommendation:
        'Use inclusive language (e.g., "team" instead of "guys", "allowlist/blocklist")',
    },
  ];

  // Award points for passed checks
  checks.forEach((check) => {
    if (check.passed) {
      check.points = check.maxPoints;
    }
  });

  const totalScore = checks.reduce((sum, check) => sum + check.points, 0);
  const maxScore = checks.reduce((sum, check) => sum + check.maxPoints, 0);

  return {
    name: "Accessibility",
    score: totalScore,
    maxScore,
    details: checks,
  };
}

function evaluateOnboarding(
  content: string,
  _projectType: string,
): HealthScoreComponent {
  const checks: HealthCheckDetail[] = [
    {
      check: "Quick start section",
      passed: /quick.start|getting.started|installation|setup/i.test(content),
      points: 0,
      maxPoints: 5,
      recommendation:
        "Add a quick start section to help users get up and running fast",
    },
    {
      check: "Prerequisites clearly listed",
      passed: /prerequisites|requirements|dependencies|before.you.begin/i.test(
        content,
      ),
      points: 0,
      maxPoints: 5,
      recommendation: "Clearly list all prerequisites and system requirements",
    },
    {
      check: "First contribution guide",
      passed: /first.contribution|new.contributor|beginner|newcomer/i.test(
        content,
      ),
      points: 0,
      maxPoints: 5,
      recommendation:
        "Include guidance specifically for first-time contributors",
    },
    {
      check: "Good first issues mentioned",
      passed: /good.first.issue|beginner.friendly|easy.pick|help.wanted/i.test(
        content,
      ),
      points: 0,
      maxPoints: 5,
      recommendation: "Mention good first issues or beginner-friendly tasks",
    },
  ];

  // Award points for passed checks
  checks.forEach((check) => {
    if (check.passed) {
      check.points = check.maxPoints;
    }
  });

  const totalScore = checks.reduce((sum, check) => sum + check.points, 0);
  const maxScore = checks.reduce((sum, check) => sum + check.maxPoints, 0);

  return {
    name: "Onboarding",
    score: totalScore,
    maxScore,
    details: checks,
  };
}

function evaluateContentQuality(content: string): HealthScoreComponent {
  const wordCount = content.split(/\s+/).length;
  const codeBlocks = (content.match(/```/g) || []).length / 2;
  const links = (content.match(/\[.*?\]\(.*?\)/g) || []).length;

  const checks: HealthCheckDetail[] = [
    {
      check: "Adequate content length",
      passed: wordCount >= 50 && wordCount <= 2000,
      points: 0,
      maxPoints: 5,
      recommendation:
        "Maintain optimal README length (50-2000 words) for readability",
    },
    {
      check: "Code examples provided",
      passed: codeBlocks >= 2,
      points: 0,
      maxPoints: 5,
      recommendation: "Include practical code examples to demonstrate usage",
    },
    {
      check: "External links present",
      passed: links >= 3,
      points: 0,
      maxPoints: 5,
      recommendation:
        "Add relevant external links (docs, demos, related projects)",
    },
    {
      check: "Project description clarity",
      passed: /## |### /.test(content) && content.length > 500,
      points: 0,
      maxPoints: 5,
      recommendation:
        "Provide clear, detailed project description with proper structure",
    },
  ];

  // Award points for passed checks
  checks.forEach((check) => {
    if (check.passed) {
      check.points = check.maxPoints;
    }
  });

  const totalScore = checks.reduce((sum, check) => sum + check.points, 0);
  const maxScore = checks.reduce((sum, check) => sum + check.maxPoints, 0);

  return {
    name: "Content Quality",
    score: totalScore,
    maxScore,
    details: checks,
  };
}

async function analyzeRepositoryContext(repoPath: string): Promise<any> {
  try {
    const repoDir = path.resolve(repoPath);
    const files = await fs.readdir(repoDir);

    return {
      hasCodeOfConduct: files.includes("CODE_OF_CONDUCT.md"),
      hasContributing: files.includes("CONTRIBUTING.md"),
      hasSecurityPolicy: files.includes("SECURITY.md"),
      hasGithubDir: files.includes(".github"),
      packageJson: files.includes("package.json"),
    };
  } catch (error) {
    return null;
  }
}

function getGrade(percentage: number): "A" | "B" | "C" | "D" | "F" {
  if (percentage >= 90) return "A";
  if (percentage >= 80) return "B";
  if (percentage >= 70) return "C";
  if (percentage >= 60) return "D";
  return "F";
}

function generateHealthRecommendations(
  analysis: any[],
  _projectType: string,
): string[] {
  const recommendations: string[] = [];

  analysis.forEach((component: any) => {
    component.details.forEach((detail: any) => {
      if (detail.points < detail.maxPoints) {
        recommendations.push(`${component.name}: ${detail.recommendation}`);
      }
    });
  });

  return recommendations.slice(0, 10); // Top 10 recommendations
}

function identifyStrengths(components: HealthScoreComponent[]): string[] {
  const strengths: string[] = [];

  components.forEach((component) => {
    const passedChecks = component.details.filter((detail) => detail.passed);
    if (passedChecks.length > component.details.length / 2) {
      strengths.push(
        `Strong ${component.name.toLowerCase()}: ${passedChecks
          .map((c) => c.check.toLowerCase())
          .join(", ")}`,
      );
    }
  });

  return strengths;
}

function identifyCriticalIssues(components: HealthScoreComponent[]): string[] {
  const critical: string[] = [];

  components.forEach((component) => {
    if (component.score < component.maxScore * 0.3) {
      // Less than 30% score
      critical.push(
        `Critical: Poor ${component.name.toLowerCase()} (${component.score}/${
          component.maxScore
        } points)`,
      );
    }
  });

  return critical;
}

function estimateImprovementTime(
  recommendationCount: number,
  criticalCount: number,
): string {
  const baseTime = recommendationCount * 15; // 15 minutes per recommendation
  const criticalTime = criticalCount * 30; // 30 minutes per critical issue
  const totalMinutes = baseTime + criticalTime;

  if (totalMinutes < 60) return `${totalMinutes} minutes`;
  if (totalMinutes < 480) return `${Math.round(totalMinutes / 60)} hours`;
  return `${Math.round(totalMinutes / 480)} days`;
}

function generateSummary(report: ReadmeHealthReport): string {
  const { overallScore, grade, components } = report;

  const componentScores = Object.values(components)
    .map((c) => `${c.name}: ${c.score}/${c.maxScore}`)
    .join(", ");

  return `README Health Score: ${overallScore}/100 (Grade ${grade}). Component breakdown: ${componentScores}. ${report.criticalIssues.length} critical issues identified.`;
}

function generateNextSteps(report: ReadmeHealthReport): string[] {
  const steps: string[] = [];

  if (report.criticalIssues.length > 0) {
    steps.push(
      "Address critical issues first to establish baseline community health",
    );
  }

  if (report.recommendations.length > 0) {
    steps.push(
      `Implement top ${Math.min(
        3,
        report.recommendations.length,
      )} recommendations for quick wins`,
    );
  }

  if (report.overallScore < 85) {
    steps.push("Target 85+ health score for optimal community engagement");
  }

  steps.push("Re-evaluate after improvements to track progress");

  return steps;
}

```

--------------------------------------------------------------------------------
/tests/tools/analyze-deployments.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for Phase 2.4: Deployment Analytics and Insights
 * Tests the analyze_deployments tool with comprehensive pattern analysis
 */

import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
import { promises as fs } from "fs";
import { join } from "path";
import { tmpdir } from "os";
import {
  initializeKnowledgeGraph,
  getKnowledgeGraph,
  createOrUpdateProject,
  trackDeployment,
} from "../../src/memory/kg-integration.js";
import { analyzeDeployments } from "../../src/tools/analyze-deployments.js";

describe("analyzeDeployments (Phase 2.4)", () => {
  let testDir: string;
  let originalEnv: string | undefined;

  beforeEach(async () => {
    // Create temporary test directory
    testDir = join(tmpdir(), `analyze-deployments-test-${Date.now()}`);
    await fs.mkdir(testDir, { recursive: true });

    // Set environment variable for storage
    originalEnv = process.env.DOCUMCP_STORAGE_DIR;
    process.env.DOCUMCP_STORAGE_DIR = testDir;

    // Initialize KG
    await initializeKnowledgeGraph(testDir);
  });

  afterEach(async () => {
    // Restore environment
    if (originalEnv) {
      process.env.DOCUMCP_STORAGE_DIR = originalEnv;
    } else {
      delete process.env.DOCUMCP_STORAGE_DIR;
    }

    // Clean up test directory
    try {
      await fs.rm(testDir, { recursive: true, force: true });
    } catch (error) {
      console.warn("Failed to clean up test directory:", error);
    }
  });

  /**
   * Helper function to create sample deployment data
   */
  const createSampleDeployments = async () => {
    const timestamp = new Date().toISOString();

    // Create 3 projects
    const project1 = await createOrUpdateProject({
      id: "project1",
      timestamp,
      path: "/test/project1",
      projectName: "Docusaurus Site",
      structure: {
        totalFiles: 50,
        languages: { typescript: 30, javascript: 20 },
        hasTests: true,
        hasCI: true,
        hasDocs: true,
      },
    });

    const project2 = await createOrUpdateProject({
      id: "project2",
      timestamp,
      path: "/test/project2",
      projectName: "Hugo Blog",
      structure: {
        totalFiles: 30,
        languages: { go: 15, html: 15 },
        hasTests: false,
        hasCI: true,
        hasDocs: true,
      },
    });

    const project3 = await createOrUpdateProject({
      id: "project3",
      timestamp,
      path: "/test/project3",
      projectName: "MkDocs Docs",
      structure: {
        totalFiles: 40,
        languages: { python: 25, markdown: 15 },
        hasTests: true,
        hasCI: true,
        hasDocs: true,
      },
    });

    // Track successful deployments
    await trackDeployment(project1.id, "docusaurus", true, {
      buildTime: 25000,
    });
    await trackDeployment(project1.id, "docusaurus", true, {
      buildTime: 23000,
    });

    await trackDeployment(project2.id, "hugo", true, { buildTime: 15000 });
    await trackDeployment(project2.id, "hugo", true, { buildTime: 14000 });
    await trackDeployment(project2.id, "hugo", true, { buildTime: 16000 });

    await trackDeployment(project3.id, "mkdocs", true, { buildTime: 30000 });
    await trackDeployment(project3.id, "mkdocs", false, {
      errorMessage: "Build failed",
    });

    return { project1, project2, project3 };
  };

  describe("Full Report Analysis", () => {
    it("should generate comprehensive analytics report with no data", async () => {
      const result = await analyzeDeployments({});

      const content = result.content[0];
      expect(content.type).toBe("text");
      const data = JSON.parse(content.text);

      expect(data.summary).toBeDefined();
      expect(data.summary.totalProjects).toBe(0);
      expect(data.summary.totalDeployments).toBe(0);
      expect(data.patterns).toEqual([]);
      // With 0 deployments, we get a warning insight about low success rate
      expect(Array.isArray(data.insights)).toBe(true);
      expect(data.recommendations).toBeDefined();
    });

    it("should generate comprehensive analytics report with sample data", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "full_report",
      });

      const content = result.content[0];
      expect(content.type).toBe("text");
      const data = JSON.parse(content.text);

      // Verify summary
      expect(data.summary).toBeDefined();
      expect(data.summary.totalProjects).toBe(3);
      // Each project has 1 configuration node, so 3 total deployments tracked
      expect(data.summary.totalDeployments).toBeGreaterThanOrEqual(3);
      expect(data.summary.overallSuccessRate).toBeGreaterThan(0);
      expect(data.summary.mostUsedSSG).toBeDefined();

      // Verify patterns
      expect(data.patterns).toBeDefined();
      expect(data.patterns.length).toBeGreaterThan(0);
      expect(data.patterns[0]).toHaveProperty("ssg");
      expect(data.patterns[0]).toHaveProperty("totalDeployments");
      expect(data.patterns[0]).toHaveProperty("successRate");

      // Verify insights and recommendations
      expect(data.insights).toBeDefined();
      expect(data.recommendations).toBeDefined();
    });

    it("should include insights about high success rates", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "full_report",
      });

      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Should have success insights for docusaurus and hugo
      const successInsights = data.insights.filter(
        (i: any) => i.type === "success",
      );
      expect(successInsights.length).toBeGreaterThan(0);
    });
  });

  describe("SSG Statistics Analysis", () => {
    it("should return error for non-existent SSG", async () => {
      const result = await analyzeDeployments({
        analysisType: "ssg_stats",
        ssg: "nonexistent",
      });

      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Should return error response when SSG has no data
      expect(data.success).toBe(false);
      expect(data.error).toBeDefined();
    });

    it("should return statistics for specific SSG", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "ssg_stats",
        ssg: "docusaurus",
      });

      const content = result.content[0];
      expect(content.type).toBe("text");
      const data = JSON.parse(content.text);

      expect(data.ssg).toBe("docusaurus");
      // project1 has 2 deployments with docusaurus
      expect(data.totalDeployments).toBeGreaterThanOrEqual(1);
      expect(data.successfulDeployments).toBeGreaterThanOrEqual(1);
      expect(data.successRate).toBeGreaterThan(0);
      expect(data.averageBuildTime).toBeDefined();
      expect(data.projectCount).toBeGreaterThan(0);
    });

    it("should calculate average build time correctly", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "ssg_stats",
        ssg: "hugo",
      });

      const content = result.content[0];
      const data = JSON.parse(content.text);

      expect(data.averageBuildTime).toBeDefined();
      // Hugo has 3 deployments with build times
      expect(data.averageBuildTime).toBeGreaterThan(0);
      expect(data.averageBuildTime).toBeLessThan(20000);
    });

    it("should show success rate less than 100% for failed deployments", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "ssg_stats",
        ssg: "mkdocs",
      });

      const content = result.content[0];
      const data = JSON.parse(content.text);

      expect(data.totalDeployments).toBeGreaterThanOrEqual(1);
      expect(data.failedDeployments).toBeGreaterThanOrEqual(1);
      expect(data.successRate).toBeLessThan(1.0);
    });
  });

  describe("SSG Comparison Analysis", () => {
    it("should fail without enough SSGs", async () => {
      const result = await analyzeDeployments({
        analysisType: "compare",
        ssgs: ["docusaurus"],
      });

      const content = result.content[0];
      expect(content.type).toBe("text");
      const data = JSON.parse(content.text);

      // Should be an error response
      expect(data.success).toBe(false);
      expect(data.error).toBeDefined();
      expect(data.error.code).toBe("ANALYTICS_FAILED");
    });

    it("should compare multiple SSGs by success rate", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "compare",
        ssgs: ["docusaurus", "hugo", "mkdocs"],
      });

      const content = result.content[0];
      expect(content.type).toBe("text");
      const data = JSON.parse(content.text);

      expect(Array.isArray(data)).toBe(true);
      expect(data.length).toBeGreaterThan(0);

      // Should be sorted by success rate (descending)
      for (let i = 0; i < data.length - 1; i++) {
        expect(data[i].pattern.successRate).toBeGreaterThanOrEqual(
          data[i + 1].pattern.successRate,
        );
      }
    });

    it("should include only SSGs with deployment data", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "compare",
        ssgs: ["docusaurus", "nonexistent", "hugo"],
      });

      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Should only include docusaurus and hugo
      expect(data.length).toBe(2);
      const ssgs = data.map((d: any) => d.ssg);
      expect(ssgs).toContain("docusaurus");
      expect(ssgs).toContain("hugo");
      expect(ssgs).not.toContain("nonexistent");
    });
  });

  describe("Health Score Analysis", () => {
    it("should calculate health score with no data", async () => {
      const result = await analyzeDeployments({
        analysisType: "health",
      });

      const content = result.content[0];
      expect(content.type).toBe("text");
      const data = JSON.parse(content.text);

      expect(data.score).toBeDefined();
      expect(data.score).toBeGreaterThanOrEqual(0);
      expect(data.score).toBeLessThanOrEqual(100);
      expect(data.factors).toBeDefined();
      expect(Array.isArray(data.factors)).toBe(true);
      expect(data.factors.length).toBe(4); // 4 factors
    });

    it("should calculate health score with sample data", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "health",
      });

      const content = result.content[0];
      const data = JSON.parse(content.text);

      expect(data.score).toBeGreaterThan(0);
      expect(data.factors.length).toBe(4);

      // Check all factors present
      const factorNames = data.factors.map((f: any) => f.name);
      expect(factorNames).toContain("Overall Success Rate");
      expect(factorNames).toContain("Active Projects");
      expect(factorNames).toContain("Deployment Activity");
      expect(factorNames).toContain("SSG Diversity");

      // Each factor should have impact and status
      data.factors.forEach((factor: any) => {
        expect(factor.impact).toBeDefined();
        expect(factor.status).toMatch(/^(good|warning|critical)$/);
      });
    });

    it("should have good health with high success rate", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "health",
      });

      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Should have decent health with our sample data
      expect(data.score).toBeGreaterThan(30);

      const successRateFactor = data.factors.find(
        (f: any) => f.name === "Overall Success Rate",
      );
      expect(successRateFactor.status).toMatch(/^(good|warning)$/);
    });
  });

  describe("Trend Analysis", () => {
    it("should analyze trends with default period", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "trends",
      });

      const content = result.content[0];
      expect(content.type).toBe("text");
      const data = JSON.parse(content.text);

      expect(Array.isArray(data)).toBe(true);
      // Trends are grouped by time periods
    });

    it("should analyze trends with custom period", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "trends",
        periodDays: 7,
      });

      const content = result.content[0];
      const data = JSON.parse(content.text);

      expect(Array.isArray(data)).toBe(true);
    });
  });

  describe("Error Handling", () => {
    it("should handle missing SSG parameter for ssg_stats", async () => {
      const result = await analyzeDeployments({
        analysisType: "ssg_stats",
      });

      const content = result.content[0];
      const data = JSON.parse(content.text);

      expect(data.success).toBe(false);
      expect(data.error).toBeDefined();
      expect(data.error.code).toBe("ANALYTICS_FAILED");
      expect(data.error.message).toContain("SSG name required");
    });

    it("should handle invalid analysis type gracefully", async () => {
      const result = await analyzeDeployments({
        analysisType: "full_report",
      });

      const content = result.content[0];
      expect(content.type).toBe("text");
      // Should not throw, should return valid response
    });
  });

  describe("Recommendations Generation", () => {
    it("should generate recommendations based on patterns", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "full_report",
      });

      const content = result.content[0];
      const data = JSON.parse(content.text);

      expect(data.recommendations).toBeDefined();
      expect(Array.isArray(data.recommendations)).toBe(true);
      expect(data.recommendations.length).toBeGreaterThan(0);
    });

    it("should recommend best performing SSG", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "full_report",
      });

      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Should have recommendations
      expect(data.recommendations.length).toBeGreaterThan(0);
      // At least one recommendation should mention an SSG or general advice
      const allText = data.recommendations.join(" ").toLowerCase();
      expect(allText.length).toBeGreaterThan(0);
    });
  });

  describe("Build Time Analysis", () => {
    it("should identify fast builds in insights", async () => {
      await createSampleDeployments();

      const result = await analyzeDeployments({
        analysisType: "full_report",
      });

      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Hugo has ~15s builds, should be identified as fast
      const fastBuildInsights = data.insights.filter(
        (i: any) => i.title && i.title.includes("Fast Builds"),
      );
      expect(fastBuildInsights.length).toBeGreaterThan(0);
    });
  });
});

```

--------------------------------------------------------------------------------
/tests/call-graph-builder.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for Call Graph Builder (Issue #72)
 */

import {
  ASTAnalyzer,
  CallGraph,
  CallGraphNode,
  ConditionalPath,
  ExceptionPath,
  CallGraphOptions,
} from "../src/utils/ast-analyzer.js";
import { promises as fs } from "fs";
import path from "path";
import os from "os";

describe("Call Graph Builder", () => {
  let analyzer: ASTAnalyzer;
  let tempDir: string;

  beforeAll(async () => {
    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "call-graph-test-"));
    analyzer = new ASTAnalyzer();
    await analyzer.initialize();
  });

  afterAll(async () => {
    try {
      await fs.rm(tempDir, { recursive: true, force: true });
    } catch {
      // Ignore cleanup errors
    }
  });

  describe("buildCallGraph", () => {
    it("should build call graph for a simple function", async () => {
      const code = `
export function main() {
  helper();
}

function helper() {
  return "helped";
}
`;
      await fs.writeFile(path.join(tempDir, "simple.ts"), code);

      const graph = await analyzer.buildCallGraph("main", tempDir);

      expect(graph.entryPoint).toBe("main");
      expect(graph.root.function.name).toBe("main");
      expect(graph.root.calls.length).toBeGreaterThanOrEqual(1);
      expect(graph.root.calls[0]?.function.name).toBe("helper");
    });

    it("should handle nested function calls", async () => {
      const code = `
export function outer() {
  middle();
}

function middle() {
  inner();
}

function inner() {
  return 42;
}
`;
      await fs.writeFile(path.join(tempDir, "nested.ts"), code);

      const graph = await analyzer.buildCallGraph("outer", tempDir, {
        maxDepth: 5,
      });

      expect(graph.entryPoint).toBe("outer");
      expect(graph.maxDepthReached).toBeGreaterThanOrEqual(2);

      // Find middle call
      const middleCall = graph.root.calls.find(
        (c) => c.function.name === "middle",
      );
      expect(middleCall).toBeDefined();

      // Find inner call within middle
      if (middleCall) {
        const innerCall = middleCall.calls.find(
          (c) => c.function.name === "inner",
        );
        expect(innerCall).toBeDefined();
      }
    });

    it("should respect maxDepth option", async () => {
      const code = `
export function level1() {
  level2();
}

function level2() {
  level3();
}

function level3() {
  level4();
}

function level4() {
  return "deep";
}
`;
      await fs.writeFile(path.join(tempDir, "deep.ts"), code);

      const graph = await analyzer.buildCallGraph("level1", tempDir, {
        maxDepth: 2,
      });

      expect(graph.maxDepthReached).toBeLessThanOrEqual(2);
      // level3 and level4 should not be fully traversed
    });

    it("should detect circular references", async () => {
      const code = `
export function funcA() {
  funcB();
}

function funcB() {
  funcA();
}
`;
      await fs.writeFile(path.join(tempDir, "circular.ts"), code);

      const graph = await analyzer.buildCallGraph("funcA", tempDir);

      expect(graph.circularReferences.length).toBeGreaterThanOrEqual(0);
    });

    it("should return empty graph for non-existent function", async () => {
      const graph = await analyzer.buildCallGraph(
        "nonExistentFunction",
        tempDir,
      );

      expect(graph.entryPoint).toBe("nonExistentFunction");
      expect(graph.root.isExternal).toBe(true);
      expect(graph.unresolvedCalls.length).toBeGreaterThan(0);
    });
  });

  describe("Conditional branch extraction", () => {
    it("should extract if/else branches", async () => {
      const code = `
export function withCondition(flag: boolean) {
  if (flag) {
    trueHandler();
  } else {
    falseHandler();
  }
}

function trueHandler() {
  return true;
}

function falseHandler() {
  return false;
}
`;
      await fs.writeFile(path.join(tempDir, "conditional.ts"), code);

      const graph = await analyzer.buildCallGraph("withCondition", tempDir, {
        extractConditionals: true,
      });

      expect(graph.root.conditionalBranches.length).toBeGreaterThanOrEqual(1);
      const ifBranch = graph.root.conditionalBranches.find(
        (c) => c.type === "if",
      );
      expect(ifBranch).toBeDefined();
      expect(ifBranch?.condition).toContain("flag");
    });

    it("should extract switch statement branches", async () => {
      const code = `
export function handleStatus(status: string) {
  switch (status) {
    case "success":
      handleSuccess();
      break;
    case "error":
      handleError();
      break;
    default:
      handleDefault();
  }
}

function handleSuccess() {}
function handleError() {}
function handleDefault() {}
`;
      await fs.writeFile(path.join(tempDir, "switch.ts"), code);

      const graph = await analyzer.buildCallGraph("handleStatus", tempDir, {
        extractConditionals: true,
      });

      const switchCases = graph.root.conditionalBranches.filter(
        (c) => c.type === "switch-case",
      );
      expect(switchCases.length).toBeGreaterThanOrEqual(1);
    });

    it("should extract ternary operators", async () => {
      const code = `
export function ternaryExample(condition: boolean) {
  const result = condition ? getValue() : getDefault();
  return result;
}

function getValue() { return 1; }
function getDefault() { return 0; }
`;
      await fs.writeFile(path.join(tempDir, "ternary.ts"), code);

      const graph = await analyzer.buildCallGraph("ternaryExample", tempDir, {
        extractConditionals: true,
      });

      const ternary = graph.root.conditionalBranches.find(
        (c) => c.type === "ternary",
      );
      expect(ternary).toBeDefined();
    });

    it("should skip conditional extraction when disabled", async () => {
      const code = `
export function withCondition(flag: boolean) {
  if (flag) {
    trueHandler();
  }
}

function trueHandler() {}
`;
      await fs.writeFile(path.join(tempDir, "no-conditionals.ts"), code);

      const graph = await analyzer.buildCallGraph("withCondition", tempDir, {
        extractConditionals: false,
      });

      expect(graph.root.conditionalBranches.length).toBe(0);
    });
  });

  describe("Exception path identification", () => {
    it("should detect throw statements", async () => {
      const code = `
export function throwingFunction() {
  throw new Error("Something went wrong");
}
`;
      await fs.writeFile(path.join(tempDir, "throwing.ts"), code);

      const graph = await analyzer.buildCallGraph("throwingFunction", tempDir, {
        trackExceptions: true,
      });

      expect(graph.root.exceptions.length).toBeGreaterThanOrEqual(1);
      const exception = graph.root.exceptions[0];
      expect(exception?.exceptionType).toBe("Error");
      expect(exception?.isCaught).toBe(false);
    });

    it("should detect caught exceptions", async () => {
      const code = `
export function caughtException() {
  try {
    throw new Error("Will be caught");
  } catch (e) {
    console.log(e);
  }
}
`;
      await fs.writeFile(path.join(tempDir, "caught.ts"), code);

      const graph = await analyzer.buildCallGraph("caughtException", tempDir, {
        trackExceptions: true,
      });

      const caughtExceptions = graph.root.exceptions.filter((e) => e.isCaught);
      expect(caughtExceptions.length).toBeGreaterThanOrEqual(1);
    });

    it("should detect custom exception types", async () => {
      const code = `
class CustomError extends Error {}

export function customException() {
  throw new CustomError("Custom error");
}
`;
      await fs.writeFile(path.join(tempDir, "custom-error.ts"), code);

      const graph = await analyzer.buildCallGraph("customException", tempDir, {
        trackExceptions: true,
      });

      const customEx = graph.root.exceptions.find(
        (e) => e.exceptionType === "CustomError",
      );
      expect(customEx).toBeDefined();
    });

    it("should skip exception tracking when disabled", async () => {
      const code = `
export function throwingFunction() {
  throw new Error("Something went wrong");
}
`;
      await fs.writeFile(path.join(tempDir, "no-exceptions.ts"), code);

      const graph = await analyzer.buildCallGraph("throwingFunction", tempDir, {
        trackExceptions: false,
      });

      expect(graph.root.exceptions.length).toBe(0);
    });
  });

  describe("Cross-file import resolution", () => {
    it("should resolve same-directory imports", async () => {
      // Create main file
      const mainCode = `
import { helper } from "./helper.js";

export function main() {
  helper();
}
`;
      // Create helper file
      const helperCode = `
export function helper() {
  return "helped";
}
`;
      const subDir = path.join(tempDir, "cross-file");
      await fs.mkdir(subDir, { recursive: true });
      await fs.writeFile(path.join(subDir, "main.ts"), mainCode);
      await fs.writeFile(path.join(subDir, "helper.ts"), helperCode);

      const graph = await analyzer.buildCallGraph("main", subDir, {
        resolveImports: true,
      });

      // Should have found the helper function
      expect(graph.analyzedFiles.length).toBeGreaterThanOrEqual(1);
    });

    it("should track unresolved external calls", async () => {
      const code = `
import { externalFunction } from "external-package";

export function main() {
  externalFunction();
  unknownFunction();
}
`;
      await fs.writeFile(path.join(tempDir, "external.ts"), code);

      const graph = await analyzer.buildCallGraph("main", tempDir);

      // External package calls should be unresolved
      expect(graph.unresolvedCalls.length).toBeGreaterThanOrEqual(1);
    });

    it("should skip import resolution when disabled", async () => {
      const code = `
import { helper } from "./helper.js";

export function main() {
  helper();
}
`;
      await fs.writeFile(path.join(tempDir, "no-resolve.ts"), code);

      const graph = await analyzer.buildCallGraph("main", tempDir, {
        resolveImports: false,
      });

      // Only the main file should be analyzed
      expect(graph.analyzedFiles.length).toBeLessThanOrEqual(1);
    });
  });

  describe("Class method handling", () => {
    it("should handle class method calls", async () => {
      const code = `
export class MyClass {
  public methodA() {
    this.methodB();
  }

  private methodB() {
    return "B";
  }
}
`;
      await fs.writeFile(path.join(tempDir, "class-methods.ts"), code);

      const graph = await analyzer.buildCallGraph("methodA", tempDir);

      // Should find methodA as entry
      expect(graph.entryPoint).toBe("methodA");
    });
  });

  describe("CallGraph structure", () => {
    it("should have correct graph structure", async () => {
      const code = `
export function main() {
  helper();
}

function helper() {}
`;
      await fs.writeFile(path.join(tempDir, "structure.ts"), code);

      const graph = await analyzer.buildCallGraph("main", tempDir);

      // Verify graph properties
      expect(graph).toHaveProperty("entryPoint");
      expect(graph).toHaveProperty("root");
      expect(graph).toHaveProperty("allFunctions");
      expect(graph).toHaveProperty("maxDepthReached");
      expect(graph).toHaveProperty("analyzedFiles");
      expect(graph).toHaveProperty("circularReferences");
      expect(graph).toHaveProperty("unresolvedCalls");
      expect(graph).toHaveProperty("buildTime");

      // Verify allFunctions is a Map
      expect(graph.allFunctions).toBeInstanceOf(Map);
    });

    it("should have correct node structure", async () => {
      const code = `
export function main() {
  helper();
}

function helper() {}
`;
      await fs.writeFile(path.join(tempDir, "node-structure.ts"), code);

      const graph = await analyzer.buildCallGraph("main", tempDir);

      // Verify node properties
      const node = graph.root;
      expect(node).toHaveProperty("function");
      expect(node).toHaveProperty("location");
      expect(node).toHaveProperty("calls");
      expect(node).toHaveProperty("conditionalBranches");
      expect(node).toHaveProperty("exceptions");
      expect(node).toHaveProperty("depth");
      expect(node).toHaveProperty("truncated");
      expect(node).toHaveProperty("isExternal");

      // Verify location structure
      expect(node.location).toHaveProperty("file");
      expect(node.location).toHaveProperty("line");
    });
  });

  describe("Edge cases", () => {
    it("should handle empty function body", async () => {
      const code = `
export function emptyFunction() {}
`;
      await fs.writeFile(path.join(tempDir, "empty.ts"), code);

      const graph = await analyzer.buildCallGraph("emptyFunction", tempDir);

      expect(graph.root.function.name).toBe("emptyFunction");
      expect(graph.root.calls.length).toBe(0);
    });

    it("should handle async functions", async () => {
      const code = `
export async function asyncMain() {
  await asyncHelper();
}

async function asyncHelper() {
  return Promise.resolve(42);
}
`;
      await fs.writeFile(path.join(tempDir, "async.ts"), code);

      const graph = await analyzer.buildCallGraph("asyncMain", tempDir);

      expect(graph.root.function.name).toBe("asyncMain");
      expect(graph.root.function.isAsync).toBe(true);
    });

    it("should handle arrow functions", async () => {
      const code = `
export const arrowMain = () => {
  arrowHelper();
};

const arrowHelper = () => "helped";
`;
      await fs.writeFile(path.join(tempDir, "arrow.ts"), code);

      const graph = await analyzer.buildCallGraph("arrowMain", tempDir);

      expect(graph.root.function.name).toBe("arrowMain");
    });

    it("should handle functions with complex parameters", async () => {
      const code = `
export function complexParams(
  required: string,
  optional?: number,
  defaultVal = "default",
  ...rest: string[]
) {
  helper();
}

function helper() {}
`;
      await fs.writeFile(path.join(tempDir, "complex-params.ts"), code);

      const graph = await analyzer.buildCallGraph("complexParams", tempDir);

      expect(graph.root.function.parameters.length).toBeGreaterThanOrEqual(1);
    });

    it("should handle deeply nested conditionals", async () => {
      const code = `
export function nested(a: boolean, b: boolean, c: boolean) {
  if (a) {
    if (b) {
      if (c) {
        deepFunction();
      }
    }
  }
}

function deepFunction() {}
`;
      await fs.writeFile(path.join(tempDir, "nested-conditionals.ts"), code);

      const graph = await analyzer.buildCallGraph("nested", tempDir, {
        extractConditionals: true,
      });

      // Should have at least one conditional
      expect(graph.root.conditionalBranches.length).toBeGreaterThanOrEqual(1);
    });
  });

  describe("Options validation", () => {
    it("should use default options when not provided", async () => {
      const code = `
export function main() {}
`;
      await fs.writeFile(path.join(tempDir, "defaults.ts"), code);

      const graph = await analyzer.buildCallGraph("main", tempDir);

      // Should complete without errors using defaults
      expect(graph.entryPoint).toBe("main");
    });

    it("should handle custom extensions option", async () => {
      const code = `
export function main() {}
`;
      await fs.writeFile(path.join(tempDir, "custom.ts"), code);

      const graph = await analyzer.buildCallGraph("main", tempDir, {
        extensions: [".ts"],
      });

      expect(graph.entryPoint).toBe("main");
    });
  });
});

```

--------------------------------------------------------------------------------
/tests/memory/kg-code-integration.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for Knowledge Graph Code Integration
 */

import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
import { promises as fs } from "fs";
import path from "path";
import { tmpdir } from "os";
import {
  createCodeFileEntities,
  createDocumentationEntities,
  linkCodeToDocs,
} from "../../src/memory/kg-code-integration.js";
import { ExtractedContent } from "../../src/utils/content-extractor.js";
import {
  initializeKnowledgeGraph,
  getKnowledgeGraph,
} from "../../src/memory/kg-integration.js";

describe("KG Code Integration", () => {
  let testDir: string;
  let projectId: string;

  beforeEach(async () => {
    // Create temporary directory for test files
    testDir = path.join(tmpdir(), `documcp-test-${Date.now()}`);
    await fs.mkdir(testDir, { recursive: true });
    projectId = `project:test_${Date.now()}`;

    // Initialize KG with test storage
    const storageDir = path.join(testDir, ".documcp/memory");
    await initializeKnowledgeGraph(storageDir);
  });

  afterEach(async () => {
    // Cleanup
    try {
      await fs.rm(testDir, { recursive: true, force: true });
    } catch {
      // Ignore cleanup errors
    }
  });

  describe("createCodeFileEntities", () => {
    it("should create code file entities from TypeScript files", async () => {
      // Create a test TypeScript file
      const srcDir = path.join(testDir, "src");
      await fs.mkdir(srcDir, { recursive: true });

      const tsContent = `
export class UserService {
  async getUser(id: string) {
    return { id, name: "Test User" };
  }

  async createUser(data: any) {
    return { ...data, id: "123" };
  }
}

export async function validateUser(user: any) {
  return user.name && user.id;
}
`;

      await fs.writeFile(path.join(srcDir, "user.ts"), tsContent, "utf-8");

      // Create entities
      const entities = await createCodeFileEntities(projectId, testDir);

      // Assertions
      expect(entities.length).toBe(1);
      expect(entities[0].type).toBe("code_file");
      expect(entities[0].properties.language).toBe("typescript");
      expect(entities[0].properties.path).toBe("src/user.ts");
      expect(entities[0].properties.classes).toContain("UserService");
      expect(entities[0].properties.functions).toContain("validateUser");
      expect(entities[0].properties.contentHash).toBeDefined();
      expect(entities[0].properties.linesOfCode).toBeGreaterThan(0);
    });

    it("should create code file entities from Python files", async () => {
      const srcDir = path.join(testDir, "src");
      await fs.mkdir(srcDir, { recursive: true });

      const pyContent = `
class Database:
    def connect(self):
        pass

    def query(self, sql):
        return []

def initialize_db():
    return Database()
`;

      await fs.writeFile(path.join(srcDir, "database.py"), pyContent, "utf-8");

      const entities = await createCodeFileEntities(projectId, testDir);

      expect(entities.length).toBe(1);
      expect(entities[0].properties.language).toBe("python");
      expect(entities[0].properties.classes).toContain("Database");
      expect(entities[0].properties.functions).toContain("initialize_db");
    });

    it("should handle nested directories", async () => {
      const nestedDir = path.join(testDir, "src", "services", "auth");
      await fs.mkdir(nestedDir, { recursive: true });

      await fs.writeFile(
        path.join(nestedDir, "login.ts"),
        "export function login() {}",
        "utf-8",
      );

      const entities = await createCodeFileEntities(projectId, testDir);

      expect(entities.length).toBe(1);
      expect(entities[0].properties.path).toBe("src/services/auth/login.ts");
    });

    it("should skip non-code files", async () => {
      const srcDir = path.join(testDir, "src");
      await fs.mkdir(srcDir, { recursive: true });

      await fs.writeFile(path.join(srcDir, "README.md"), "# Readme", "utf-8");
      await fs.writeFile(path.join(srcDir, "config.json"), "{}", "utf-8");

      const entities = await createCodeFileEntities(projectId, testDir);

      expect(entities.length).toBe(0);
    });

    it("should estimate complexity correctly", async () => {
      const srcDir = path.join(testDir, "src");
      await fs.mkdir(srcDir, { recursive: true });

      // Small file - low complexity
      const smallFile = "export function simple() { return 1; }";
      await fs.writeFile(path.join(srcDir, "small.ts"), smallFile, "utf-8");

      // Large file - high complexity
      const largeFile = Array(200)
        .fill("function test() { return 1; }")
        .join("\n");
      await fs.writeFile(path.join(srcDir, "large.ts"), largeFile, "utf-8");

      const entities = await createCodeFileEntities(projectId, testDir);

      const smallEntity = entities.find((e) =>
        e.properties.path.includes("small.ts"),
      );
      const largeEntity = entities.find((e) =>
        e.properties.path.includes("large.ts"),
      );

      expect(smallEntity?.properties.complexity).toBe("low");
      expect(largeEntity?.properties.complexity).toBe("high");
    });

    it("should create relationships with project", async () => {
      const srcDir = path.join(testDir, "src");
      await fs.mkdir(srcDir, { recursive: true });
      await fs.writeFile(
        path.join(srcDir, "test.ts"),
        "export function test() {}",
        "utf-8",
      );

      await createCodeFileEntities(projectId, testDir);

      const kg = await getKnowledgeGraph();
      const edges = await kg.findEdges({ source: projectId });

      expect(edges.some((e) => e.type === "depends_on")).toBe(true);
    });
  });

  describe("createDocumentationEntities", () => {
    it("should create documentation section entities from README", async () => {
      const extractedContent: ExtractedContent = {
        readme: {
          content: "# My Project\n\nThis is a test project.",
          sections: [
            {
              title: "My Project",
              content: "This is a test project.",
              level: 1,
            },
            { title: "Installation", content: "npm install", level: 2 },
          ],
        },
        existingDocs: [],
        adrs: [],
        codeExamples: [],
        apiDocs: [],
      };

      const entities = await createDocumentationEntities(
        projectId,
        extractedContent,
      );

      expect(entities.length).toBe(2);
      expect(entities[0].type).toBe("documentation_section");
      expect(entities[0].properties.sectionTitle).toBe("My Project");
      expect(entities[0].properties.contentHash).toBeDefined();
      expect(entities[0].properties.category).toBe("reference");
    });

    it("should categorize documentation correctly", async () => {
      const extractedContent: ExtractedContent = {
        existingDocs: [
          {
            path: "docs/tutorials/getting-started.md",
            title: "Getting Started",
            content: "# Tutorial",
            category: "tutorial",
          },
          {
            path: "docs/how-to/deploy.md",
            title: "Deploy Guide",
            content: "# How to Deploy",
            category: "how-to",
          },
          {
            path: "docs/api/reference.md",
            title: "API Reference",
            content: "# API",
            category: "reference",
          },
        ],
        adrs: [],
        codeExamples: [],
        apiDocs: [],
      };

      const entities = await createDocumentationEntities(
        projectId,
        extractedContent,
      );

      expect(entities.length).toBe(3);
      expect(
        entities.find((e) => e.properties.category === "tutorial"),
      ).toBeDefined();
      expect(
        entities.find((e) => e.properties.category === "how-to"),
      ).toBeDefined();
      expect(
        entities.find((e) => e.properties.category === "reference"),
      ).toBeDefined();
    });

    it("should extract code references from content", async () => {
      const extractedContent: ExtractedContent = {
        existingDocs: [
          {
            path: "docs/guide.md",
            title: "Guide",
            content:
              "Call `getUserById()` from `src/user.ts` using `UserService` class",
            category: "how-to",
          },
        ],
        adrs: [],
        codeExamples: [],
        apiDocs: [],
      };

      const entities = await createDocumentationEntities(
        projectId,
        extractedContent,
      );

      expect(entities[0].properties.referencedCodeFiles).toContain(
        "src/user.ts",
      );
      expect(entities[0].properties.referencedFunctions).toContain(
        "getUserById",
      );
      expect(entities[0].properties.referencedClasses).toContain("UserService");
    });

    it("should detect code examples in documentation", async () => {
      const extractedContent: ExtractedContent = {
        existingDocs: [
          {
            path: "docs/example.md",
            title: "Example",
            content: "# Example\n\n```typescript\nconst x = 1;\n```",
          },
        ],
        adrs: [],
        codeExamples: [],
        apiDocs: [],
      };

      const entities = await createDocumentationEntities(
        projectId,
        extractedContent,
      );

      expect(entities[0].properties.hasCodeExamples).toBe(true);
      expect(entities[0].properties.effectivenessScore).toBeGreaterThan(0.5);
    });

    it("should process ADRs as explanation category", async () => {
      const extractedContent: ExtractedContent = {
        existingDocs: [],
        adrs: [
          {
            number: "001",
            title: "Use TypeScript",
            status: "Accepted",
            content: "We will use TypeScript for type safety",
            decision: "Use TypeScript",
            consequences: "Better IDE support",
          },
        ],
        codeExamples: [],
        apiDocs: [],
      };

      const entities = await createDocumentationEntities(
        projectId,
        extractedContent,
      );

      expect(entities.length).toBe(1);
      expect(entities[0].properties.category).toBe("explanation");
      expect(entities[0].properties.sectionTitle).toBe("Use TypeScript");
    });
  });

  describe("linkCodeToDocs", () => {
    it("should create references edges when docs reference code", async () => {
      // Create code entity
      const srcDir = path.join(testDir, "src");
      await fs.mkdir(srcDir, { recursive: true });
      await fs.writeFile(
        path.join(srcDir, "user.ts"),
        "export function getUser() {}",
        "utf-8",
      );

      const codeFiles = await createCodeFileEntities(projectId, testDir);

      // Create doc entity that references the code
      const extractedContent: ExtractedContent = {
        existingDocs: [
          {
            path: "docs/api.md",
            title: "API",
            content: "Use `getUser()` from `src/user.ts`",
            category: "reference",
          },
        ],
        adrs: [],
        codeExamples: [],
        apiDocs: [],
      };

      const docSections = await createDocumentationEntities(
        projectId,
        extractedContent,
      );

      // Link them
      const edges = await linkCodeToDocs(codeFiles, docSections);

      // Should create references edge (doc -> code)
      const referencesEdge = edges.find((e) => e.type === "references");
      expect(referencesEdge).toBeDefined();
      expect(referencesEdge?.source).toBe(docSections[0].id);
      expect(referencesEdge?.target).toBe(codeFiles[0].id);
      expect(referencesEdge?.properties.referenceType).toBe("api-reference");

      // Should create documents edge (code -> doc)
      const documentsEdge = edges.find((e) => e.type === "documents");
      expect(documentsEdge).toBeDefined();
      expect(documentsEdge?.source).toBe(codeFiles[0].id);
      expect(documentsEdge?.target).toBe(docSections[0].id);
    });

    it("should detect outdated documentation", async () => {
      // Create code entity with recent modification
      const srcDir = path.join(testDir, "src");
      await fs.mkdir(srcDir, { recursive: true });
      await fs.writeFile(
        path.join(srcDir, "user.ts"),
        "export function getUser() {}",
        "utf-8",
      );

      const codeFiles = await createCodeFileEntities(projectId, testDir);

      // Simulate old documentation (modify lastUpdated)
      const extractedContent: ExtractedContent = {
        existingDocs: [
          {
            path: "docs/api.md",
            title: "API",
            content: "Use `getUser()` from `src/user.ts`",
            category: "reference",
          },
        ],
        adrs: [],
        codeExamples: [],
        apiDocs: [],
      };

      const docSections = await createDocumentationEntities(
        projectId,
        extractedContent,
      );

      // Manually set old timestamp on doc
      docSections[0].properties.lastUpdated = new Date(
        Date.now() - 86400000,
      ).toISOString();

      const edges = await linkCodeToDocs(codeFiles, docSections);

      // Should create outdated_for edge
      const outdatedEdge = edges.find((e) => e.type === "outdated_for");
      expect(outdatedEdge).toBeDefined();
      expect(outdatedEdge?.properties.severity).toBe("medium");
    });

    it("should determine coverage based on referenced functions", async () => {
      const srcDir = path.join(testDir, "src");
      await fs.mkdir(srcDir, { recursive: true });

      // Code with 3 functions
      await fs.writeFile(
        path.join(srcDir, "user.ts"),
        `
export function getUser() {}
export function createUser() {}
export function deleteUser() {}
      `,
        "utf-8",
      );

      const codeFiles = await createCodeFileEntities(projectId, testDir);

      // Doc that only references 2 functions (66% coverage)
      const extractedContent: ExtractedContent = {
        existingDocs: [
          {
            path: "docs/api.md",
            title: "API",
            content: "Use `getUser()` and `createUser()` from `src/user.ts`",
            category: "reference",
          },
        ],
        adrs: [],
        codeExamples: [],
        apiDocs: [],
      };

      const docSections = await createDocumentationEntities(
        projectId,
        extractedContent,
      );

      const edges = await linkCodeToDocs(codeFiles, docSections);

      const documentsEdge = edges.find((e) => e.type === "documents");
      expect(documentsEdge?.properties.coverage).toBe("complete"); // >= 50%
    });

    it("should handle documentation with no code references", async () => {
      const srcDir = path.join(testDir, "src");
      await fs.mkdir(srcDir, { recursive: true });
      await fs.writeFile(
        path.join(srcDir, "user.ts"),
        "export function getUser() {}",
        "utf-8",
      );

      const codeFiles = await createCodeFileEntities(projectId, testDir);

      // Doc with no code references
      const extractedContent: ExtractedContent = {
        existingDocs: [
          {
            path: "docs/guide.md",
            title: "Guide",
            content: "This is a general guide with no code references",
            category: "tutorial",
          },
        ],
        adrs: [],
        codeExamples: [],
        apiDocs: [],
      };

      const docSections = await createDocumentationEntities(
        projectId,
        extractedContent,
      );

      const edges = await linkCodeToDocs(codeFiles, docSections);

      // Should not create edges between unrelated code and docs
      expect(edges.length).toBe(0);
    });
  });
});

```

--------------------------------------------------------------------------------
/tests/tools/recommend-ssg-historical.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for Phase 2.1: Historical Deployment Data Integration
 * Tests the enhanced recommend_ssg tool with knowledge graph integration
 */

import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
import { promises as fs } from "fs";
import { join } from "path";
import { tmpdir } from "os";
import {
  initializeKnowledgeGraph,
  createOrUpdateProject,
  trackDeployment,
  getMemoryManager,
} from "../../src/memory/kg-integration.js";
import { recommendSSG } from "../../src/tools/recommend-ssg.js";
import { MemoryManager } from "../../src/memory/manager.js";

describe("recommendSSG with Historical Data (Phase 2.1)", () => {
  let testDir: string;
  let originalEnv: string | undefined;
  let memoryManager: MemoryManager;

  beforeEach(async () => {
    // Create temporary test directory
    testDir = join(tmpdir(), `recommend-ssg-historical-test-${Date.now()}`);
    await fs.mkdir(testDir, { recursive: true });

    // Set environment variable for storage
    originalEnv = process.env.DOCUMCP_STORAGE_DIR;
    process.env.DOCUMCP_STORAGE_DIR = testDir;

    // Initialize KG and memory - this creates the global memory manager
    await initializeKnowledgeGraph(testDir);

    // Use the same memory manager instance that kg-integration created
    memoryManager = await getMemoryManager();
  });

  afterEach(async () => {
    // Restore environment
    if (originalEnv) {
      process.env.DOCUMCP_STORAGE_DIR = originalEnv;
    } else {
      delete process.env.DOCUMCP_STORAGE_DIR;
    }

    // Clean up test directory
    try {
      await fs.rm(testDir, { recursive: true, force: true });
    } catch (error) {
      console.warn("Failed to clean up test directory:", error);
    }
  });

  describe("Historical Data Retrieval", () => {
    it("should include historical data when similar projects exist", async () => {
      // Create a project with successful deployments
      const project1 = await createOrUpdateProject({
        id: "test_project_1",
        timestamp: new Date().toISOString(),
        path: "/test/project1",
        projectName: "Test Project 1",
        structure: {
          totalFiles: 50,
          languages: { typescript: 30, javascript: 20 },
          hasTests: true,
          hasCI: false,
          hasDocs: false,
        },
      });

      // Track successful Docusaurus deployments
      await trackDeployment(project1.id, "docusaurus", true, {
        buildTime: 45,
      });
      await trackDeployment(project1.id, "docusaurus", true, {
        buildTime: 42,
      });

      // Store analysis in memory for recommendation
      const memoryEntry = await memoryManager.remember("analysis", {
        path: "/test/project2",
        dependencies: {
          ecosystem: "javascript",
          languages: ["typescript", "javascript"],
        },
        structure: { totalFiles: 60 },
      });

      // Get recommendation
      const result = await recommendSSG({
        analysisId: memoryEntry.id,
        preferences: {},
      });

      const content = result.content[0];
      expect(content.type).toBe("text");
      const data = JSON.parse(content.text);

      // Should include historical data
      expect(data.historicalData).toBeDefined();
      expect(data.historicalData.similarProjectCount).toBeGreaterThan(0);
      expect(data.historicalData.successRates.docusaurus).toBeDefined();
      expect(data.historicalData.successRates.docusaurus.rate).toBe(1.0);
      expect(data.historicalData.successRates.docusaurus.sampleSize).toBe(2);
    });

    it("should boost confidence when historical success rate is high", async () => {
      // Create multiple successful projects
      for (let i = 0; i < 3; i++) {
        const project = await createOrUpdateProject({
          id: `project_${i}`,
          timestamp: new Date().toISOString(),
          path: `/test/project${i}`,
          projectName: `Project ${i}`,
          structure: {
            totalFiles: 50,
            languages: { typescript: 50 },
            hasTests: true,
            hasCI: false,
            hasDocs: false,
          },
        });

        // Track successful Hugo deployments
        await trackDeployment(project.id, "hugo", true, { buildTime: 30 });
      }

      // Store analysis
      const memoryEntry = await memoryManager.remember("analysis", {
        path: "/test/new-project",
        dependencies: {
          ecosystem: "go",
          languages: ["typescript"],
        },
        structure: { totalFiles: 60 },
      });

      const result = await recommendSSG({ analysisId: memoryEntry.id });
      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Should have high confidence due to historical success
      expect(data.confidence).toBeGreaterThan(0.9);
      expect(data.reasoning[0]).toContain("100% success rate");
    });

    it("should reduce confidence when historical success rate is low", async () => {
      // Create project with failed deployments
      const project = await createOrUpdateProject({
        id: "failing_project",
        timestamp: new Date().toISOString(),
        path: "/test/failing",
        projectName: "Failing Project",
        structure: {
          totalFiles: 50,
          languages: { python: 50 },
          hasTests: true,
          hasCI: false,
          hasDocs: false,
        },
      });

      // Track mostly failed Jekyll deployments
      await trackDeployment(project.id, "jekyll", false, {
        errorMessage: "Build failed",
      });
      await trackDeployment(project.id, "jekyll", false, {
        errorMessage: "Build failed",
      });
      await trackDeployment(project.id, "jekyll", true, { buildTime: 60 });

      // Store analysis
      const memoryEntry003 = await memoryManager.remember("analysis", {
        path: "/test/new-python",
        dependencies: {
          ecosystem: "python",
          languages: ["python"],
        },
        structure: { totalFiles: 60 },
      });

      const result = await recommendSSG({ analysisId: memoryEntry003.id });
      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Should have reduced confidence
      expect(data.confidence).toBeLessThan(0.8);
      expect(data.reasoning[0]).toContain("33% success rate");
    });

    it("should switch to top performer when significantly better", async () => {
      // Create projects with mixed results
      const project1 = await createOrUpdateProject({
        id: "project_mixed_1",
        timestamp: new Date().toISOString(),
        path: "/test/mixed1",
        projectName: "Mixed Project 1",
        structure: {
          totalFiles: 50,
          languages: { javascript: 50 },
          hasTests: true,
          hasCI: false,
          hasDocs: false,
        },
      });

      // Docusaurus: 50% success rate (2 samples)
      await trackDeployment(project1.id, "docusaurus", true);
      await trackDeployment(project1.id, "docusaurus", false);

      // Eleventy: 100% success rate (3 samples)
      const project2 = await createOrUpdateProject({
        id: "project_mixed_2",
        timestamp: new Date().toISOString(),
        path: "/test/mixed2",
        projectName: "Mixed Project 2",
        structure: {
          totalFiles: 50,
          languages: { javascript: 50 },
          hasTests: true,
          hasCI: false,
          hasDocs: false,
        },
      });

      await trackDeployment(project2.id, "eleventy", true);
      await trackDeployment(project2.id, "eleventy", true);
      await trackDeployment(project2.id, "eleventy", true);

      // Store analysis preferring JavaScript
      const memoryEntry004 = await memoryManager.remember("analysis", {
        path: "/test/new-js",
        dependencies: {
          ecosystem: "javascript",
          languages: ["javascript"],
        },
        structure: { totalFiles: 40 },
      });

      const result = await recommendSSG({ analysisId: memoryEntry004.id });
      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Should switch to Eleventy due to better success rate
      expect(data.recommended).toBe("eleventy");
      expect(data.reasoning[0]).toContain("Switching to eleventy");
      expect(data.reasoning[0]).toContain("100% success rate");
    });

    it("should mention top performer as alternative if not switching", async () => {
      // Create successful Hugo deployments
      const project = await createOrUpdateProject({
        id: "hugo_success",
        timestamp: new Date().toISOString(),
        path: "/test/hugo",
        projectName: "Hugo Success",
        structure: {
          totalFiles: 100,
          languages: { go: 80, markdown: 20 },
          hasTests: true,
          hasCI: false,
          hasDocs: false,
        },
      });

      await trackDeployment(project.id, "hugo", true);
      await trackDeployment(project.id, "hugo", true);

      // Store analysis for different ecosystem
      const memoryEntry005 = await memoryManager.remember("analysis", {
        path: "/test/new-python",
        dependencies: {
          ecosystem: "python",
          languages: ["python"],
        },
        structure: { totalFiles: 60 },
      });

      const result = await recommendSSG({ analysisId: memoryEntry005.id });
      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Should keep Python recommendation but mention Hugo
      expect(data.recommended).toBe("mkdocs");
      const hugoMention = data.reasoning.find((r: string) =>
        r.includes("hugo"),
      );
      expect(hugoMention).toBeDefined();
    });

    it("should include deployment statistics in reasoning", async () => {
      // Create multiple projects with various deployments
      for (let i = 0; i < 3; i++) {
        const project = await createOrUpdateProject({
          id: `stats_project_${i}`,
          timestamp: new Date().toISOString(),
          path: `/test/stats${i}`,
          projectName: `Stats Project ${i}`,
          structure: {
            totalFiles: 50,
            languages: { typescript: 50 },
            hasTests: true,
            hasCI: false,
            hasDocs: false,
          },
        });

        await trackDeployment(project.id, "docusaurus", true);
        await trackDeployment(project.id, "docusaurus", true);
      }

      const memoryEntry006 = await memoryManager.remember("analysis", {
        path: "/test/stats-new",
        dependencies: {
          ecosystem: "javascript",
          languages: ["typescript"],
        },
        structure: { totalFiles: 50 },
      });

      const result = await recommendSSG({ analysisId: memoryEntry006.id });
      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Should mention deployment statistics
      const statsReasoning = data.reasoning.find((r: string) =>
        r.includes("deployment(s) across"),
      );
      expect(statsReasoning).toBeDefined();
      expect(statsReasoning).toContain("6 deployment(s)");
      expect(statsReasoning).toContain("3 similar project(s)");
    });
  });

  describe("Historical Data Structure", () => {
    it("should provide complete historical data structure", async () => {
      const project = await createOrUpdateProject({
        id: "structure_test",
        timestamp: new Date().toISOString(),
        path: "/test/structure",
        projectName: "Structure Test",
        structure: {
          totalFiles: 50,
          languages: { javascript: 50 },
          hasTests: true,
          hasCI: false,
          hasDocs: false,
        },
      });

      await trackDeployment(project.id, "jekyll", true);
      await trackDeployment(project.id, "hugo", true);
      await trackDeployment(project.id, "hugo", true);

      const memoryEntry007 = await memoryManager.remember("analysis", {
        path: "/test/structure-new",
        dependencies: {
          ecosystem: "javascript",
          languages: ["javascript"],
        },
        structure: { totalFiles: 50 },
      });

      const result = await recommendSSG({ analysisId: memoryEntry007.id });
      const content = result.content[0];
      const data = JSON.parse(content.text);

      expect(data.historicalData).toBeDefined();
      expect(data.historicalData.similarProjectCount).toBe(1);
      expect(data.historicalData.successRates).toBeDefined();
      expect(data.historicalData.successRates.jekyll).toEqual({
        rate: 1.0,
        sampleSize: 1,
      });
      expect(data.historicalData.successRates.hugo).toEqual({
        rate: 1.0,
        sampleSize: 2,
      });
      expect(data.historicalData.topPerformer).toBeDefined();
      expect(data.historicalData.topPerformer?.ssg).toBe("hugo");
      expect(data.historicalData.topPerformer?.deploymentCount).toBe(2);
    });

    it("should handle no historical data gracefully", async () => {
      const memoryEntry008 = await memoryManager.remember("analysis", {
        path: "/test/no-history",
        dependencies: {
          ecosystem: "ruby",
          languages: ["ruby"],
        },
        structure: { totalFiles: 30 },
      });

      const result = await recommendSSG({ analysisId: memoryEntry008.id });
      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Should still make recommendation
      expect(data.recommended).toBe("jekyll");
      expect(data.confidence).toBeGreaterThan(0);

      // Historical data should show no similar projects
      expect(data.historicalData).toBeDefined();
      expect(data.historicalData.similarProjectCount).toBe(0);
      expect(Object.keys(data.historicalData.successRates)).toHaveLength(0);
    });
  });

  describe("Edge Cases", () => {
    it("should handle single deployment samples cautiously", async () => {
      const project = await createOrUpdateProject({
        id: "single_sample",
        timestamp: new Date().toISOString(),
        path: "/test/single",
        projectName: "Single Sample",
        structure: {
          totalFiles: 50,
          languages: { python: 50 },
          hasTests: true,
          hasCI: false,
          hasDocs: false,
        },
      });

      // Single successful deployment
      await trackDeployment(project.id, "mkdocs", true);

      const memoryEntry009 = await memoryManager.remember("analysis", {
        path: "/test/single-new",
        dependencies: {
          ecosystem: "python",
          languages: ["python"],
        },
        structure: { totalFiles: 50 },
      });

      const result = await recommendSSG({ analysisId: memoryEntry009.id });
      const content = result.content[0];
      const data = JSON.parse(content.text);

      // Should not be a top performer with only 1 sample
      expect(data.historicalData?.topPerformer).toBeUndefined();
    });

    it("should handle knowledge graph initialization failure", async () => {
      // Use invalid storage directory
      const invalidDir = "/invalid/path/that/does/not/exist";
      const memoryEntry010 = await memoryManager.remember("analysis", {
        path: "/test/kg-fail",
        dependencies: {
          ecosystem: "javascript",
          languages: ["javascript"],
        },
        structure: { totalFiles: 50 },
      });

      // Should still make recommendation despite KG failure
      const result = await recommendSSG({ analysisId: memoryEntry010.id });
      const content = result.content[0];
      const data = JSON.parse(content.text);

      expect(data.recommended).toBeDefined();
      expect(data.confidence).toBeGreaterThan(0);
    });
  });
});

```

--------------------------------------------------------------------------------
/src/memory/deployment-analytics.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Deployment Analytics Module
 * Phase 2.4: Pattern Analysis and Insights
 *
 * Analyzes deployment history to identify patterns, trends, and provide insights
 */

import { getKnowledgeGraph } from "./kg-integration.js";
import { GraphNode, GraphEdge } from "./knowledge-graph.js";

export interface DeploymentPattern {
  ssg: string;
  totalDeployments: number;
  successfulDeployments: number;
  failedDeployments: number;
  successRate: number;
  averageBuildTime?: number;
  commonTechnologies: string[];
  projectCount: number;
}

export interface DeploymentTrend {
  period: string;
  deployments: number;
  successRate: number;
  topSSG: string;
}

export interface DeploymentInsight {
  type: "success" | "warning" | "recommendation";
  title: string;
  description: string;
  ssg?: string;
  metric?: number;
}

export interface AnalyticsReport {
  summary: {
    totalProjects: number;
    totalDeployments: number;
    overallSuccessRate: number;
    mostUsedSSG: string;
    mostSuccessfulSSG: string;
  };
  patterns: DeploymentPattern[];
  insights: DeploymentInsight[];
  recommendations: string[];
}

/**
 * Deployment Analytics Engine
 */
export class DeploymentAnalytics {
  /**
   * Generate comprehensive analytics report
   */
  async generateReport(): Promise<AnalyticsReport> {
    const kg = await getKnowledgeGraph();

    // Get all projects and deployments
    const projects = await kg.findNodes({ type: "project" });
    const deploymentEdges = await kg.findEdges({
      properties: { baseType: "project_deployed_with" },
    });

    // Aggregate deployment data by SSG
    const ssgStats = await this.aggregateSSGStatistics(
      projects,
      deploymentEdges,
    );

    // Calculate summary metrics
    const summary = this.calculateSummary(ssgStats, projects.length);

    // Identify patterns
    const patterns = this.identifyPatterns(ssgStats);

    // Generate insights
    const insights = this.generateInsights(patterns, summary);

    // Generate recommendations
    const recommendations = this.generateRecommendations(patterns, insights);

    return {
      summary,
      patterns,
      insights,
      recommendations,
    };
  }

  /**
   * Get deployment statistics for a specific SSG
   */
  async getSSGStatistics(ssg: string): Promise<DeploymentPattern | null> {
    const kg = await getKnowledgeGraph();

    const deployments = await kg.findEdges({
      properties: { baseType: "project_deployed_with" },
    });

    const allNodes = await kg.getAllNodes();

    // Filter deployments for this SSG
    const ssgDeployments = deployments.filter((edge) => {
      const configNode = allNodes.find((n) => n.id === edge.target);
      return configNode?.properties.ssg === ssg;
    });

    if (ssgDeployments.length === 0) {
      return null;
    }

    const successful = ssgDeployments.filter(
      (d) => d.properties.success,
    ).length;
    const failed = ssgDeployments.length - successful;

    // Calculate average build time
    const buildTimes = ssgDeployments
      .filter((d) => d.properties.buildTime)
      .map((d) => d.properties.buildTime as number);

    const averageBuildTime =
      buildTimes.length > 0
        ? buildTimes.reduce((a, b) => a + b, 0) / buildTimes.length
        : undefined;

    // Get unique projects using this SSG
    const projectIds = new Set(ssgDeployments.map((d) => d.source));

    // Get common technologies from projects
    const technologies = new Set<string>();
    for (const projectId of projectIds) {
      const project = allNodes.find((n) => n.id === projectId);
      if (project?.properties.technologies) {
        project.properties.technologies.forEach((tech: string) =>
          technologies.add(tech),
        );
      }
    }

    return {
      ssg,
      totalDeployments: ssgDeployments.length,
      successfulDeployments: successful,
      failedDeployments: failed,
      successRate: successful / ssgDeployments.length,
      averageBuildTime,
      commonTechnologies: Array.from(technologies),
      projectCount: projectIds.size,
    };
  }

  /**
   * Compare multiple SSGs
   */
  async compareSSGs(
    ssgs: string[],
  ): Promise<{ ssg: string; pattern: DeploymentPattern }[]> {
    const comparisons: { ssg: string; pattern: DeploymentPattern }[] = [];

    for (const ssg of ssgs) {
      const pattern = await this.getSSGStatistics(ssg);
      if (pattern) {
        comparisons.push({ ssg, pattern });
      }
    }

    // Sort by success rate
    return comparisons.sort(
      (a, b) => b.pattern.successRate - a.pattern.successRate,
    );
  }

  /**
   * Identify deployment trends over time
   */
  async identifyTrends(periodDays: number = 30): Promise<DeploymentTrend[]> {
    const kg = await getKnowledgeGraph();
    const deployments = await kg.findEdges({
      properties: { baseType: "project_deployed_with" },
    });

    // Group deployments by time period
    const now = Date.now();
    const periodMs = periodDays * 24 * 60 * 60 * 1000;

    const trends: Map<string, DeploymentTrend> = new Map();

    for (const deployment of deployments) {
      const timestamp = deployment.properties.timestamp;
      if (!timestamp) continue;

      const deploymentTime = new Date(timestamp).getTime();
      const periodsAgo = Math.floor((now - deploymentTime) / periodMs);

      if (periodsAgo < 0 || periodsAgo > 12) continue; // Last 12 periods

      const periodKey = `${periodsAgo} periods ago`;

      if (!trends.has(periodKey)) {
        trends.set(periodKey, {
          period: periodKey,
          deployments: 0,
          successRate: 0,
          topSSG: "",
        });
      }

      const trend = trends.get(periodKey)!;
      trend.deployments++;

      if (deployment.properties.success) {
        trend.successRate++;
      }
    }

    // Calculate success rates and identify top SSG per period
    for (const trend of trends.values()) {
      trend.successRate = trend.successRate / trend.deployments;
    }

    return Array.from(trends.values()).sort((a, b) =>
      a.period.localeCompare(b.period),
    );
  }

  /**
   * Get deployment health score (0-100)
   */
  async getHealthScore(): Promise<{
    score: number;
    factors: {
      name: string;
      impact: number;
      status: "good" | "warning" | "critical";
    }[];
  }> {
    const report = await this.generateReport();

    const factors: {
      name: string;
      impact: number;
      status: "good" | "warning" | "critical";
    }[] = [];
    let totalScore = 0;

    // Factor 1: Overall success rate (40 points)
    const successRateScore = report.summary.overallSuccessRate * 40;
    totalScore += successRateScore;
    factors.push({
      name: "Overall Success Rate",
      impact: successRateScore,
      status:
        report.summary.overallSuccessRate > 0.8
          ? "good"
          : report.summary.overallSuccessRate > 0.5
            ? "warning"
            : "critical",
    });

    // Factor 2: Number of projects (20 points)
    const projectScore = Math.min(20, report.summary.totalProjects * 2);
    totalScore += projectScore;
    factors.push({
      name: "Active Projects",
      impact: projectScore,
      status:
        report.summary.totalProjects > 5
          ? "good"
          : report.summary.totalProjects > 2
            ? "warning"
            : "critical",
    });

    // Factor 3: Deployment frequency (20 points)
    const deploymentScore = Math.min(20, report.summary.totalDeployments * 1.5);
    totalScore += deploymentScore;
    factors.push({
      name: "Deployment Activity",
      impact: deploymentScore,
      status:
        report.summary.totalDeployments > 10
          ? "good"
          : report.summary.totalDeployments > 5
            ? "warning"
            : "critical",
    });

    // Factor 4: SSG diversity (20 points)
    const ssgDiversity = report.patterns.length;
    const diversityScore = Math.min(20, ssgDiversity * 5);
    totalScore += diversityScore;
    factors.push({
      name: "SSG Diversity",
      impact: diversityScore,
      status:
        ssgDiversity > 3 ? "good" : ssgDiversity > 1 ? "warning" : "critical",
    });

    return {
      score: Math.round(totalScore),
      factors,
    };
  }

  /**
   * Private: Aggregate SSG statistics
   */
  private async aggregateSSGStatistics(
    projects: GraphNode[],
    deploymentEdges: GraphEdge[],
  ): Promise<Map<string, DeploymentPattern>> {
    const kg = await getKnowledgeGraph();
    const allNodes = await kg.getAllNodes();
    const ssgStats = new Map<string, DeploymentPattern>();

    for (const deployment of deploymentEdges) {
      const configNode = allNodes.find((n) => n.id === deployment.target);
      if (!configNode || configNode.type !== "configuration") continue;

      const ssg = configNode.properties.ssg;
      if (!ssg) continue;

      if (!ssgStats.has(ssg)) {
        ssgStats.set(ssg, {
          ssg,
          totalDeployments: 0,
          successfulDeployments: 0,
          failedDeployments: 0,
          successRate: 0,
          commonTechnologies: [],
          projectCount: 0,
        });
      }

      const stats = ssgStats.get(ssg)!;
      stats.totalDeployments++;

      if (deployment.properties.success) {
        stats.successfulDeployments++;
      } else {
        stats.failedDeployments++;
      }

      // Track build times
      if (deployment.properties.buildTime) {
        if (!stats.averageBuildTime) {
          stats.averageBuildTime = 0;
        }
        stats.averageBuildTime += deployment.properties.buildTime;
      }
    }

    // Calculate final metrics
    for (const stats of ssgStats.values()) {
      stats.successRate = stats.successfulDeployments / stats.totalDeployments;
      if (stats.averageBuildTime) {
        stats.averageBuildTime /= stats.totalDeployments;
      }
    }

    return ssgStats;
  }

  /**
   * Private: Calculate summary metrics
   */
  private calculateSummary(
    ssgStats: Map<string, DeploymentPattern>,
    projectCount: number,
  ): AnalyticsReport["summary"] {
    let totalDeployments = 0;
    let totalSuccessful = 0;
    let mostUsedSSG = "";
    let mostUsedCount = 0;
    let mostSuccessfulSSG = "";
    let highestSuccessRate = 0;

    for (const [ssg, stats] of ssgStats.entries()) {
      totalDeployments += stats.totalDeployments;
      totalSuccessful += stats.successfulDeployments;

      if (stats.totalDeployments > mostUsedCount) {
        mostUsedCount = stats.totalDeployments;
        mostUsedSSG = ssg;
      }

      if (
        stats.successRate > highestSuccessRate &&
        stats.totalDeployments >= 2
      ) {
        highestSuccessRate = stats.successRate;
        mostSuccessfulSSG = ssg;
      }
    }

    return {
      totalProjects: projectCount,
      totalDeployments,
      overallSuccessRate:
        totalDeployments > 0 ? totalSuccessful / totalDeployments : 0,
      mostUsedSSG: mostUsedSSG || "none",
      mostSuccessfulSSG: mostSuccessfulSSG || mostUsedSSG || "none",
    };
  }

  /**
   * Private: Identify patterns
   */
  private identifyPatterns(
    ssgStats: Map<string, DeploymentPattern>,
  ): DeploymentPattern[] {
    return Array.from(ssgStats.values()).sort(
      (a, b) => b.totalDeployments - a.totalDeployments,
    );
  }

  /**
   * Private: Generate insights
   */
  private generateInsights(
    patterns: DeploymentPattern[],
    summary: AnalyticsReport["summary"],
  ): DeploymentInsight[] {
    const insights: DeploymentInsight[] = [];

    // Overall health insight
    if (summary.overallSuccessRate > 0.8) {
      insights.push({
        type: "success",
        title: "High Success Rate",
        description: `Excellent! ${(summary.overallSuccessRate * 100).toFixed(
          1,
        )}% of deployments succeed`,
        metric: summary.overallSuccessRate,
      });
    } else if (summary.overallSuccessRate < 0.5) {
      insights.push({
        type: "warning",
        title: "Low Success Rate",
        description: `Only ${(summary.overallSuccessRate * 100).toFixed(
          1,
        )}% of deployments succeed. Review common failure patterns.`,
        metric: summary.overallSuccessRate,
      });
    }

    // SSG-specific insights
    for (const pattern of patterns) {
      if (pattern.successRate === 1.0 && pattern.totalDeployments >= 3) {
        insights.push({
          type: "success",
          title: `${pattern.ssg} Perfect Track Record`,
          description: `All ${pattern.totalDeployments} deployments with ${pattern.ssg} succeeded`,
          ssg: pattern.ssg,
          metric: pattern.successRate,
        });
      } else if (pattern.successRate < 0.5 && pattern.totalDeployments >= 2) {
        insights.push({
          type: "warning",
          title: `${pattern.ssg} Struggling`,
          description: `Only ${(pattern.successRate * 100).toFixed(
            0,
          )}% success rate with ${pattern.ssg}`,
          ssg: pattern.ssg,
          metric: pattern.successRate,
        });
      }

      // Build time insights
      if (pattern.averageBuildTime) {
        if (pattern.averageBuildTime < 30000) {
          insights.push({
            type: "success",
            title: `${pattern.ssg} Fast Builds`,
            description: `Average build time: ${(
              pattern.averageBuildTime / 1000
            ).toFixed(1)}s`,
            ssg: pattern.ssg,
            metric: pattern.averageBuildTime,
          });
        } else if (pattern.averageBuildTime > 120000) {
          insights.push({
            type: "warning",
            title: `${pattern.ssg} Slow Builds`,
            description: `Average build time: ${(
              pattern.averageBuildTime / 1000
            ).toFixed(1)}s. Consider optimization.`,
            ssg: pattern.ssg,
            metric: pattern.averageBuildTime,
          });
        }
      }
    }

    return insights;
  }

  /**
   * Private: Generate recommendations
   */
  private generateRecommendations(
    patterns: DeploymentPattern[],
    insights: DeploymentInsight[],
  ): string[] {
    const recommendations: string[] = [];

    // Find best performing SSG
    const bestSSG = patterns.find(
      (p) => p.successRate > 0.8 && p.totalDeployments >= 2,
    );
    if (bestSSG) {
      recommendations.push(
        `Consider using ${bestSSG.ssg} for new projects (${(
          bestSSG.successRate * 100
        ).toFixed(0)}% success rate)`,
      );
    }

    // Identify problematic SSGs
    const problematicSSG = patterns.find(
      (p) => p.successRate < 0.5 && p.totalDeployments >= 3,
    );
    if (problematicSSG) {
      recommendations.push(
        `Review ${problematicSSG.ssg} deployment process - ${problematicSSG.failedDeployments} recent failures`,
      );
    }

    // Diversity recommendation
    if (patterns.length < 2) {
      recommendations.push(
        "Experiment with different SSGs to find the best fit for different project types",
      );
    }

    // Activity recommendation
    const totalDeployments = patterns.reduce(
      (sum, p) => sum + p.totalDeployments,
      0,
    );
    if (totalDeployments < 5) {
      recommendations.push(
        "Deploy more projects to build a robust historical dataset for better recommendations",
      );
    }

    // Warning-based recommendations
    const warnings = insights.filter((i) => i.type === "warning");
    if (warnings.length > 2) {
      recommendations.push(
        "Multiple deployment issues detected - consider reviewing documentation setup process",
      );
    }

    return recommendations;
  }
}

/**
 * Get singleton analytics instance
 */
let analyticsInstance: DeploymentAnalytics | null = null;

export function getDeploymentAnalytics(): DeploymentAnalytics {
  if (!analyticsInstance) {
    analyticsInstance = new DeploymentAnalytics();
  }
  return analyticsInstance;
}

```

--------------------------------------------------------------------------------
/docs/adrs/adr-0004-diataxis-framework-integration.md:
--------------------------------------------------------------------------------

```markdown
---
id: adr-4-diataxis-framework-integration
title: "ADR-004: Diataxis Framework Integration"
sidebar_label: "ADR-004: Diataxis Framework Integration"
sidebar_position: 4
documcp:
  last_updated: "2025-01-14T00:00:00.000Z"
  last_validated: "2025-01-14T00:00:00.000Z"
  auto_updated: false
  update_frequency: monthly
  validated_against_commit: 9bbac23
---

# ADR-004: Diataxis Framework Integration for Documentation Structure

## Status

Accepted

## Context

DocuMCP aims to improve the quality and effectiveness of technical documentation by implementing proven information architecture principles. The Diataxis framework provides a systematic approach to organizing technical documentation into four distinct categories that serve different user needs and learning contexts.

Diataxis Framework Components:

- **Tutorials**: Learning-oriented content for skill acquisition (study context)
- **How-to Guides**: Problem-solving oriented content for specific tasks (work context)
- **Reference**: Information-oriented content for lookup and verification (information context)
- **Explanation**: Understanding-oriented content for context and background (understanding context)

Current documentation challenges:

- Most projects mix different content types without clear organization
- Users struggle to find appropriate content for their current needs
- Documentation often fails to serve different user contexts effectively
- Information architecture is typically ad-hoc and inconsistent

The framework addresses fundamental differences in user intent:

- **Study vs. Work**: Different contexts require different content approaches
- **Acquisition vs. Application**: Learning new skills vs. applying existing knowledge
- **Practical vs. Theoretical**: Task completion vs. understanding concepts

## Decision

We will integrate the Diataxis framework as the foundational information architecture for all DocuMCP-generated documentation structures, with intelligent content planning and navigation generation adapted to each static site generator's capabilities.

### Integration Approach:

#### 1. Automated Structure Generation

- **Directory organization** that clearly separates Diataxis content types
- **Navigation systems** that help users understand content categorization
- **Template generation** for each content type with appropriate guidance
- **Cross-reference systems** that maintain logical relationships between content types

#### 2. Content Type Templates

- **Tutorial templates** with learning objectives, prerequisites, step-by-step instructions
- **How-to guide templates** focused on problem-solution patterns
- **Reference templates** for systematic information organization
- **Explanation templates** for conceptual and architectural content

#### 3. Content Planning Intelligence

- **Automated content suggestions** based on project analysis
- **Gap identification** for missing content types
- **User journey mapping** to appropriate content categories
- **Content relationship mapping** to ensure comprehensive coverage

#### 4. SSG-Specific Implementation

- **Adaptation to SSG capabilities** while maintaining Diataxis principles
- **Theme and plugin recommendations** that support Diataxis organization
- **Navigation configuration** optimized for each SSG's features

## Alternatives Considered

### Generic Documentation Templates

- **Pros**: Simpler implementation, fewer constraints on content organization
- **Cons**: Perpetuates existing documentation quality problems, no systematic improvement
- **Decision**: Rejected due to missed opportunity for significant quality improvement

### Custom Documentation Framework

- **Pros**: Full control over documentation approach and features
- **Cons**: Reinventing proven methodology, reduced credibility, maintenance burden
- **Decision**: Rejected in favor of proven, established framework

### Multiple Framework Options

- **Pros**: Could accommodate different project preferences and approaches
- **Cons**: Choice paralysis, inconsistent quality, complex implementation
- **Decision**: Rejected to maintain focus and ensure consistent quality outcomes

### Optional Diataxis Integration

- **Pros**: Gives users choice, accommodates existing documentation structures
- **Cons**: Reduces value proposition, complicates implementation, inconsistent results
- **Decision**: Rejected to ensure consistent quality and educational value

## Consequences

### Positive

- **Improved Documentation Quality**: Systematic application of proven principles
- **Better User Experience**: Users can find appropriate content for their context
- **Educational Value**: Projects learn proper documentation organization
- **Consistency**: All DocuMCP projects benefit from same high-quality structure
- **Maintenance Benefits**: Clear content types simplify ongoing documentation work

### Negative

- **Learning Curve**: Teams need to understand Diataxis principles for optimal results
- **Initial Overhead**: More structure requires more initial planning and content creation
- **Rigidity**: Some projects might prefer different organizational approaches

### Risks and Mitigations

- **User Resistance**: Provide clear education about benefits and implementation guidance
- **Implementation Complexity**: Start with basic structure, enhance over time
- **Content Quality**: Provide high-quality templates and examples

## Implementation Details

### Diataxis Type Tracking in Code Examples (Phase 3 Enhancement)

Code examples in documentation are now tracked with Diataxis type information to enable context-aware validation and better content organization:

```typescript
interface CodeExample {
  language: string;
  code: string;
  description: string;
  referencedSymbols: string[];
  diataxisType?: "tutorial" | "how-to" | "reference" | "explanation";
  validationHints?: {
    expectedBehavior?: string;
    dependencies?: string[];
    contextRequired?: boolean;
  };
}
```

**Benefits**:

- Context-aware validation based on Diataxis category
- Improved code example organization and discovery
- Better drift detection for category-specific examples
- Enhanced content accuracy validation

**Implementation**: The drift detection system (ADR-009) uses Diataxis type information to provide category-specific validation rules and priority scoring.

### Directory Structure Generation

```typescript
interface DiataxisStructure {
  tutorials: DirectoryConfig;
  howToGuides: DirectoryConfig;
  reference: DirectoryConfig;
  explanation: DirectoryConfig;
  navigation: NavigationConfig;
}

const DIATAXIS_TEMPLATES: Record<SSGType, DiataxisStructure> = {
  hugo: {
    tutorials: { path: "content/tutorials", layout: "tutorial" },
    howToGuides: { path: "content/how-to", layout: "guide" },
    reference: { path: "content/reference", layout: "reference" },
    explanation: { path: "content/explanation", layout: "explanation" },
    navigation: { menu: "diataxis", weight: "category-based" },
  },
  // ... other SSG configurations
};
```

### Content Template System

```typescript
interface ContentTemplate {
  frontmatter: Record<string, any>;
  structure: ContentSection[];
  guidance: string[];
  examples: string[];
}

const TUTORIAL_TEMPLATE: ContentTemplate = {
  frontmatter: {
    title: "{{ tutorial_title }}",
    description: "{{ tutorial_description }}",
    difficulty: "{{ difficulty_level }}",
    prerequisites: "{{ prerequisites }}",
    estimated_time: "{{ time_estimate }}",
  },
  structure: [
    { section: "learning_objectives", required: true },
    { section: "prerequisites", required: true },
    { section: "step_by_step_instructions", required: true },
    { section: "verification", required: true },
    { section: "next_steps", required: false },
  ],
  guidance: [
    "Focus on learning and skill acquisition",
    "Provide complete, working examples",
    "Include verification steps for each major milestone",
    "Assume minimal prior knowledge",
  ],
};
```

### Content Planning Algorithm

```typescript
interface ContentPlan {
  tutorials: TutorialSuggestion[];
  howToGuides: HowToSuggestion[];
  reference: ReferenceSuggestion[];
  explanation: ExplanationSuggestion[];
}

function generateContentPlan(projectAnalysis: ProjectAnalysis): ContentPlan {
  return {
    tutorials: suggestTutorials(projectAnalysis),
    howToGuides: suggestHowToGuides(projectAnalysis),
    reference: suggestReference(projectAnalysis),
    explanation: suggestExplanation(projectAnalysis),
  };
}

function suggestTutorials(analysis: ProjectAnalysis): TutorialSuggestion[] {
  const suggestions: TutorialSuggestion[] = [];

  // Getting started tutorial (always recommended)
  suggestions.push({
    title: "Getting Started",
    description: "First steps with {{ project_name }}",
    priority: "high",
    estimated_effort: "medium",
  });

  // Feature-specific tutorials based on project complexity
  if (analysis.complexity.apiSurface > 5) {
    suggestions.push({
      title: "API Integration Tutorial",
      description: "Complete guide to integrating with the API",
      priority: "high",
      estimated_effort: "large",
    });
  }

  return suggestions;
}
```

### Navigation Generation

```typescript
interface DiataxisNavigation {
  structure: NavigationItem[];
  labels: NavigationLabels;
  descriptions: CategoryDescriptions;
}

const NAVIGATION_STRUCTURE: DiataxisNavigation = {
  structure: [
    {
      category: "tutorials",
      label: "Tutorials",
      description: "Learning-oriented guides",
      icon: "graduation-cap",
      order: 1,
    },
    {
      category: "how-to",
      label: "How-to Guides",
      description: "Problem-solving recipes",
      icon: "tools",
      order: 2,
    },
    {
      category: "reference",
      label: "Reference",
      description: "Technical information",
      icon: "book",
      order: 3,
    },
    {
      category: "explanation",
      label: "Explanation",
      description: "Understanding and context",
      icon: "lightbulb",
      order: 4,
    },
  ],
  labels: {
    tutorials: "Learn",
    howToGuides: "Solve",
    reference: "Lookup",
    explanation: "Understand",
  },
  descriptions: {
    tutorials: "Step-by-step learning paths",
    howToGuides: "Solutions to specific problems",
    reference: "Complete technical details",
    explanation: "Background and concepts",
  },
};
```

### SSG-Specific Adaptations

```typescript
interface SSGDiataxisAdapter {
  generateStructure(
    ssg: SSGType,
    project: ProjectAnalysis,
  ): DiataxisImplementation;
  createNavigation(
    ssg: SSGType,
    structure: DiataxisStructure,
  ): NavigationConfig;
  generateTemplates(ssg: SSGType, contentTypes: ContentType[]): TemplateSet;
}

class HugoDiataxisAdapter implements SSGDiataxisAdapter {
  generateStructure(
    ssg: SSGType,
    project: ProjectAnalysis,
  ): DiataxisImplementation {
    return {
      contentDirectories: this.createHugoContentStructure(),
      frontmatterSchemas: this.createHugoFrontmatter(),
      taxonomies: this.createDiataxisTaxonomies(),
      menuConfiguration: this.createHugoMenus(),
    };
  }

  createHugoContentStructure(): ContentStructure {
    return {
      "content/tutorials/": { weight: 10, section: "tutorials" },
      "content/how-to/": { weight: 20, section: "guides" },
      "content/reference/": { weight: 30, section: "reference" },
      "content/explanation/": { weight: 40, section: "explanation" },
    };
  }
}
```

## Quality Assurance

### Diataxis Compliance Validation

```typescript
interface DiataxisValidator {
  validateStructure(documentation: DocumentationStructure): ValidationResult;
  checkContentTypeAlignment(
    content: Content,
    declaredType: ContentType,
  ): AlignmentResult;
  identifyMissingCategories(structure: DocumentationStructure): Gap[];
}

function validateDiataxisCompliance(
  docs: DocumentationStructure,
): ComplianceReport {
  return {
    structureCompliance: checkDirectoryOrganization(docs),
    contentTypeAccuracy: validateContentCategorization(docs),
    navigationClarity: assessNavigationEffectiveness(docs),
    crossReferenceCompleteness: checkContentRelationships(docs),
  };
}
```

### Content Quality Guidelines

- **Tutorial Content**: Must include learning objectives, prerequisites, and verification steps
- **How-to Content**: Must focus on specific problems with clear solution steps
- **Reference Content**: Must be comprehensive, accurate, and systematically organized
- **Explanation Content**: Must provide context, background, and conceptual understanding

### Testing Strategy

- **Structure Tests**: Validate directory organization and navigation generation
- **Template Tests**: Ensure all content type templates are properly formatted
- **Integration Tests**: Test complete Diataxis implementation across different SSGs
- **User Experience Tests**: Validate that users can effectively navigate and find content

## Educational Integration

### User Guidance

- **Diataxis Explanation**: Clear documentation of framework benefits and principles
- **Content Type Guidelines**: Detailed guidance for creating each type of content
- **Migration Assistance**: Help converting existing documentation to Diataxis structure
- **Best Practice Examples**: Templates and examples demonstrating effective implementation

### Community Building

- **Diataxis Advocacy**: Promote framework adoption across open-source community
- **Success Story Sharing**: Highlight projects benefiting from Diataxis implementation
- **Training Resources**: Develop educational materials for technical writers and maintainers
- **Feedback Collection**: Gather community input for framework implementation improvements

## Future Enhancements

### Advanced Features

- **Content Gap Analysis**: AI-powered identification of missing content areas
- **User Journey Optimization**: Intelligent linking between content types based on user flows
- **Content Quality Scoring**: Automated assessment of content quality within each category
- **Personalized Navigation**: Adaptive navigation based on user role and experience level

### Tool Integration

- **Analytics Integration**: Track how users navigate between different content types
- **Content Management**: Tools for maintaining Diataxis compliance over time
- **Translation Support**: Multi-language implementations of Diataxis structure
- **Accessibility Features**: Ensure Diataxis implementation supports accessibility standards

## Implementation Status

**Status**: ✅ Implemented (2025-12-12)

**Implementation Files**:

- `src/tools/populate-content.ts` - Diataxis content population engine
- `src/prompts/technical-writer-prompts.ts` - Diataxis-aware prompt generation
- `src/utils/drift-detector.ts` - Diataxis type tracking in code examples
- `docs/tutorials/`, `docs/how-to/`, `docs/reference/`, `docs/explanation/` - Diataxis structure in use

**Key Features Implemented**:

- ✅ Automated Diataxis structure generation
- ✅ Content type templates (tutorials, how-to guides, reference, explanation)
- ✅ Content planning intelligence based on project analysis
- ✅ SSG-specific Diataxis adaptations
- ✅ Diataxis type tracking in code examples (Phase 3 enhancement)
- ✅ Navigation generation aligned with Diataxis principles

**Validation**: The framework is actively used in the documentation structure and content generation tools. Code examples include Diataxis type information for context-aware validation.

## References

- [Diataxis Framework Official Documentation](https://diataxis.fr/)
- [Information Architecture Principles](https://www.usability.gov/what-and-why/information-architecture.html)
- [Technical Writing Best Practices](https://developers.google.com/tech-writing)
- Commit: 9bbac23 - feat: Add Diataxis type tracking to CodeExample interface (#81)
- GitHub Issue: #81 - Diataxis type tracking to CodeExample interface

```

--------------------------------------------------------------------------------
/tests/utils/semantic-analyzer.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for Semantic Analyzer
 */

import {
  SemanticAnalyzer,
  createSemanticAnalyzer,
  type SemanticAnalysisOptions,
  type EnhancedSemanticAnalysis,
} from '../../src/utils/semantic-analyzer.js';
import { createLLMClient } from '../../src/utils/llm-client.js';

// Mock the LLM client module
jest.mock('../../src/utils/llm-client.js', () => {
  const actual = jest.requireActual('../../src/utils/llm-client.js');
  return {
    ...actual,
    createLLMClient: jest.fn(),
  };
});

const mockCreateLLMClient = createLLMClient as jest.MockedFunction<typeof createLLMClient>;

describe('SemanticAnalyzer', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('createSemanticAnalyzer', () => {
    test('should create analyzer with default options', () => {
      const analyzer = createSemanticAnalyzer();
      expect(analyzer).toBeInstanceOf(SemanticAnalyzer);
    });

    test('should create analyzer with custom options', () => {
      const analyzer = createSemanticAnalyzer({
        confidenceThreshold: 0.8,
        useLLM: false,
      });
      expect(analyzer).toBeInstanceOf(SemanticAnalyzer);
    });
  });

  describe('SemanticAnalyzer without LLM', () => {
    let analyzer: SemanticAnalyzer;

    beforeEach(async () => {
      mockCreateLLMClient.mockReturnValue(null);
      analyzer = new SemanticAnalyzer({ useLLM: false });
      await analyzer.initialize();
    });

    test('should initialize successfully', async () => {
      expect(analyzer).toBeDefined();
    });

    test('should report LLM as unavailable', () => {
      expect(analyzer.isLLMAvailable()).toBe(false);
    });

    describe('analyzeSemanticImpact - AST mode', () => {
      test('should detect no changes when code is identical', async () => {
        const code = 'function test(x: number) { return x * 2; }';
        const result = await analyzer.analyzeSemanticImpact(code, code);

        expect(result.analysisMode).toBe('ast');
        expect(result.hasBehavioralChange).toBe(false);
        expect(result.llmAvailable).toBe(false);
        expect(result.timestamp).toBeDefined();
      });

      test('should detect parameter changes', async () => {
        const before = 'function test(x: number) { return x * 2; }';
        const after = 'function test(x: number, y: string) { return x * 2; }';

        const result = await analyzer.analyzeSemanticImpact(before, after, 'test');

        expect(result.hasBehavioralChange).toBe(true);
        expect(result.breakingForExamples).toBe(true);
        expect(result.changeDescription).toContain('Breaking changes');
        expect(result.astDiffs).toBeDefined();
        expect(result.astDiffs!.length).toBeGreaterThan(0);
      });

      test('should detect async modifier changes', async () => {
        const before = 'function test() { return 42; }';
        const after = 'async function test() { return 42; }';

        const result = await analyzer.analyzeSemanticImpact(before, after, 'test');

        expect(result.hasBehavioralChange).toBe(true);
        expect(result.astDiffs).toBeDefined();
        expect(result.astDiffs!.some(d => d.details.includes('Async'))).toBe(true);
      });

      test('should detect return type changes', async () => {
        const before = 'function test(): number { return 42; }';
        const after = 'function test(): string { return "42"; }';

        const result = await analyzer.analyzeSemanticImpact(before, after, 'test');

        expect(result.hasBehavioralChange).toBe(true);
        expect(result.breakingForExamples).toBe(true);
        expect(result.astDiffs).toBeDefined();
      });

      test('should detect implementation changes', async () => {
        const before = 'function test(x: number) { return x * 2; }';
        const after = 'function test(x: number) { return x * 3; }';

        const result = await analyzer.analyzeSemanticImpact(before, after, 'test');

        expect(result.hasBehavioralChange).toBe(true);
        expect(result.astDiffs).toBeDefined();
      });

      test('should identify affected documentation sections', async () => {
        const before = 'function test(x: number): number { return x; }';
        const after = 'function test(x: string): number { return 0; }';

        const result = await analyzer.analyzeSemanticImpact(before, after, 'test');

        expect(result.affectedDocSections).toContain('API Reference');
      });

      test('should have moderate confidence for AST analysis', async () => {
        const before = 'function test(x: number) { return x * 2; }';
        const after = 'function test(x: number) { return x * 3; }';

        const result = await analyzer.analyzeSemanticImpact(before, after);

        expect(result.confidence).toBeGreaterThan(0);
        expect(result.confidence).toBeLessThanOrEqual(1);
      });
    });

    describe('validateExamples - without LLM', () => {
      test('should require manual review when LLM unavailable', async () => {
        const examples = ['const x = test(5);'];
        const implementation = 'function test(n) { return n * 2; }';

        const result = await analyzer.validateExamples(examples, implementation);

        expect(result.requiresManualReview).toBe(true);
        expect(result.overallConfidence).toBe(0);
        expect(result.suggestions).toContain('LLM not available - manual validation required');
      });
    });

    describe('analyzeBatch', () => {
      test('should analyze multiple changes', async () => {
        const changes = [
          {
            before: 'function a(x: number) { return x; }',
            after: 'function a(x: string) { return x; }',
            name: 'a',
          },
          {
            before: 'function b() { return 1; }',
            after: 'async function b() { return 1; }',
            name: 'b',
          },
        ];

        const results = await analyzer.analyzeBatch(changes);

        expect(results).toHaveLength(2);
        expect(results[0].analysisMode).toBe('ast');
        expect(results[1].analysisMode).toBe('ast');
      });
    });
  });

  describe('SemanticAnalyzer with LLM', () => {
    let analyzer: SemanticAnalyzer;
    let mockLLMClient: any;

    beforeEach(async () => {
      mockLLMClient = {
        complete: jest.fn(),
        analyzeCodeChange: jest.fn(),
        simulateExecution: jest.fn(),
      };

      mockCreateLLMClient.mockReturnValue(mockLLMClient);
      analyzer = new SemanticAnalyzer({ useLLM: true });
      await analyzer.initialize();
    });

    test('should report LLM as available', () => {
      expect(analyzer.isLLMAvailable()).toBe(true);
    });

    describe('analyzeSemanticImpact - LLM mode', () => {
      test('should use LLM analysis with high confidence', async () => {
        const mockAnalysis = {
          hasBehavioralChange: true,
          breakingForExamples: false,
          changeDescription: 'Implementation optimized',
          affectedDocSections: ['Performance'],
          confidence: 0.9,
        };

        mockLLMClient.analyzeCodeChange.mockResolvedValue(mockAnalysis);

        const before = 'function test(x) { return x * 2; }';
        const after = 'function test(x) { return x << 1; }';

        const result = await analyzer.analyzeSemanticImpact(before, after, 'test');

        expect(result.analysisMode).toBe('llm');
        expect(result.hasBehavioralChange).toBe(true);
        expect(result.confidence).toBe(0.9);
        expect(result.llmAvailable).toBe(true);
        expect(mockLLMClient.analyzeCodeChange).toHaveBeenCalledWith(before, after);
      });

      test('should use hybrid mode with low LLM confidence', async () => {
        const mockAnalysis = {
          hasBehavioralChange: true,
          breakingForExamples: false,
          changeDescription: 'Unclear change',
          affectedDocSections: [],
          confidence: 0.3, // Below threshold
        };

        mockLLMClient.analyzeCodeChange.mockResolvedValue(mockAnalysis);

        const before = 'function test(x: number) { return x * 2; }';
        const after = 'function test(x: number) { return x * 3; }';

        const result = await analyzer.analyzeSemanticImpact(before, after, 'test');

        expect(result.analysisMode).toBe('hybrid');
        expect(result.astDiffs).toBeDefined();
        expect(result.llmAvailable).toBe(true);
      });

      test('should fallback to AST when LLM fails', async () => {
        mockLLMClient.analyzeCodeChange.mockRejectedValue(new Error('LLM error'));

        const before = 'function test(x: number) { return x; }';
        const after = 'function test(x: string) { return x; }';

        const result = await analyzer.analyzeSemanticImpact(before, after, 'test');

        expect(result.analysisMode).toBe('ast');
        expect(result.llmAvailable).toBe(false);
      });

      test('should combine LLM and AST results in hybrid mode', async () => {
        const mockAnalysis = {
          hasBehavioralChange: false,
          breakingForExamples: false,
          changeDescription: 'Minor refactoring',
          affectedDocSections: ['Code Style'],
          confidence: 0.5,
        };

        mockLLMClient.analyzeCodeChange.mockResolvedValue(mockAnalysis);

        const before = 'function test(x: number): number { return x; }';
        const after = 'function test(x: string): string { return x; }';

        const result = await analyzer.analyzeSemanticImpact(before, after, 'test');

        expect(result.analysisMode).toBe('hybrid');
        expect(result.hasBehavioralChange).toBe(true); // AST detected breaking change
        expect(result.affectedDocSections.length).toBeGreaterThan(0);
        expect(result.changeDescription).toContain('Minor refactoring');
        expect(result.changeDescription).toContain('AST analysis');
      });
    });

    describe('validateExamples - with LLM', () => {
      test('should validate examples successfully', async () => {
        const mockSimulation = {
          success: true,
          expectedOutput: '10',
          actualOutput: '10',
          matches: true,
          differences: [],
          confidence: 0.95,
        };

        mockLLMClient.simulateExecution.mockResolvedValue(mockSimulation);

        const examples = ['const result = multiply(2, 5);'];
        const implementation = 'function multiply(a, b) { return a * b; }';

        const result = await analyzer.validateExamples(examples, implementation);

        expect(result.isValid).toBe(true);
        expect(result.examples).toHaveLength(1);
        expect(result.examples[0].isValid).toBe(true);
        expect(result.overallConfidence).toBeGreaterThan(0.9);
        expect(result.requiresManualReview).toBe(false);
      });

      test('should detect invalid examples', async () => {
        const mockSimulation = {
          success: true,
          expectedOutput: '10',
          actualOutput: '5',
          matches: false,
          differences: ['Output mismatch'],
          confidence: 0.85,
        };

        mockLLMClient.simulateExecution.mockResolvedValue(mockSimulation);

        const examples = ['const result = multiply(2, 5);'];
        const implementation = 'function multiply(a, b) { return a + b; }';

        const result = await analyzer.validateExamples(examples, implementation);

        expect(result.isValid).toBe(false);
        expect(result.examples[0].isValid).toBe(false);
        expect(result.examples[0].issues.length).toBeGreaterThan(0);
        expect(result.suggestions).toContain('1 example(s) may be invalid');
      });

      test('should handle validation errors gracefully', async () => {
        mockLLMClient.simulateExecution.mockRejectedValue(new Error('Simulation failed'));

        const examples = ['const result = test();'];
        const implementation = 'function test() {}';

        const result = await analyzer.validateExamples(examples, implementation);

        expect(result.isValid).toBe(false);
        expect(result.examples[0].isValid).toBe(false);
        expect(result.examples[0].issues).toContain('Validation failed');
      });

      test('should recommend manual review for low confidence', async () => {
        const mockSimulation = {
          success: true,
          expectedOutput: 'unknown',
          actualOutput: 'unknown',
          matches: true,
          differences: [],
          confidence: 0.4, // Below threshold
        };

        mockLLMClient.simulateExecution.mockResolvedValue(mockSimulation);

        const examples = ['test();'];
        const implementation = 'function test() {}';

        const result = await analyzer.validateExamples(examples, implementation);

        expect(result.requiresManualReview).toBe(true);
        expect(result.suggestions).toContain('Low confidence - manual review recommended');
      });

      test('should validate multiple examples', async () => {
        mockLLMClient.simulateExecution
          .mockResolvedValueOnce({
            success: true,
            expectedOutput: '10',
            actualOutput: '10',
            matches: true,
            differences: [],
            confidence: 0.9,
          })
          .mockResolvedValueOnce({
            success: true,
            expectedOutput: '20',
            actualOutput: '20',
            matches: true,
            differences: [],
            confidence: 0.85,
          });

        const examples = [
          'const a = multiply(2, 5);',
          'const b = multiply(4, 5);',
        ];
        const implementation = 'function multiply(a, b) { return a * b; }';

        const result = await analyzer.validateExamples(examples, implementation);

        expect(result.isValid).toBe(true);
        expect(result.examples).toHaveLength(2);
        expect(result.overallConfidence).toBeCloseTo(0.875, 2);
      });
    });

    describe('analyzeBatch - with LLM', () => {
      test('should analyze multiple changes with LLM', async () => {
        mockLLMClient.analyzeCodeChange
          .mockResolvedValueOnce({
            hasBehavioralChange: true,
            breakingForExamples: false,
            changeDescription: 'First change',
            affectedDocSections: ['API'],
            confidence: 0.9,
          })
          .mockResolvedValueOnce({
            hasBehavioralChange: false,
            breakingForExamples: false,
            changeDescription: 'Second change',
            affectedDocSections: [],
            confidence: 0.8,
          });

        const changes = [
          { before: 'code1', after: 'code2', name: 'func1' },
          { before: 'code3', after: 'code4', name: 'func2' },
        ];

        const results = await analyzer.analyzeBatch(changes);

        expect(results).toHaveLength(2);
        expect(results[0].analysisMode).toBe('llm');
        expect(results[1].analysisMode).toBe('llm');
        expect(mockLLMClient.analyzeCodeChange).toHaveBeenCalledTimes(2);
      });
    });
  });

  describe('Custom confidence threshold', () => {
    test('should use custom threshold for hybrid mode decision', async () => {
      const mockLLMClient = {
        complete: jest.fn(),
        analyzeCodeChange: jest.fn().mockResolvedValue({
          hasBehavioralChange: true,
          breakingForExamples: false,
          changeDescription: 'Change',
          affectedDocSections: [],
          confidence: 0.75,
        }),
        simulateExecution: jest.fn(),
      };

      mockCreateLLMClient.mockReturnValue(mockLLMClient);

      const analyzer = new SemanticAnalyzer({
        useLLM: true,
        confidenceThreshold: 0.8, // Higher than LLM confidence
      });
      await analyzer.initialize();

      const result = await analyzer.analyzeSemanticImpact('code1', 'code2');

      // Should use hybrid mode because confidence (0.75) < threshold (0.8)
      expect(result.analysisMode).toBe('hybrid');
    });
  });
});

```

--------------------------------------------------------------------------------
/tests/tools/generate-readme-template.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
import { promises as fs } from "fs";
import * as path from "path";
import * as tmp from "tmp";
import {
  generateReadmeTemplate,
  ReadmeTemplateGenerator,
  GenerateReadmeTemplateSchema,
  TemplateType,
} from "../../src/tools/generate-readme-template";

describe("README Template Generator", () => {
  let tempDir: string;
  let generator: ReadmeTemplateGenerator;

  beforeEach(() => {
    tempDir = tmp.dirSync({ unsafeCleanup: true }).name;
    generator = new ReadmeTemplateGenerator();
  });

  afterEach(async () => {
    try {
      await fs.rmdir(tempDir, { recursive: true });
    } catch {
      // Ignore cleanup errors
    }
  });

  describe("Input Validation", () => {
    it("should validate required fields", () => {
      expect(() => GenerateReadmeTemplateSchema.parse({})).toThrow();
      expect(() =>
        GenerateReadmeTemplateSchema.parse({
          projectName: "",
          description: "test",
        }),
      ).toThrow();
      expect(() =>
        GenerateReadmeTemplateSchema.parse({
          projectName: "test",
          description: "",
        }),
      ).toThrow();
    });

    it("should accept valid input with defaults", () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "test-project",
        description: "A test project",
        templateType: "library",
      });

      expect(input.license).toBe("MIT");
      expect(input.includeScreenshots).toBe(false);
      expect(input.includeBadges).toBe(true);
      expect(input.includeContributing).toBe(true);
    });

    it("should validate template types", () => {
      expect(() =>
        GenerateReadmeTemplateSchema.parse({
          projectName: "test",
          description: "test",
          templateType: "invalid-type",
        }),
      ).toThrow();

      const validTypes: TemplateType[] = [
        "library",
        "application",
        "cli-tool",
        "api",
        "documentation",
      ];
      for (const type of validTypes) {
        expect(() =>
          GenerateReadmeTemplateSchema.parse({
            projectName: "test",
            description: "test",
            templateType: type,
          }),
        ).not.toThrow();
      }
    });
  });

  describe("Template Generation", () => {
    it("should generate library template correctly", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "awesome-lib",
        description: "An awesome JavaScript library",
        templateType: "library",
        author: "john-doe",
      });
      const result = await generateReadmeTemplate(input);

      expect(result.content).toContain("# awesome-lib");
      expect(result.content).toContain("> An awesome JavaScript library");
      expect(result.content).toContain("npm install awesome-lib");
      expect(result.content).toContain(
        "const awesomeLib = require('awesome-lib')",
      );
      expect(result.content).toContain("## TL;DR");
      expect(result.content).toContain("## Quick Start");
      expect(result.content).toContain("## API Documentation");
      expect(result.content).toContain("MIT © john-doe");
      expect(result.metadata.templateType).toBe("library");
      expect(result.metadata.estimatedLength).toBe(150);
    });

    it("should generate application template correctly", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "my-app",
        description: "A web application",
        templateType: "application",
        author: "jane-doe",
        includeScreenshots: true,
      });
      const result = await generateReadmeTemplate(input);

      expect(result.content).toContain("# my-app");
      expect(result.content).toContain("> A web application");
      expect(result.content).toContain("## What This Does");
      expect(result.content).toContain(
        "git clone https://github.com/jane-doe/my-app.git",
      );
      expect(result.content).toContain("npm start");
      expect(result.content).toContain("## Configuration");
      expect(result.content).toContain("![my-app Screenshot]");
      expect(result.metadata.templateType).toBe("application");
    });

    it("should generate CLI tool template correctly", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "my-cli",
        description: "A command line tool",
        templateType: "cli-tool",
        author: "dev-user",
      });
      const result = await generateReadmeTemplate(input);

      expect(result.content).toContain("# my-cli");
      expect(result.content).toContain("npm install -g my-cli");
      expect(result.content).toContain("npx my-cli --help");
      expect(result.content).toContain("## Usage");
      expect(result.content).toContain("## Options");
      expect(result.content).toContain("| Option | Description | Default |");
      expect(result.metadata.templateType).toBe("cli-tool");
    });

    it("should handle camelCase conversion correctly", () => {
      const testCases = [
        { input: "my-awesome-lib", expected: "myAwesomeLib" },
        { input: "simple_package", expected: "simplePackage" },
        { input: "Mixed-Case_Name", expected: "mixedCaseName" },
        { input: "single", expected: "single" },
      ];

      for (const testCase of testCases) {
        const generator = new ReadmeTemplateGenerator();
        const input = GenerateReadmeTemplateSchema.parse({
          projectName: testCase.input,
          description: "test",
          templateType: "library",
        });
        const result = generator.generateTemplate(input);

        expect(result).toContain(
          `const ${testCase.expected} = require('${testCase.input}')`,
        );
      }
    });
  });

  describe("Badge Generation", () => {
    it("should include badges when enabled", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "badge-lib",
        description: "Library with badges",
        templateType: "library",
        author: "dev",
        includeBadges: true,
      });
      const result = await generateReadmeTemplate(input);

      expect(result.content).toContain("[![npm version]");
      expect(result.content).toContain("[![Build Status]");
      expect(result.content).toContain("[![License: MIT]");
      expect(result.content).toContain("dev/badge-lib");
    });

    it("should exclude badges when disabled", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "no-badge-lib",
        description: "Library without badges",
        templateType: "library",
        includeBadges: false,
      });
      const result = await generateReadmeTemplate(input);

      expect(result.content).not.toContain("[![");
    });

    it("should customize badge URLs with author", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "app-with-badges",
        description: "App with custom badges",
        templateType: "application",
        author: "dev",
        includeBadges: true,
      });
      const result = await generateReadmeTemplate(input);

      expect(result.content).toContain("dev/app-with-badges");
    });
  });

  describe("Screenshot Handling", () => {
    it("should include screenshot placeholder when enabled", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "screenshot-app",
        description: "App with screenshots",
        templateType: "application",
        includeScreenshots: true,
      });
      const result = await generateReadmeTemplate(input);

      expect(result.content).toContain(
        "![screenshot-app Screenshot](docs/screenshot.png)",
      );
      expect(result.content).toContain("*Add a screenshot or demo GIF here*");
    });

    it("should exclude screenshots when disabled", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "no-screenshot-app",
        description: "App without screenshots",
        templateType: "application",
        includeScreenshots: false,
      });
      const result = await generateReadmeTemplate(input);

      expect(result.content).not.toContain("![visual-app Screenshot]");
    });
  });

  describe("Contributing Section", () => {
    it("should include contributing section when enabled", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "contrib-lib",
        description: "Library with contributing section",
        templateType: "library",
        includeContributing: true,
      });
      const result = await generateReadmeTemplate(input);

      expect(result.content).toContain("## Contributing");
      expect(result.content).toContain("CONTRIBUTING.md");
    });

    it("should exclude contributing section when disabled", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "no-contrib-lib",
        description: "Library without contributing section",
        templateType: "library",
        includeContributing: false,
      });
      const result = await generateReadmeTemplate(input);

      expect(result.content).not.toContain("## Contributing");
    });
  });

  describe("File Output", () => {
    it("should write to file when outputPath is specified", async () => {
      const outputPath = path.join(tempDir, "README.md");

      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "output-lib",
        description: "Library with file output",
        templateType: "library",
        outputPath: outputPath,
      });
      const result = await generateReadmeTemplate(input);

      await expect(fs.access(outputPath)).resolves.toBeUndefined();
      const fileContent = await fs.readFile(outputPath, "utf-8");
      expect(fileContent).toBe(result.content);
      expect(fileContent).toContain("# output-lib");
    });

    it("should not write file when outputPath is not specified", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "no-file-test",
        description: "Library without file output",
        templateType: "library",
      });
      await generateReadmeTemplate(input);

      const possiblePath = path.join(tempDir, "README.md");
      await expect(fs.access(possiblePath)).rejects.toThrow();
    });
  });

  describe("Template Metadata", () => {
    it("should return correct metadata for each template type", () => {
      const templateTypes: TemplateType[] = [
        "library",
        "application",
        "cli-tool",
      ];

      for (const type of templateTypes) {
        const info = generator.getTemplateInfo(type);
        expect(info).toBeDefined();
        expect(info!.type).toBe(type);
        expect(info!.estimatedLength).toBeGreaterThan(0);
      }
    });

    it("should return null for invalid template type", () => {
      const info = generator.getTemplateInfo("invalid" as TemplateType);
      expect(info).toBeNull();
    });

    it("should count sections correctly", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "error-lib",
        description: "Library that causes error",
        templateType: "library",
      });
      const result = await generateReadmeTemplate(input);

      const sectionCount = (result.content.match(/^##\s/gm) || []).length;
      expect(result.metadata.sectionsIncluded).toBeGreaterThanOrEqual(
        sectionCount,
      );
      expect(result.metadata.sectionsIncluded).toBeGreaterThan(3);
    });
  });

  describe("Available Templates", () => {
    it("should return list of available template types", () => {
      const availableTypes = generator.getAvailableTemplates();
      expect(availableTypes).toContain("library");
      expect(availableTypes).toContain("application");
      expect(availableTypes).toContain("cli-tool");
      expect(availableTypes.length).toBeGreaterThan(0);
    });
  });

  describe("Error Handling", () => {
    it("should throw error for unsupported template type", async () => {
      const generator = new ReadmeTemplateGenerator();
      expect(() =>
        generator.generateTemplate({
          projectName: "test",
          description: "test",
          templateType: "unsupported" as TemplateType,
          license: "MIT",
          includeScreenshots: false,
          includeBadges: true,
          includeContributing: true,
        }),
      ).toThrow('Template type "unsupported" not supported');
    });

    it("should handle file write errors gracefully", async () => {
      const invalidPath = "/invalid/nonexistent/path/README.md";

      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "error-test",
        description: "test error handling",
        templateType: "library",
        outputPath: invalidPath,
      });
      await expect(generateReadmeTemplate(input)).rejects.toThrow();
    });
  });

  describe("Variable Replacement", () => {
    it("should replace all template variables correctly", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "license-lib",
        description: "Library with custom license",
        templateType: "library",
        author: "dev",
        license: "Apache-2.0",
      });
      const result = await generateReadmeTemplate(input);

      expect(result.content).not.toContain("{{projectName}}");
      expect(result.content).not.toContain("{{description}}");
      expect(result.content).not.toContain("{{author}}");
      expect(result.content).not.toContain("{{license}}");
      expect(result.content).toContain("license-lib");
      expect(result.content).toContain("Library with custom license");
      expect(result.content).toContain("dev");
      expect(result.content).toContain("Apache-2.0");
    });

    it("should use default values for missing optional fields", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "time-lib",
        description: "Library with timing",
        templateType: "library",
      });
      const result = await generateReadmeTemplate(input);

      expect(result.content).toContain("your-username");
      expect(result.content).toContain("MIT");
    });
  });

  describe("Template Structure Validation", () => {
    it("should generate valid markdown structure", async () => {
      const input = GenerateReadmeTemplateSchema.parse({
        projectName: "structure-test",
        description: "test structure",
        templateType: "library",
      });
      const result = await generateReadmeTemplate(input);

      // Check for proper heading hierarchy
      const lines = result.content.split("\n");
      const headings = lines.filter((line) => line.startsWith("#"));

      expect(headings.length).toBeGreaterThan(0);
      expect(headings[0]).toMatch(/^#\s+/); // Main title

      // Check for code blocks
      expect(result.content).toMatch(/```[\s\S]*?```/);

      // Check for proper spacing
      expect(result.content).not.toMatch(/#{1,6}\s*\n\s*#{1,6}/);
    });

    it("should maintain consistent formatting across templates", async () => {
      const templateTypes: TemplateType[] = [
        "library",
        "application",
        "cli-tool",
      ];

      for (const type of templateTypes) {
        const input = GenerateReadmeTemplateSchema.parse({
          projectName: "format-test",
          description: "test format",
          templateType: type,
        });
        const result = await generateReadmeTemplate(input);

        // All templates should have main title
        expect(result.content).toMatch(/^#\s+format-test/m);

        // All templates should have license section
        expect(result.content).toContain("## License");

        // All templates should end with license info
        expect(result.content.trim()).toMatch(/MIT © your-username$/);
      }
    });
  });
});

```

--------------------------------------------------------------------------------
/tests/memory/user-preferences.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for User Preference Management
 */

import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
import { promises as fs } from "fs";
import { join } from "path";
import { tmpdir } from "os";
import {
  UserPreferenceManager,
  getUserPreferenceManager,
  clearPreferenceManagerCache,
} from "../../src/memory/user-preferences.js";
import {
  getKnowledgeGraph,
  initializeKnowledgeGraph,
} from "../../src/memory/kg-integration.js";

describe("UserPreferenceManager", () => {
  let testDir: string;

  beforeEach(async () => {
    // Create temporary test directory
    testDir = join(tmpdir(), `user-prefs-test-${Date.now()}`);
    await fs.mkdir(testDir, { recursive: true });

    // Initialize KG with test directory
    await initializeKnowledgeGraph(testDir);
    clearPreferenceManagerCache();
  });

  afterEach(async () => {
    clearPreferenceManagerCache();
    // Clean up test directory
    try {
      await fs.rm(testDir, { recursive: true, force: true });
    } catch (error) {
      // Ignore cleanup errors
    }
  });

  describe("Initialization", () => {
    it("should create default preferences for new user", async () => {
      const manager = new UserPreferenceManager("test-user");
      await manager.initialize();

      const prefs = await manager.getPreferences();
      expect(prefs.userId).toBe("test-user");
      expect(prefs.preferredSSGs).toEqual([]);
      expect(prefs.documentationStyle).toBe("comprehensive");
      expect(prefs.expertiseLevel).toBe("intermediate");
      expect(prefs.autoApplyPreferences).toBe(true);
    });

    it("should load existing preferences from knowledge graph", async () => {
      // Create a user with preferences
      const kg = await getKnowledgeGraph();
      kg.addNode({
        id: "user:existing-user",
        type: "user",
        label: "existing-user",
        properties: {
          userId: "existing-user",
          preferredSSGs: ["jekyll", "hugo"],
          documentationStyle: "minimal",
          expertiseLevel: "advanced",
          preferredTechnologies: ["typescript"],
          preferredDiataxisCategories: ["tutorials"],
          autoApplyPreferences: false,
          lastActive: "2025-01-01T00:00:00.000Z",
        },
        weight: 1.0,
      });

      const manager = new UserPreferenceManager("existing-user");
      await manager.initialize();

      const prefs = await manager.getPreferences();
      expect(prefs.userId).toBe("existing-user");
      expect(prefs.preferredSSGs).toEqual(["jekyll", "hugo"]);
      expect(prefs.documentationStyle).toBe("minimal");
      expect(prefs.expertiseLevel).toBe("advanced");
      expect(prefs.autoApplyPreferences).toBe(false);
    });

    it("should handle getPreferences before initialization", async () => {
      const manager = new UserPreferenceManager("auto-init");
      const prefs = await manager.getPreferences();

      expect(prefs.userId).toBe("auto-init");
      expect(prefs.preferredSSGs).toEqual([]);
    });
  });

  describe("Update Preferences", () => {
    it("should update preferences and save to knowledge graph", async () => {
      const manager = new UserPreferenceManager("update-test");
      await manager.initialize();

      await manager.updatePreferences({
        documentationStyle: "tutorial-heavy",
        expertiseLevel: "beginner",
        preferredTechnologies: ["python", "go"],
      });

      const prefs = await manager.getPreferences();
      expect(prefs.documentationStyle).toBe("tutorial-heavy");
      expect(prefs.expertiseLevel).toBe("beginner");
      expect(prefs.preferredTechnologies).toEqual(["python", "go"]);
    });

    it("should initialize before update if not already initialized", async () => {
      const manager = new UserPreferenceManager("lazy-init");

      await manager.updatePreferences({
        expertiseLevel: "advanced",
      });

      const prefs = await manager.getPreferences();
      expect(prefs.expertiseLevel).toBe("advanced");
    });
  });

  describe("Track SSG Usage", () => {
    it("should track successful SSG usage and create preference", async () => {
      const manager = new UserPreferenceManager("ssg-tracker");
      await manager.initialize();

      await manager.trackSSGUsage({
        ssg: "jekyll",
        success: true,
        timestamp: "2025-01-01T00:00:00.000Z",
      });

      const prefs = await manager.getPreferences();
      expect(prefs.preferredSSGs).toContain("jekyll");
    });

    it("should track failed SSG usage", async () => {
      const manager = new UserPreferenceManager("fail-tracker");
      await manager.initialize();

      await manager.trackSSGUsage({
        ssg: "hugo",
        success: false,
        timestamp: "2025-01-01T00:00:00.000Z",
      });

      const kg = await getKnowledgeGraph();
      const edges = await kg.findEdges({
        type: "user_prefers_ssg",
      });

      expect(edges.length).toBeGreaterThan(0);
      const edge = edges.find((e) => e.target.includes("hugo"));
      expect(edge).toBeDefined();
      expect(edge!.weight).toBe(0.5); // Failed usage has lower weight
    });

    it("should update existing SSG preference", async () => {
      const manager = new UserPreferenceManager("update-tracker");
      await manager.initialize();

      // First usage - success
      await manager.trackSSGUsage({
        ssg: "docusaurus",
        success: true,
        timestamp: "2025-01-01T00:00:00.000Z",
      });

      // Second usage - success
      await manager.trackSSGUsage({
        ssg: "docusaurus",
        success: true,
        timestamp: "2025-01-02T00:00:00.000Z",
      });

      const kg = await getKnowledgeGraph();
      const edges = await kg.findEdges({
        type: "user_prefers_ssg",
      });

      const docEdge = edges.find((e) => e.target.includes("docusaurus"));
      expect(docEdge!.properties.usageCount).toBe(2);
      expect(docEdge!.properties.successRate).toBe(1.0);
    });

    it("should calculate average success rate correctly", async () => {
      const manager = new UserPreferenceManager("avg-tracker");
      await manager.initialize();

      // Success
      await manager.trackSSGUsage({
        ssg: "mkdocs",
        success: true,
        timestamp: "2025-01-01T00:00:00.000Z",
      });

      // Failure
      await manager.trackSSGUsage({
        ssg: "mkdocs",
        success: false,
        timestamp: "2025-01-02T00:00:00.000Z",
      });

      const kg = await getKnowledgeGraph();
      const edges = await kg.findEdges({
        type: "user_prefers_ssg",
      });

      const mkdocsEdge = edges.find((e) => e.target.includes("mkdocs"));
      expect(mkdocsEdge!.properties.successRate).toBe(0.5);
    });

    it("should create user node if it doesn't exist during tracking", async () => {
      const manager = new UserPreferenceManager("new-tracker");
      // Don't initialize - let trackSSGUsage create it

      await manager.trackSSGUsage({
        ssg: "eleventy",
        success: true,
        timestamp: "2025-01-01T00:00:00.000Z",
      });

      const kg = await getKnowledgeGraph();
      const userNode = await kg.findNode({
        type: "user",
        properties: { userId: "new-tracker" },
      });

      expect(userNode).toBeDefined();
    });
  });

  describe("SSG Recommendations", () => {
    it("should return recommendations sorted by score", async () => {
      const manager = new UserPreferenceManager("rec-test");
      await manager.initialize();

      // Track multiple SSGs with different success rates
      await manager.trackSSGUsage({
        ssg: "jekyll",
        success: true,
        timestamp: "2025-01-01T00:00:00.000Z",
      });
      await manager.trackSSGUsage({
        ssg: "jekyll",
        success: true,
        timestamp: "2025-01-02T00:00:00.000Z",
      });
      await manager.trackSSGUsage({
        ssg: "hugo",
        success: true,
        timestamp: "2025-01-03T00:00:00.000Z",
      });

      const recommendations = await manager.getSSGRecommendations();

      expect(recommendations.length).toBeGreaterThan(0);
      expect(recommendations[0].ssg).toBe("jekyll"); // Higher usage count
      expect(recommendations[0].score).toBeGreaterThan(
        recommendations[1].score,
      );
    });

    it("should include reason with high success rate", async () => {
      const manager = new UserPreferenceManager("reason-test");
      await manager.initialize();

      await manager.trackSSGUsage({
        ssg: "docusaurus",
        success: true,
        timestamp: "2025-01-01T00:00:00.000Z",
      });

      const recommendations = await manager.getSSGRecommendations();
      const docRec = recommendations.find((r) => r.ssg === "docusaurus");

      expect(docRec!.reason).toContain("100% success rate");
    });

    it("should include reason with low success rate", async () => {
      const manager = new UserPreferenceManager("low-success-test");
      await manager.initialize();

      // Track both success and failure to get a low rate (not exactly 0)
      await manager.trackSSGUsage({
        ssg: "eleventy",
        success: true,
        timestamp: "2025-01-01T00:00:00.000Z",
      });
      await manager.trackSSGUsage({
        ssg: "eleventy",
        success: false,
        timestamp: "2025-01-02T00:00:00.000Z",
      });
      await manager.trackSSGUsage({
        ssg: "eleventy",
        success: false,
        timestamp: "2025-01-03T00:00:00.000Z",
      });

      const recommendations = await manager.getSSGRecommendations();
      const eleventyRec = recommendations.find((r) => r.ssg === "eleventy");

      expect(eleventyRec!.reason).toContain("only");
      expect(eleventyRec!.reason).toContain("success rate");
    });

    it("should return empty array if no user node exists", async () => {
      const manager = new UserPreferenceManager("no-user");
      // Don't initialize or create user node

      const recommendations = await manager.getSSGRecommendations();

      expect(recommendations).toEqual([]);
    });
  });

  describe("Apply Preferences to Recommendation", () => {
    it("should return original recommendation if autoApply is false", async () => {
      const manager = new UserPreferenceManager("no-auto");
      await manager.updatePreferences({
        autoApplyPreferences: false,
        preferredSSGs: ["jekyll"],
      });

      const result = manager.applyPreferencesToRecommendation("hugo", [
        "jekyll",
        "hugo",
      ]);

      expect(result.recommended).toBe("hugo");
      expect(result.adjustmentReason).toBeUndefined();
    });

    it("should keep recommendation if it matches preferred SSG", async () => {
      const manager = new UserPreferenceManager("match-pref");
      await manager.updatePreferences({
        preferredSSGs: ["jekyll", "hugo"],
      });

      const result = manager.applyPreferencesToRecommendation("jekyll", [
        "jekyll",
        "hugo",
        "mkdocs",
      ]);

      expect(result.recommended).toBe("jekyll");
      expect(result.adjustmentReason).toContain("Matches your preferred SSG");
    });

    it("should switch to preferred SSG if in alternatives", async () => {
      const manager = new UserPreferenceManager("switch-pref");
      await manager.updatePreferences({
        preferredSSGs: ["docusaurus"],
      });

      const result = manager.applyPreferencesToRecommendation("jekyll", [
        "jekyll",
        "docusaurus",
        "hugo",
      ]);

      expect(result.recommended).toBe("docusaurus");
      expect(result.adjustmentReason).toContain(
        "Switched to docusaurus based on your usage history",
      );
    });

    it("should return original if no preferred SSGs match", async () => {
      const manager = new UserPreferenceManager("no-match");
      await manager.updatePreferences({
        preferredSSGs: ["eleventy"],
      });

      const result = manager.applyPreferencesToRecommendation("jekyll", [
        "jekyll",
        "hugo",
      ]);

      expect(result.recommended).toBe("jekyll");
      expect(result.adjustmentReason).toBeUndefined();
    });

    it("should return original if no preferences set", async () => {
      const manager = new UserPreferenceManager("empty-pref");
      await manager.initialize();

      const result = manager.applyPreferencesToRecommendation("jekyll", [
        "jekyll",
        "hugo",
      ]);

      expect(result.recommended).toBe("jekyll");
      expect(result.adjustmentReason).toBeUndefined();
    });
  });

  describe("Reset Preferences", () => {
    it("should reset preferences to defaults", async () => {
      const manager = new UserPreferenceManager("reset-test");
      await manager.updatePreferences({
        documentationStyle: "minimal",
        expertiseLevel: "advanced",
        preferredSSGs: ["jekyll", "hugo"],
      });

      await manager.resetPreferences();

      const prefs = await manager.getPreferences();
      expect(prefs.documentationStyle).toBe("comprehensive");
      expect(prefs.expertiseLevel).toBe("intermediate");
      expect(prefs.preferredSSGs).toEqual([]);
    });
  });

  describe("Export/Import Preferences", () => {
    it("should export preferences as JSON", async () => {
      const manager = new UserPreferenceManager("export-test");
      await manager.updatePreferences({
        expertiseLevel: "advanced",
        preferredSSGs: ["jekyll"],
      });

      const exported = await manager.exportPreferences();
      const parsed = JSON.parse(exported);

      expect(parsed.userId).toBe("export-test");
      expect(parsed.expertiseLevel).toBe("advanced");
      expect(parsed.preferredSSGs).toEqual(["jekyll"]);
    });

    it("should import preferences from JSON", async () => {
      const manager = new UserPreferenceManager("import-test");
      await manager.initialize();

      const importData = {
        userId: "import-test",
        preferredSSGs: ["hugo", "docusaurus"],
        documentationStyle: "tutorial-heavy" as const,
        expertiseLevel: "beginner" as const,
        preferredTechnologies: ["python"],
        preferredDiataxisCategories: ["tutorials" as const],
        autoApplyPreferences: false,
        lastUpdated: "2025-01-01T00:00:00.000Z",
      };

      await manager.importPreferences(JSON.stringify(importData));

      const prefs = await manager.getPreferences();
      expect(prefs.expertiseLevel).toBe("beginner");
      expect(prefs.preferredSSGs).toEqual(["hugo", "docusaurus"]);
      expect(prefs.autoApplyPreferences).toBe(false);
    });

    it("should throw error on userId mismatch during import", async () => {
      const manager = new UserPreferenceManager("user1");
      await manager.initialize();

      const importData = {
        userId: "user2", // Different user ID
        preferredSSGs: [],
        documentationStyle: "comprehensive" as const,
        expertiseLevel: "intermediate" as const,
        preferredTechnologies: [],
        preferredDiataxisCategories: [],
        autoApplyPreferences: true,
        lastUpdated: "2025-01-01T00:00:00.000Z",
      };

      await expect(
        manager.importPreferences(JSON.stringify(importData)),
      ).rejects.toThrow("User ID mismatch");
    });
  });

  describe("Manager Cache", () => {
    it("should cache preference managers", async () => {
      const manager1 = await getUserPreferenceManager("cached-user");
      const manager2 = await getUserPreferenceManager("cached-user");

      expect(manager1).toBe(manager2); // Same instance
    });

    it("should create different managers for different users", async () => {
      const manager1 = await getUserPreferenceManager("user1");
      const manager2 = await getUserPreferenceManager("user2");

      expect(manager1).not.toBe(manager2);
    });

    it("should clear cache", async () => {
      const manager1 = await getUserPreferenceManager("clear-test");
      clearPreferenceManagerCache();
      const manager2 = await getUserPreferenceManager("clear-test");

      expect(manager1).not.toBe(manager2); // Different instances after clear
    });
  });
});

```

--------------------------------------------------------------------------------
/src/tools/optimize-readme.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { promises as fs } from "fs";
import path from "path";
import { MCPToolResponse } from "../types/api.js";

// Input validation schema
const OptimizeReadmeInputSchema = z.object({
  readme_path: z.string().min(1, "README path is required"),
  strategy: z
    .enum([
      "community_focused",
      "enterprise_focused",
      "developer_focused",
      "general",
    ])
    .optional()
    .default("community_focused"),
  max_length: z.number().min(50).max(1000).optional().default(300),
  include_tldr: z.boolean().optional().default(true),
  preserve_existing: z.boolean().optional().default(false),
  output_path: z.string().optional(),
  create_docs_directory: z.boolean().optional().default(true),
});

export type OptimizeReadmeInput = z.infer<typeof OptimizeReadmeInputSchema>;

interface OptimizationResult {
  originalLength: number;
  optimizedLength: number;
  reductionPercentage: number;
  optimizedContent: string;
  extractedSections: ExtractedSection[];
  tldrGenerated: string | null;
  restructuringChanges: RestructuringChange[];
  recommendations: string[];
}

interface ExtractedSection {
  title: string;
  content: string;
  suggestedLocation: string;
  reason: string;
}

interface RestructuringChange {
  type: "moved" | "condensed" | "split" | "added" | "removed";
  section: string;
  description: string;
  impact: string;
}

/**
 * Optimizes README content by restructuring, condensing, and extracting detailed sections.
 *
 * Performs intelligent README optimization including length reduction, structure improvement,
 * content extraction to separate documentation, and TL;DR generation. Uses different strategies
 * based on target audience (community, enterprise, developer, general) to maximize effectiveness.
 *
 * @param input - The input parameters for README optimization
 * @param input.readme_path - The file system path to the README file to optimize
 * @param input.strategy - The optimization strategy to apply (default: "community_focused")
 * @param input.max_length - Target maximum length in lines (default: 300)
 * @param input.include_tldr - Whether to generate a TL;DR section (default: true)
 * @param input.preserve_existing - Whether to preserve existing content structure (default: false)
 * @param input.output_path - Optional output path for optimized README
 * @param input.create_docs_directory - Whether to create docs/ directory for extracted content (default: true)
 *
 * @returns Promise resolving to README optimization results
 * @returns optimization - Complete optimization results including length reduction and restructuring
 * @returns nextSteps - Array of recommended next actions after optimization
 *
 * @throws {Error} When README file is inaccessible or invalid
 * @throws {Error} When optimization processing fails
 * @throws {Error} When output directory cannot be created
 *
 * @example
 * ```typescript
 * // Optimize README for community contributors
 * const result = await optimizeReadme({
 *   readme_path: "./README.md",
 *   strategy: "community_focused",
 *   max_length: 300,
 *   include_tldr: true
 * });
 *
 * console.log(`Reduced from ${result.data.optimization.originalLength} to ${result.data.optimization.optimizedLength} lines`);
 * console.log(`Reduction: ${result.data.optimization.reductionPercentage}%`);
 *
 * // Optimize for enterprise with aggressive reduction
 * const enterprise = await optimizeReadme({
 *   readme_path: "./README.md",
 *   strategy: "enterprise_focused",
 *   max_length: 200,
 *   preserve_existing: true
 * });
 * ```
 *
 * @since 1.0.0
 */
export async function optimizeReadme(
  input: Partial<OptimizeReadmeInput>,
): Promise<
  MCPToolResponse<{ optimization: OptimizationResult; nextSteps: string[] }>
> {
  const startTime = Date.now();

  try {
    // Validate input
    const validatedInput = OptimizeReadmeInputSchema.parse(input);
    const {
      readme_path,
      strategy,
      max_length,
      include_tldr,
      output_path,
      create_docs_directory,
    } = validatedInput;

    // Read original README
    const originalContent = await fs.readFile(readme_path, "utf-8");
    const originalLength = originalContent.split("\n").length;

    // Parse README structure
    const sections = parseReadmeStructure(originalContent);

    // Generate TL;DR if requested
    const tldrGenerated = include_tldr
      ? generateTldr(originalContent, sections)
      : null;

    // Identify sections to extract
    const extractedSections = identifySectionsToExtract(
      sections,
      strategy,
      max_length,
    );

    // Create basic optimization result
    const optimizedContent =
      originalContent +
      "\n\n## TL;DR\n\n" +
      (tldrGenerated || "Quick overview of the project.");
    const restructuringChanges = [
      {
        type: "added" as const,
        section: "TL;DR",
        description: "Added concise project overview",
        impact: "Helps users quickly understand project value",
      },
    ];

    const optimizedLength = optimizedContent.split("\n").length;
    const reductionPercentage = Math.round(
      ((originalLength - optimizedLength) / originalLength) * 100,
    );

    // Create docs directory and extract detailed content if requested
    if (create_docs_directory && extractedSections.length > 0) {
      await createDocsStructure(path.dirname(readme_path), extractedSections);
    }

    // Write optimized README if output path specified
    if (output_path) {
      await fs.writeFile(output_path, optimizedContent, "utf-8");
    }

    const recommendations = generateOptimizationRecommendations(
      originalLength,
      optimizedLength,
      extractedSections,
      strategy,
    );

    const optimization: OptimizationResult = {
      originalLength,
      optimizedLength,
      reductionPercentage,
      optimizedContent,
      extractedSections,
      tldrGenerated,
      restructuringChanges,
      recommendations,
    };

    const nextSteps = generateOptimizationNextSteps(
      optimization,
      validatedInput,
    );

    return {
      success: true,
      data: {
        optimization,
        nextSteps,
      },
      metadata: {
        toolVersion: "1.0.0",
        executionTime: Date.now() - startTime,
        timestamp: new Date().toISOString(),
      },
    };
  } catch (error) {
    return {
      success: false,
      error: {
        code: "OPTIMIZATION_FAILED",
        message: "Failed to optimize README",
        details: error instanceof Error ? error.message : "Unknown error",
        resolution: "Check README file path and permissions",
      },
      metadata: {
        toolVersion: "1.0.0",
        executionTime: Date.now() - startTime,
        timestamp: new Date().toISOString(),
      },
    };
  }
}

interface ReadmeSection {
  title: string;
  content: string;
  level: number;
  startLine: number;
  endLine: number;
  wordCount: number;
  isEssential: boolean;
}

function parseReadmeStructure(content: string): ReadmeSection[] {
  const lines = content.split("\n");
  const sections: ReadmeSection[] = [];
  let currentTitle = "";
  let currentLevel = 0;
  let currentStartLine = 0;

  lines.forEach((line, index) => {
    const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);

    if (headingMatch) {
      // Save previous section
      if (currentTitle) {
        const endLine = index - 1;
        const sectionContent = lines
          .slice(currentStartLine, endLine + 1)
          .join("\n");
        const wordCount = sectionContent.split(/\s+/).length;
        const isEssential = isEssentialSection(currentTitle);

        sections.push({
          title: currentTitle,
          content: sectionContent,
          level: currentLevel,
          startLine: currentStartLine,
          endLine: endLine,
          wordCount: wordCount,
          isEssential: isEssential,
        });
      }

      // Start new section
      currentTitle = headingMatch[2].trim();
      currentLevel = headingMatch[1].length;
      currentStartLine = index;
    }
  });

  // Add final section
  if (currentTitle) {
    const endLine = lines.length - 1;
    const sectionContent = lines
      .slice(currentStartLine, endLine + 1)
      .join("\n");
    const wordCount = sectionContent.split(/\s+/).length;
    const isEssential = isEssentialSection(currentTitle);

    sections.push({
      title: currentTitle,
      content: sectionContent,
      level: currentLevel,
      startLine: currentStartLine,
      endLine: endLine,
      wordCount: wordCount,
      isEssential: isEssential,
    });
  }

  return sections;
}

function isEssentialSection(title: string): boolean {
  const essentialKeywords = [
    "installation",
    "install",
    "setup",
    "getting started",
    "quick start",
    "usage",
    "example",
    "api",
    "license",
    "contributing",
  ];

  return essentialKeywords.some((keyword) =>
    title.toLowerCase().includes(keyword),
  );
}

function generateTldr(content: string, sections: ReadmeSection[]): string {
  // Extract project name from first heading
  const projectNameMatch = content.match(/^#\s+(.+)$/m);
  const projectName = projectNameMatch ? projectNameMatch[1] : "This project";

  // Extract description (usually after title or in blockquote)
  const descriptionMatch = content.match(/>\s*(.+)/);
  let description = descriptionMatch ? descriptionMatch[1] : "";

  // If no description found, try to extract from first paragraph
  if (!description) {
    const firstParagraphMatch = content.match(/^[^#\n].{20,200}/m);
    description = firstParagraphMatch
      ? firstParagraphMatch[0].substring(0, 100) + "..."
      : "";
  }

  // Identify key features or use cases
  const features: string[] = [];
  sections.forEach((section) => {
    if (
      section.title.toLowerCase().includes("feature") ||
      section.title.toLowerCase().includes("what") ||
      section.title.toLowerCase().includes("why")
    ) {
      const bullets = section.content.match(/^\s*[-*+]\s+(.+)$/gm);
      if (bullets && bullets.length > 0) {
        features.push(
          ...bullets
            .slice(0, 3)
            .map((b) => b.replace(/^\s*[-*+]\s+/, "").trim()),
        );
      }
    }
  });

  let tldr = `## TL;DR\n\n${projectName} ${description}\n\n`;

  if (features.length > 0) {
    tldr += `**Key features:**\n`;
    features.slice(0, 3).forEach((feature) => {
      tldr += `- ${feature}\n`;
    });
    tldr += "\n";
  }

  // Add quick start reference
  const hasInstallSection = sections.some(
    (s) =>
      s.title.toLowerCase().includes("install") ||
      s.title.toLowerCase().includes("setup"),
  );

  if (hasInstallSection) {
    tldr += `**Quick start:** See [Installation](#installation) → [Usage](#usage)\n\n`;
  }

  return tldr;
}

function identifySectionsToExtract(
  sections: ReadmeSection[],
  strategy: string,
  maxLength: number,
): ExtractedSection[] {
  const extractedSections: ExtractedSection[] = [];
  const currentLength = sections.reduce(
    (sum, s) => sum + s.content.split("\n").length,
    0,
  );

  if (currentLength <= maxLength) {
    return extractedSections; // No extraction needed
  }

  // Define extraction rules based on strategy
  const extractionRules = getExtractionRules(strategy);

  sections.forEach((section) => {
    for (const rule of extractionRules) {
      if (rule.matcher(section) && !section.isEssential) {
        extractedSections.push({
          title: section.title,
          content: section.content,
          suggestedLocation: rule.suggestedLocation,
          reason: rule.reason,
        });
        break;
      }
    }
  });

  return extractedSections;
}

function getExtractionRules(strategy: string) {
  const baseRules = [
    {
      matcher: (section: ReadmeSection) => section.wordCount > 200,
      suggestedLocation: "docs/detailed-guide.md",
      reason: "Section too long for main README",
    },
    {
      matcher: (section: ReadmeSection) =>
        /troubleshoot|faq|common issues|problems/i.test(section.title),
      suggestedLocation: "docs/troubleshooting.md",
      reason: "Troubleshooting content better suited for separate document",
    },
    {
      matcher: (section: ReadmeSection) =>
        /advanced|configuration|config/i.test(section.title),
      suggestedLocation: "docs/configuration.md",
      reason: "Advanced configuration details",
    },
    {
      matcher: (section: ReadmeSection) =>
        /development|developer|build|compile/i.test(section.title),
      suggestedLocation: "docs/development.md",
      reason: "Development-specific information",
    },
  ];

  if (strategy === "community_focused") {
    baseRules.push({
      matcher: (section: ReadmeSection) =>
        /architecture|design|technical/i.test(section.title),
      suggestedLocation: "docs/technical.md",
      reason: "Technical details can overwhelm community contributors",
    });
  }

  return baseRules;
}

async function createDocsStructure(
  projectDir: string,
  extractedSections: ExtractedSection[],
): Promise<void> {
  const docsDir = path.join(projectDir, "docs");

  try {
    await fs.mkdir(docsDir, { recursive: true });
  } catch {
    // Directory might already exist
  }

  // Create extracted documentation files
  for (const section of extractedSections) {
    const filePath = path.join(projectDir, section.suggestedLocation);
    const fileDir = path.dirname(filePath);

    try {
      await fs.mkdir(fileDir, { recursive: true });
      await fs.writeFile(filePath, section.content, "utf-8");
    } catch (error) {
      console.warn(`Failed to create ${filePath}:`, error);
    }
  }

  // Create docs index
  const indexContent = generateDocsIndex(extractedSections);
  await fs.writeFile(path.join(docsDir, "README.md"), indexContent, "utf-8");
}

function generateDocsIndex(extractedSections: ExtractedSection[]): string {
  let content = "# Documentation\n\n";
  content +=
    "This directory contains detailed documentation extracted from the main README for better organization.\n\n";

  content += "## Available Documentation\n\n";
  extractedSections.forEach((section) => {
    const filename = path.basename(section.suggestedLocation);
    content += `- [${section.title}](${filename}) - ${section.reason}\n`;
  });

  return content;
}

function generateOptimizationRecommendations(
  originalLength: number,
  optimizedLength: number,
  extractedSections: ExtractedSection[],
  strategy: string,
): string[] {
  const recommendations: string[] = [];
  const reduction = originalLength - optimizedLength;

  if (reduction > 0) {
    recommendations.push(
      `✅ Reduced README length by ${reduction} lines (${Math.round(
        (reduction / originalLength) * 100,
      )}%)`,
    );
  }

  if (extractedSections.length > 0) {
    recommendations.push(
      `📁 Moved ${extractedSections.length} detailed sections to docs/ directory`,
    );
  }

  if (strategy === "community_focused") {
    recommendations.push(
      "👥 Optimized for community contributors - prioritized quick start and contribution info",
    );
  }

  recommendations.push(
    "🔗 Added links to detailed documentation for users who need more information",
  );
  recommendations.push(
    "📊 Consider adding a table of contents for sections with 5+ headings",
  );

  return recommendations;
}

function generateOptimizationNextSteps(
  optimization: OptimizationResult,
  input: OptimizeReadmeInput,
): string[] {
  const steps: string[] = [];

  if (!input.output_path) {
    steps.push("💾 Review optimized content and save to README.md when ready");
  }

  if (optimization.extractedSections.length > 0) {
    steps.push("📝 Review extracted documentation files in docs/ directory");
    steps.push("🔗 Update any internal links that may have been affected");
  }

  if (optimization.reductionPercentage > 30) {
    steps.push(
      "👀 Have team members review the condensed content for accuracy",
    );
  }

  steps.push("📈 Run analyze_readme again to verify improvements");
  steps.push("🎯 Consider setting up automated README length monitoring");

  return steps;
}

```
Page 8/23FirstPrevNextLast