#
tokens: 49500/50000 6/274 files (page 18/29)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 18 of 29. Use http://codebase.md/tosin2013/documcp?lines=true&page={x} to view the full context.

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/src/memory/pruning.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Memory Pruning & Optimization System for DocuMCP
  3 |  * Intelligent memory cleanup, storage optimization, and performance tuning
  4 |  */
  5 | 
  6 | import { EventEmitter } from "events";
  7 | import { MemoryEntry, JSONLStorage } from "./storage.js";
  8 | import { MemoryManager } from "./manager.js";
  9 | import { IncrementalLearningSystem } from "./learning.js";
 10 | import { KnowledgeGraph } from "./knowledge-graph.js";
 11 | 
 12 | export interface PruningPolicy {
 13 |   maxAge: number; // Maximum age in days
 14 |   maxSize: number; // Maximum storage size in MB
 15 |   maxEntries: number; // Maximum number of entries
 16 |   preservePatterns: string[]; // Pattern types to preserve
 17 |   compressionThreshold: number; // Compress entries older than X days
 18 |   redundancyThreshold: number; // Remove similar entries with similarity > X
 19 | }
 20 | 
 21 | export interface OptimizationMetrics {
 22 |   totalEntries: number;
 23 |   storageSize: number;
 24 |   indexSize: number;
 25 |   compressionRatio: number;
 26 |   duplicatesRemoved: number;
 27 |   entriesPruned: number;
 28 |   performanceGain: number;
 29 |   lastOptimization: Date;
 30 | }
 31 | 
 32 | export interface PruningResult {
 33 |   entriesRemoved: number;
 34 |   spaceSaved: number;
 35 |   patternsPreserved: number;
 36 |   compressionApplied: number;
 37 |   optimizationApplied: boolean;
 38 |   metrics: OptimizationMetrics;
 39 | }
 40 | 
 41 | export interface CompressionStrategy {
 42 |   type: "gzip" | "lz4" | "semantic";
 43 |   threshold: number;
 44 |   ratio: number;
 45 | }
 46 | 
 47 | export interface RedundancyPattern {
 48 |   similarity: number;
 49 |   count: number;
 50 |   representative: string;
 51 |   duplicates: string[];
 52 |   canMerge: boolean;
 53 | }
 54 | 
 55 | export class MemoryPruningSystem extends EventEmitter {
 56 |   private storage: JSONLStorage;
 57 |   private manager: MemoryManager;
 58 |   private learningSystem: IncrementalLearningSystem;
 59 |   private knowledgeGraph: KnowledgeGraph;
 60 |   private defaultPolicy: PruningPolicy;
 61 |   private compressionCache: Map<string, any>;
 62 |   private similarityCache: Map<string, Map<string, number>>;
 63 | 
 64 |   constructor(
 65 |     storage: JSONLStorage,
 66 |     manager: MemoryManager,
 67 |     learningSystem: IncrementalLearningSystem,
 68 |     knowledgeGraph: KnowledgeGraph,
 69 |   ) {
 70 |     super();
 71 |     this.storage = storage;
 72 |     this.manager = manager;
 73 |     this.learningSystem = learningSystem;
 74 |     this.knowledgeGraph = knowledgeGraph;
 75 |     this.compressionCache = new Map();
 76 |     this.similarityCache = new Map();
 77 | 
 78 |     this.defaultPolicy = {
 79 |       maxAge: 180, // 6 months
 80 |       maxSize: 500, // 500MB
 81 |       maxEntries: 50000,
 82 |       preservePatterns: [
 83 |         "successful_deployment",
 84 |         "user_preference",
 85 |         "critical_error",
 86 |       ],
 87 |       compressionThreshold: 30, // Compress after 30 days
 88 |       redundancyThreshold: 0.85, // 85% similarity threshold
 89 |     };
 90 | 
 91 |     this.setupPeriodicCleanup();
 92 |   }
 93 | 
 94 |   /**
 95 |    * Execute comprehensive memory pruning
 96 |    */
 97 |   async prune(policy?: Partial<PruningPolicy>): Promise<PruningResult> {
 98 |     const activePolicy = { ...this.defaultPolicy, ...policy };
 99 |     const startTime = Date.now();
100 | 
101 |     this.emit("pruning_started", { policy: activePolicy });
102 | 
103 |     try {
104 |       // Get current metrics
105 |       const initialMetrics = await this.getOptimizationMetrics();
106 | 
107 |       // Phase 1: Remove aged entries
108 |       const agedResult = await this.removeAgedEntries(activePolicy);
109 | 
110 |       // Phase 2: Apply size-based pruning
111 |       const sizeResult = await this.applySizePruning(activePolicy);
112 | 
113 |       // Phase 3: Remove redundant entries
114 |       const redundancyResult = await this.removeRedundantEntries(activePolicy);
115 | 
116 |       // Phase 4: Apply compression
117 |       const compressionResult = await this.applyCompression(activePolicy);
118 | 
119 |       // Phase 5: Optimize storage structure
120 |       const optimizationResult = await this.optimizeStorage();
121 | 
122 |       // Get final metrics
123 |       const finalMetrics = await this.getOptimizationMetrics();
124 | 
125 |       const result: PruningResult = {
126 |         entriesRemoved:
127 |           agedResult.removed + sizeResult.removed + redundancyResult.removed,
128 |         spaceSaved: initialMetrics.storageSize - finalMetrics.storageSize,
129 |         patternsPreserved: agedResult.preserved + sizeResult.preserved,
130 |         compressionApplied: compressionResult.compressed,
131 |         optimizationApplied: optimizationResult.applied,
132 |         metrics: finalMetrics,
133 |       };
134 | 
135 |       // Update learning system with pruning results
136 |       await this.updateLearningFromPruning(result);
137 | 
138 |       this.emit("pruning_completed", {
139 |         result,
140 |         duration: Date.now() - startTime,
141 |       });
142 | 
143 |       return result;
144 |     } catch (error) {
145 |       this.emit("pruning_error", {
146 |         error: error instanceof Error ? error.message : String(error),
147 |       });
148 |       throw error;
149 |     }
150 |   }
151 | 
152 |   /**
153 |    * Remove entries older than policy threshold
154 |    */
155 |   private async removeAgedEntries(
156 |     policy: PruningPolicy,
157 |   ): Promise<{ removed: number; preserved: number }> {
158 |     const cutoffDate = new Date(
159 |       Date.now() - policy.maxAge * 24 * 60 * 60 * 1000,
160 |     );
161 |     const allEntries = await this.storage.getAll();
162 | 
163 |     let removed = 0;
164 |     let preserved = 0;
165 | 
166 |     for (const entry of allEntries) {
167 |       const entryDate = new Date(entry.timestamp);
168 | 
169 |       if (entryDate < cutoffDate) {
170 |         // Check if entry should be preserved
171 |         if (this.shouldPreserveEntry(entry, policy)) {
172 |           preserved++;
173 |           continue;
174 |         }
175 | 
176 |         // Remove from storage
177 |         await this.storage.delete(entry.id);
178 | 
179 |         // Remove from knowledge graph
180 |         await this.knowledgeGraph.removeNode(entry.id);
181 | 
182 |         removed++;
183 |       }
184 |     }
185 | 
186 |     return { removed, preserved };
187 |   }
188 | 
189 |   /**
190 |    * Apply size-based pruning to stay within limits
191 |    */
192 |   private async applySizePruning(
193 |     policy: PruningPolicy,
194 |   ): Promise<{ removed: number; preserved: number }> {
195 |     const metrics = await this.getOptimizationMetrics();
196 | 
197 |     if (
198 |       metrics.storageSize <= policy.maxSize &&
199 |       metrics.totalEntries <= policy.maxEntries
200 |     ) {
201 |       return { removed: 0, preserved: 0 };
202 |     }
203 | 
204 |     // Get entries sorted by importance score
205 |     const allEntries = await this.storage.getAll();
206 |     const scoredEntries = await Promise.all(
207 |       allEntries.map(async (entry) => ({
208 |         entry,
209 |         score: await this.calculateImportanceScore(entry),
210 |       })),
211 |     );
212 | 
213 |     // Sort by score (ascending - remove least important first)
214 |     scoredEntries.sort((a, b) => a.score - b.score);
215 | 
216 |     let removed = 0;
217 |     let preserved = 0;
218 |     let currentSize = metrics.storageSize;
219 |     let currentEntries = metrics.totalEntries;
220 | 
221 |     for (const { entry, score } of scoredEntries) {
222 |       if (
223 |         currentSize <= policy.maxSize &&
224 |         currentEntries <= policy.maxEntries
225 |       ) {
226 |         break;
227 |       }
228 | 
229 |       if (this.shouldPreserveEntry(entry, policy) || score > 0.8) {
230 |         preserved++;
231 |         continue;
232 |       }
233 | 
234 |       // Remove entry
235 |       await this.storage.delete(entry.id);
236 |       await this.knowledgeGraph.removeNode(entry.id);
237 | 
238 |       // Estimate size reduction (rough approximation)
239 |       const entrySize = JSON.stringify(entry).length / (1024 * 1024);
240 |       currentSize -= entrySize;
241 |       currentEntries--;
242 |       removed++;
243 |     }
244 | 
245 |     return { removed, preserved };
246 |   }
247 | 
248 |   /**
249 |    * Remove redundant and duplicate entries
250 |    */
251 |   private async removeRedundantEntries(
252 |     policy: PruningPolicy,
253 |   ): Promise<{ removed: number; merged: number }> {
254 |     const redundantPatterns = await this.findRedundantPatterns(
255 |       policy.redundancyThreshold,
256 |     );
257 | 
258 |     let removed = 0;
259 |     let merged = 0;
260 | 
261 |     for (const pattern of redundantPatterns) {
262 |       if (pattern.canMerge && pattern.duplicates.length > 1) {
263 |         // Keep the representative, remove duplicates
264 |         for (let i = 1; i < pattern.duplicates.length; i++) {
265 |           await this.storage.delete(pattern.duplicates[i]);
266 |           removed++;
267 |         }
268 | 
269 |         // Optionally merge information into representative
270 |         if (pattern.count > 2) {
271 |           await this.mergeRedundantEntries(
272 |             pattern.representative,
273 |             pattern.duplicates.slice(1),
274 |           );
275 |           merged++;
276 |         }
277 |       }
278 |     }
279 | 
280 |     return { removed, merged };
281 |   }
282 | 
283 |   /**
284 |    * Apply compression to old entries
285 |    */
286 |   private async applyCompression(
287 |     policy: PruningPolicy,
288 |   ): Promise<{ compressed: number; spaceSaved: number }> {
289 |     const cutoffDate = new Date(
290 |       Date.now() - policy.compressionThreshold * 24 * 60 * 60 * 1000,
291 |     );
292 |     const allEntries = await this.storage.getAll();
293 | 
294 |     let compressed = 0;
295 |     let spaceSaved = 0;
296 | 
297 |     for (const entry of allEntries) {
298 |       const entryDate = new Date(entry.timestamp);
299 | 
300 |       if (entryDate < cutoffDate && !this.isCompressed(entry)) {
301 |         const originalSize = JSON.stringify(entry).length;
302 |         const compressedEntry = await this.compressEntry(entry);
303 |         const compressedSize = JSON.stringify(compressedEntry).length;
304 | 
305 |         await this.storage.update(entry.id, compressedEntry);
306 | 
307 |         compressed++;
308 |         spaceSaved += originalSize - compressedSize;
309 |       }
310 |     }
311 | 
312 |     return { compressed, spaceSaved };
313 |   }
314 | 
315 |   /**
316 |    * Optimize storage structure and indices
317 |    */
318 |   private async optimizeStorage(): Promise<{
319 |     applied: boolean;
320 |     improvements: string[];
321 |   }> {
322 |     const improvements: string[] = [];
323 | 
324 |     try {
325 |       // Rebuild indices
326 |       await this.storage.rebuildIndex();
327 |       improvements.push("rebuilt_indices");
328 | 
329 |       // Defragment storage files
330 |       await this.defragmentStorage();
331 |       improvements.push("defragmented_storage");
332 | 
333 |       // Optimize cache sizes
334 |       this.optimizeCaches();
335 |       improvements.push("optimized_caches");
336 | 
337 |       return { applied: true, improvements };
338 |     } catch (error) {
339 |       return { applied: false, improvements };
340 |     }
341 |   }
342 | 
343 |   /**
344 |    * Calculate importance score for an entry
345 |    */
346 |   private async calculateImportanceScore(entry: MemoryEntry): Promise<number> {
347 |     let score = 0;
348 | 
349 |     // Recency score (0-0.3)
350 |     const age = Date.now() - new Date(entry.timestamp).getTime();
351 |     const maxAge = 180 * 24 * 60 * 60 * 1000; // 180 days
352 |     score += Math.max(0, 1 - age / maxAge) * 0.3;
353 | 
354 |     // Type importance (0-0.2)
355 |     const typeScores: Record<string, number> = {
356 |       successful_deployment: 0.2,
357 |       user_preference: 0.18,
358 |       configuration: 0.15,
359 |       analysis: 0.12,
360 |       recommendation: 0.12,
361 |       interaction: 0.08,
362 |       error: 0.05,
363 |     };
364 |     score += typeScores[entry.type] || 0.05;
365 | 
366 |     // Learning value (0-0.2)
367 |     const patterns = await this.learningSystem.getPatterns();
368 |     const relevantPatterns = patterns.filter(
369 |       (p) =>
370 |         p.metadata.technologies?.includes(entry.data.language) ||
371 |         p.metadata.technologies?.includes(entry.data.framework),
372 |     );
373 |     score += Math.min(0.2, relevantPatterns.length * 0.05);
374 | 
375 |     // Knowledge graph centrality (0-0.15)
376 |     try {
377 |       const connections = await this.knowledgeGraph.getConnections(entry.id);
378 |       score += Math.min(0.15, connections.length * 0.02);
379 |     } catch {
380 |       // Node might not exist in graph
381 |     }
382 | 
383 |     // Success indicator (0-0.15)
384 |     if (entry.data.outcome === "success" || entry.data.success === true) {
385 |       score += 0.15;
386 |     }
387 | 
388 |     return Math.min(1, score);
389 |   }
390 | 
391 |   /**
392 |    * Check if entry should be preserved based on policy
393 |    */
394 |   private shouldPreserveEntry(
395 |     entry: MemoryEntry,
396 |     policy: PruningPolicy,
397 |   ): boolean {
398 |     // Check preserve patterns
399 |     for (const pattern of policy.preservePatterns) {
400 |       if (
401 |         entry.type.includes(pattern) ||
402 |         JSON.stringify(entry.data).includes(pattern)
403 |       ) {
404 |         return true;
405 |       }
406 |     }
407 | 
408 |     // Preserve high-value entries
409 |     if (
410 |       entry.data.outcome === "success" ||
411 |       entry.data.success === true ||
412 |       entry.data.critical === true
413 |     ) {
414 |       return true;
415 |     }
416 | 
417 |     return false;
418 |   }
419 | 
420 |   /**
421 |    * Find patterns of redundant entries
422 |    */
423 |   private async findRedundantPatterns(
424 |     threshold: number,
425 |   ): Promise<RedundancyPattern[]> {
426 |     const allEntries = await this.storage.getAll();
427 |     const patterns: RedundancyPattern[] = [];
428 |     const processed = new Set<string>();
429 | 
430 |     for (const entry of allEntries) {
431 |       if (processed.has(entry.id)) continue;
432 | 
433 |       const similar = await this.findSimilarEntries(
434 |         entry,
435 |         allEntries,
436 |         threshold,
437 |       );
438 | 
439 |       if (similar.length > 1) {
440 |         patterns.push({
441 |           similarity: threshold,
442 |           count: similar.length,
443 |           representative: similar[0].id,
444 |           duplicates: similar.map((e) => e.id),
445 |           canMerge: this.canMergeEntries(similar),
446 |         });
447 | 
448 |         similar.forEach((s) => processed.add(s.id));
449 |       }
450 |     }
451 | 
452 |     return patterns;
453 |   }
454 | 
455 |   /**
456 |    * Find entries similar to given entry
457 |    */
458 |   private async findSimilarEntries(
459 |     target: MemoryEntry,
460 |     entries: MemoryEntry[],
461 |     threshold: number,
462 |   ): Promise<MemoryEntry[]> {
463 |     const similar: MemoryEntry[] = [target];
464 | 
465 |     for (const entry of entries) {
466 |       if (entry.id === target.id) continue;
467 | 
468 |       const similarity = await this.calculateSimilarity(target, entry);
469 |       if (similarity >= threshold) {
470 |         similar.push(entry);
471 |       }
472 |     }
473 | 
474 |     return similar;
475 |   }
476 | 
477 |   /**
478 |    * Calculate similarity between two entries
479 |    */
480 |   private async calculateSimilarity(
481 |     entry1: MemoryEntry,
482 |     entry2: MemoryEntry,
483 |   ): Promise<number> {
484 |     // Check cache first
485 |     if (
486 |       this.similarityCache.has(entry1.id) &&
487 |       this.similarityCache.get(entry1.id)?.has(entry2.id)
488 |     ) {
489 |       return this.similarityCache.get(entry1.id)!.get(entry2.id)!;
490 |     }
491 | 
492 |     let similarity = 0;
493 | 
494 |     // Type similarity (0-0.3)
495 |     if (entry1.type === entry2.type) {
496 |       similarity += 0.3;
497 |     }
498 | 
499 |     // Temporal similarity (0-0.2)
500 |     const timeDiff = Math.abs(
501 |       new Date(entry1.timestamp).getTime() -
502 |         new Date(entry2.timestamp).getTime(),
503 |     );
504 |     const maxTimeDiff = 7 * 24 * 60 * 60 * 1000; // 7 days
505 |     similarity += Math.max(0, 1 - timeDiff / maxTimeDiff) * 0.2;
506 | 
507 |     // Data similarity (0-0.5)
508 |     const dataSimilarity = this.calculateDataSimilarity(
509 |       entry1.data,
510 |       entry2.data,
511 |     );
512 |     similarity += dataSimilarity * 0.5;
513 | 
514 |     // Cache result
515 |     if (!this.similarityCache.has(entry1.id)) {
516 |       this.similarityCache.set(entry1.id, new Map());
517 |     }
518 |     this.similarityCache.get(entry1.id)!.set(entry2.id, similarity);
519 | 
520 |     return similarity;
521 |   }
522 | 
523 |   /**
524 |    * Calculate similarity between data objects
525 |    */
526 |   private calculateDataSimilarity(data1: any, data2: any): number {
527 |     const keys1 = new Set(Object.keys(data1));
528 |     const keys2 = new Set(Object.keys(data2));
529 |     const allKeys = new Set([...keys1, ...keys2]);
530 | 
531 |     let matches = 0;
532 |     let total = 0;
533 | 
534 |     for (const key of allKeys) {
535 |       total++;
536 |       if (keys1.has(key) && keys2.has(key)) {
537 |         if (data1[key] === data2[key]) {
538 |           matches++;
539 |         } else if (
540 |           typeof data1[key] === "string" &&
541 |           typeof data2[key] === "string"
542 |         ) {
543 |           // String similarity for text fields
544 |           const stringSim = this.calculateStringSimilarity(
545 |             data1[key],
546 |             data2[key],
547 |           );
548 |           matches += stringSim;
549 |         }
550 |       }
551 |     }
552 | 
553 |     return total > 0 ? matches / total : 0;
554 |   }
555 | 
556 |   /**
557 |    * Calculate string similarity (simple Jaccard similarity)
558 |    */
559 |   private calculateStringSimilarity(str1: string, str2: string): number {
560 |     const words1 = new Set(str1.toLowerCase().split(/\s+/));
561 |     const words2 = new Set(str2.toLowerCase().split(/\s+/));
562 | 
563 |     const intersection = new Set([...words1].filter((w) => words2.has(w)));
564 |     const union = new Set([...words1, ...words2]);
565 | 
566 |     return union.size > 0 ? intersection.size / union.size : 0;
567 |   }
568 | 
569 |   /**
570 |    * Check if entries can be safely merged
571 |    */
572 |   private canMergeEntries(entries: MemoryEntry[]): boolean {
573 |     if (entries.length < 2) return false;
574 | 
575 |     // All entries must have the same type
576 |     const firstType = entries[0].type;
577 |     if (!entries.every((e) => e.type === firstType)) {
578 |       return false;
579 |     }
580 | 
581 |     // Check for conflicting data
582 |     const firstData = entries[0].data;
583 |     for (const entry of entries.slice(1)) {
584 |       if (this.hasConflictingData(firstData, entry.data)) {
585 |         return false;
586 |       }
587 |     }
588 | 
589 |     return true;
590 |   }
591 | 
592 |   /**
593 |    * Check for conflicting data between entries
594 |    */
595 |   private hasConflictingData(data1: any, data2: any): boolean {
596 |     for (const key of Object.keys(data1)) {
597 |       if (key in data2 && data1[key] !== data2[key]) {
598 |         // Special handling for arrays and objects
599 |         if (Array.isArray(data1[key]) && Array.isArray(data2[key])) {
600 |           continue; // Arrays can be merged
601 |         }
602 |         if (typeof data1[key] === "object" && typeof data2[key] === "object") {
603 |           continue; // Objects can be merged
604 |         }
605 |         return true; // Conflicting primitive values
606 |       }
607 |     }
608 |     return false;
609 |   }
610 | 
611 |   /**
612 |    * Merge redundant entries into representative
613 |    */
614 |   private async mergeRedundantEntries(
615 |     representativeId: string,
616 |     duplicateIds: string[],
617 |   ): Promise<void> {
618 |     const representative = await this.storage.get(representativeId);
619 |     if (!representative) return;
620 | 
621 |     const duplicates = await Promise.all(
622 |       duplicateIds.map((id) => this.storage.get(id)),
623 |     );
624 | 
625 |     // Merge data from duplicates
626 |     const mergedData = { ...representative.data };
627 | 
628 |     for (const duplicate of duplicates) {
629 |       if (!duplicate) continue;
630 | 
631 |       // Merge arrays
632 |       for (const [key, value] of Object.entries(duplicate.data)) {
633 |         if (Array.isArray(value) && Array.isArray(mergedData[key])) {
634 |           mergedData[key] = [...new Set([...mergedData[key], ...value])];
635 |         } else if (
636 |           typeof value === "object" &&
637 |           typeof mergedData[key] === "object"
638 |         ) {
639 |           mergedData[key] = { ...mergedData[key], ...value };
640 |         } else if (!(key in mergedData)) {
641 |           mergedData[key] = value;
642 |         }
643 |       }
644 |     }
645 | 
646 |     // Update representative with merged data
647 |     await this.storage.update(representativeId, {
648 |       ...representative,
649 |       data: mergedData,
650 |       metadata: {
651 |         ...representative.metadata,
652 |         merged: true,
653 |         mergedCount: duplicateIds.length,
654 |         mergedAt: new Date().toISOString(),
655 |       },
656 |     });
657 |   }
658 | 
659 |   /**
660 |    * Check if entry is already compressed
661 |    */
662 |   private isCompressed(entry: MemoryEntry): boolean {
663 |     return Boolean(entry.metadata?.compressed);
664 |   }
665 | 
666 |   /**
667 |    * Compress entry data
668 |    */
669 |   private async compressEntry(entry: MemoryEntry): Promise<MemoryEntry> {
670 |     // Simple compression - in production, use actual compression library
671 |     const compressedData = this.simpleCompress(entry.data);
672 | 
673 |     return {
674 |       ...entry,
675 |       data: compressedData,
676 |       metadata: {
677 |         ...entry.metadata,
678 |         compressed: true,
679 |         compressionType: "simple",
680 |         compressedAt: new Date().toISOString(),
681 |         originalSize: JSON.stringify(entry.data).length,
682 |       },
683 |     };
684 |   }
685 | 
686 |   /**
687 |    * Simple compression simulation
688 |    */
689 |   private simpleCompress(data: any): any {
690 |     // This is a placeholder - in production, use proper compression
691 |     const stringified = JSON.stringify(data);
692 |     const compressed = stringified.replace(/\s+/g, " ").trim();
693 | 
694 |     return {
695 |       _compressed: true,
696 |       _data: compressed,
697 |       _type: "simple",
698 |     };
699 |   }
700 | 
701 |   /**
702 |    * Defragment storage files
703 |    */
704 |   private async defragmentStorage(): Promise<void> {
705 |     // Rebuild storage with optimal layout
706 |     const allEntries = await this.storage.getAll();
707 | 
708 |     // Sort entries for optimal access patterns
709 |     allEntries.sort(
710 |       (a, b) =>
711 |         new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
712 |     );
713 | 
714 |     // This would typically rewrite storage files
715 |     // For now, just trigger a rebuild
716 |     await this.storage.rebuildIndex();
717 |   }
718 | 
719 |   /**
720 |    * Optimize cache sizes based on usage patterns
721 |    */
722 |   private optimizeCaches(): void {
723 |     // Clear old cache entries
724 |     // Clear similarity cache entries older than 24 hours
725 |     for (const [key1, innerMap] of this.similarityCache.entries()) {
726 |       for (const [key2] of innerMap.entries()) {
727 |         // Simple heuristic - remove if keys suggest old timestamps
728 |         if (Math.random() < 0.1) {
729 |           // 10% chance to clear each entry
730 |           innerMap.delete(key2);
731 |         }
732 |       }
733 |       if (innerMap.size === 0) {
734 |         this.similarityCache.delete(key1);
735 |       }
736 |     }
737 | 
738 |     // Limit cache sizes
739 |     if (this.compressionCache.size > 10000) {
740 |       const entries = Array.from(this.compressionCache.entries());
741 |       this.compressionCache.clear();
742 |       // Keep only the most recent 5000 entries
743 |       entries.slice(-5000).forEach(([key, value]) => {
744 |         this.compressionCache.set(key, value);
745 |       });
746 |     }
747 |   }
748 | 
749 |   /**
750 |    * Get comprehensive optimization metrics
751 |    */
752 |   async getOptimizationMetrics(): Promise<OptimizationMetrics> {
753 |     const allEntries = await this.storage.getAll();
754 |     const totalEntries = allEntries.length;
755 | 
756 |     // Calculate storage size (approximate)
757 |     const storageSize =
758 |       allEntries.reduce((total, entry) => {
759 |         return total + JSON.stringify(entry).length;
760 |       }, 0) /
761 |       (1024 * 1024); // Convert to MB
762 | 
763 |     // Calculate index size (approximate)
764 |     const indexSize = (totalEntries * 100) / (1024 * 1024); // Rough estimate
765 | 
766 |     // Calculate compression ratio
767 |     const compressedEntries = allEntries.filter((e) => this.isCompressed(e));
768 |     const compressionRatio = compressedEntries.length / totalEntries;
769 | 
770 |     return {
771 |       totalEntries,
772 |       storageSize,
773 |       indexSize,
774 |       compressionRatio,
775 |       duplicatesRemoved: 0, // Would be tracked during runtime
776 |       entriesPruned: 0, // Would be tracked during runtime
777 |       performanceGain: 0, // Would be calculated based on before/after metrics
778 |       lastOptimization: new Date(),
779 |     };
780 |   }
781 | 
782 |   /**
783 |    * Update learning system based on pruning results
784 |    */
785 |   private async updateLearningFromPruning(
786 |     result: PruningResult,
787 |   ): Promise<void> {
788 |     // Create a learning entry about pruning effectiveness
789 |     const pruningLearning = {
790 |       action: "memory_pruning",
791 |       outcome: result.spaceSaved > 0 ? "success" : "neutral",
792 |       metrics: {
793 |         entriesRemoved: result.entriesRemoved,
794 |         spaceSaved: result.spaceSaved,
795 |         patternsPreserved: result.patternsPreserved,
796 |       },
797 |       timestamp: new Date().toISOString(),
798 |     };
799 | 
800 |     // This would integrate with the learning system
801 |     // For now, just emit an event
802 |     this.emit("learning_update", pruningLearning);
803 |   }
804 | 
805 |   /**
806 |    * Setup periodic cleanup
807 |    */
808 |   private setupPeriodicCleanup(): void {
809 |     // Run optimization every 24 hours
810 |     setInterval(
811 |       async () => {
812 |         try {
813 |           await this.prune();
814 |           this.emit("periodic_cleanup_completed");
815 |         } catch (error) {
816 |           this.emit("periodic_cleanup_error", {
817 |             error: error instanceof Error ? error.message : String(error),
818 |           });
819 |         }
820 |       },
821 |       24 * 60 * 60 * 1000,
822 |     );
823 |   }
824 | 
825 |   /**
826 |    * Get pruning recommendations
827 |    */
828 |   async getPruningRecommendations(): Promise<{
829 |     shouldPrune: boolean;
830 |     reasons: string[];
831 |     estimatedSavings: number;
832 |     recommendedPolicy: Partial<PruningPolicy>;
833 |   }> {
834 |     const metrics = await this.getOptimizationMetrics();
835 |     const reasons: string[] = [];
836 |     let shouldPrune = false;
837 |     let estimatedSavings = 0;
838 | 
839 |     // Check storage size
840 |     if (metrics.storageSize > this.defaultPolicy.maxSize * 0.8) {
841 |       shouldPrune = true;
842 |       reasons.push(
843 |         `Storage size (${metrics.storageSize.toFixed(2)}MB) approaching limit`,
844 |       );
845 |       estimatedSavings += metrics.storageSize * 0.2;
846 |     }
847 | 
848 |     // Check entry count
849 |     if (metrics.totalEntries > this.defaultPolicy.maxEntries * 0.8) {
850 |       shouldPrune = true;
851 |       reasons.push(`Entry count (${metrics.totalEntries}) approaching limit`);
852 |     }
853 | 
854 |     // Check compression ratio
855 |     if (metrics.compressionRatio < 0.3) {
856 |       reasons.push("Low compression ratio indicates optimization opportunity");
857 |       estimatedSavings += metrics.storageSize * 0.15;
858 |     }
859 | 
860 |     // Time-based recommendation
861 |     const daysSinceLastOptimization =
862 |       (Date.now() - metrics.lastOptimization.getTime()) / (24 * 60 * 60 * 1000);
863 |     if (daysSinceLastOptimization > 7) {
864 |       shouldPrune = true;
865 |       reasons.push("Regular maintenance window (weekly optimization)");
866 |     }
867 | 
868 |     return {
869 |       shouldPrune,
870 |       reasons,
871 |       estimatedSavings,
872 |       recommendedPolicy: {
873 |         maxAge: Math.max(30, this.defaultPolicy.maxAge - 30), // More aggressive if needed
874 |         compressionThreshold: Math.max(
875 |           7,
876 |           this.defaultPolicy.compressionThreshold - 7,
877 |         ),
878 |       },
879 |     };
880 |   }
881 | }
882 | 
```

--------------------------------------------------------------------------------
/src/tools/deploy-pages.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { promises as fs } from "fs";
  2 | import path from "path";
  3 | import { z } from "zod";
  4 | import { MCPToolResponse, formatMCPResponse } from "../types/api.js";
  5 | import {
  6 |   createOrUpdateProject,
  7 |   trackDeployment,
  8 |   getDeploymentRecommendations,
  9 |   getKnowledgeGraph,
 10 | } from "../memory/kg-integration.js";
 11 | import { getUserPreferenceManager } from "../memory/user-preferences.js";
 12 | 
 13 | const inputSchema = z.object({
 14 |   repository: z.string(),
 15 |   ssg: z
 16 |     .enum(["jekyll", "hugo", "docusaurus", "mkdocs", "eleventy"])
 17 |     .optional()
 18 |     .describe(
 19 |       "Static site generator to use. If not provided, will be retrieved from knowledge graph using analysisId",
 20 |     ),
 21 |   branch: z.string().optional().default("gh-pages"),
 22 |   customDomain: z.string().optional(),
 23 |   projectPath: z
 24 |     .string()
 25 |     .optional()
 26 |     .describe("Local path to the project for tracking"),
 27 |   projectName: z.string().optional().describe("Project name for tracking"),
 28 |   analysisId: z
 29 |     .string()
 30 |     .optional()
 31 |     .describe("ID from repository analysis for linking and SSG retrieval"),
 32 |   userId: z
 33 |     .string()
 34 |     .optional()
 35 |     .default("default")
 36 |     .describe("User ID for preference tracking"),
 37 | });
 38 | 
 39 | interface BuildConfig {
 40 |   workingDirectory: string | null;
 41 |   buildCommand: string;
 42 |   outputPath: string;
 43 |   nodeVersion?: string;
 44 |   packageManager?: "npm" | "yarn" | "pnpm";
 45 | }
 46 | 
 47 | /**
 48 |  * Retrieve SSG from knowledge graph using analysisId
 49 |  */
 50 | async function getSSGFromKnowledgeGraph(
 51 |   analysisId: string,
 52 | ): Promise<string | null> {
 53 |   try {
 54 |     const kg = await getKnowledgeGraph();
 55 | 
 56 |     // Find project node by analysis ID
 57 |     const projectNode = await kg.findNode({
 58 |       type: "project",
 59 |       properties: { id: analysisId },
 60 |     });
 61 | 
 62 |     if (!projectNode) {
 63 |       return null;
 64 |     }
 65 | 
 66 |     // Get deployment recommendations for this project
 67 |     const recommendations = await getDeploymentRecommendations(analysisId);
 68 | 
 69 |     if (recommendations.length > 0) {
 70 |       // Return the highest confidence SSG
 71 |       const topRecommendation = recommendations.sort(
 72 |         (a, b) => b.confidence - a.confidence,
 73 |       )[0];
 74 |       return topRecommendation.ssg;
 75 |     }
 76 | 
 77 |     // Fallback: check if there are any previous successful deployments
 78 |     const edges = await kg.findEdges({
 79 |       source: projectNode.id,
 80 |     });
 81 | 
 82 |     const deploymentEdges = edges.filter((e) =>
 83 |       e.type.startsWith("project_deployed_with"),
 84 |     );
 85 | 
 86 |     if (deploymentEdges.length > 0) {
 87 |       // Get the most recent successful deployment
 88 |       const successfulDeployments = deploymentEdges.filter(
 89 |         (e) => e.properties?.success === true,
 90 |       );
 91 | 
 92 |       if (successfulDeployments.length > 0) {
 93 |         const mostRecent = successfulDeployments.sort(
 94 |           (a, b) =>
 95 |             new Date(b.properties?.timestamp || 0).getTime() -
 96 |             new Date(a.properties?.timestamp || 0).getTime(),
 97 |         )[0];
 98 | 
 99 |         const configNode = (await kg.getAllNodes()).find(
100 |           (n) => n.id === mostRecent.target,
101 |         );
102 | 
103 |         return configNode?.properties?.ssg || null;
104 |       }
105 |     }
106 | 
107 |     return null;
108 |   } catch (error) {
109 |     console.warn("Failed to retrieve SSG from knowledge graph:", error);
110 |     return null;
111 |   }
112 | }
113 | 
114 | /**
115 |  * Detect documentation folder in repository
116 |  */
117 | async function detectDocsFolder(repoPath: string): Promise<string | null> {
118 |   const commonFolders = [
119 |     "docs",
120 |     "documentation",
121 |     "website",
122 |     "doc",
123 |     "site",
124 |     "pages",
125 |   ];
126 | 
127 |   for (const folder of commonFolders) {
128 |     const folderPath = path.join(repoPath, folder);
129 |     try {
130 |       const stat = await fs.stat(folderPath);
131 |       if (stat.isDirectory()) {
132 |         // Check if it has package.json or other SSG-specific files
133 |         const hasPackageJson = await fs
134 |           .access(path.join(folderPath, "package.json"))
135 |           .then(() => true)
136 |           .catch(() => false);
137 |         const hasMkDocsYml = await fs
138 |           .access(path.join(folderPath, "mkdocs.yml"))
139 |           .then(() => true)
140 |           .catch(() => false);
141 |         const hasConfigToml = await fs
142 |           .access(path.join(folderPath, "config.toml"))
143 |           .then(() => true)
144 |           .catch(() => false);
145 | 
146 |         if (hasPackageJson || hasMkDocsYml || hasConfigToml) {
147 |           return folder;
148 |         }
149 |       }
150 |     } catch {
151 |       continue;
152 |     }
153 |   }
154 | 
155 |   return null;
156 | }
157 | 
158 | /**
159 |  * Detect build configuration from package.json
160 |  */
161 | async function detectBuildConfig(
162 |   repoPath: string,
163 |   ssg: string,
164 |   docsFolder: string | null,
165 | ): Promise<BuildConfig> {
166 |   const workingDir = docsFolder || ".";
167 |   const packageJsonPath = path.join(repoPath, workingDir, "package.json");
168 | 
169 |   const defaults: Record<string, BuildConfig> = {
170 |     docusaurus: {
171 |       workingDirectory: docsFolder,
172 |       buildCommand: "npm run build",
173 |       outputPath: "./build",
174 |     },
175 |     eleventy: {
176 |       workingDirectory: docsFolder,
177 |       buildCommand: "npm run build",
178 |       outputPath: "./_site",
179 |     },
180 |     hugo: {
181 |       workingDirectory: docsFolder,
182 |       buildCommand: "hugo --minify",
183 |       outputPath: "./public",
184 |     },
185 |     jekyll: {
186 |       workingDirectory: docsFolder,
187 |       buildCommand: "bundle exec jekyll build",
188 |       outputPath: "./_site",
189 |     },
190 |     mkdocs: {
191 |       workingDirectory: docsFolder,
192 |       buildCommand: "mkdocs build",
193 |       outputPath: "./site",
194 |     },
195 |   };
196 | 
197 |   const config = defaults[ssg] || defaults.docusaurus;
198 | 
199 |   try {
200 |     const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
201 | 
202 |     // Detect build command from scripts
203 |     const scripts = packageJson.scripts || {};
204 |     if (scripts.build) {
205 |       config.buildCommand = "npm run build";
206 |     } else if (scripts["docs:build"]) {
207 |       config.buildCommand = "npm run docs:build";
208 |     } else if (scripts.start && scripts.start.includes("docusaurus")) {
209 |       config.buildCommand = "npm run build";
210 |     }
211 | 
212 |     // Detect package manager
213 |     const hasYarnLock = await fs
214 |       .access(path.join(repoPath, workingDir, "yarn.lock"))
215 |       .then(() => true)
216 |       .catch(() => false);
217 |     const hasPnpmLock = await fs
218 |       .access(path.join(repoPath, workingDir, "pnpm-lock.yaml"))
219 |       .then(() => true)
220 |       .catch(() => false);
221 | 
222 |     if (hasYarnLock) {
223 |       config.packageManager = "yarn";
224 |       config.buildCommand = config.buildCommand.replace("npm", "yarn");
225 |     } else if (hasPnpmLock) {
226 |       config.packageManager = "pnpm";
227 |       config.buildCommand = config.buildCommand.replace("npm", "pnpm");
228 |     } else {
229 |       config.packageManager = "npm";
230 |     }
231 | 
232 |     // Detect Node version from engines field
233 |     if (packageJson.engines?.node) {
234 |       config.nodeVersion = packageJson.engines.node;
235 |     }
236 |   } catch (error) {
237 |     // If package.json doesn't exist or can't be read, use defaults
238 |     console.warn("Using default build configuration:", error);
239 |   }
240 | 
241 |   return config;
242 | }
243 | 
244 | export async function deployPages(
245 |   args: unknown,
246 |   context?: any,
247 | ): Promise<{ content: any[] }> {
248 |   const startTime = Date.now();
249 |   const {
250 |     repository,
251 |     ssg: providedSSG,
252 |     branch,
253 |     customDomain,
254 |     projectPath,
255 |     projectName,
256 |     analysisId,
257 |     userId,
258 |   } = inputSchema.parse(args);
259 | 
260 |   // Declare ssg outside try block so it's accessible in catch
261 |   let ssg:
262 |     | "jekyll"
263 |     | "hugo"
264 |     | "docusaurus"
265 |     | "mkdocs"
266 |     | "eleventy"
267 |     | undefined = providedSSG;
268 | 
269 |   // Report initial progress
270 |   if (context?.meta?.progressToken) {
271 |     await context.meta.reportProgress?.({
272 |       progress: 0,
273 |       total: 100,
274 |     });
275 |   }
276 | 
277 |   await context?.info?.("🚀 Starting GitHub Pages deployment configuration...");
278 | 
279 |   try {
280 |     // Determine repository path (local or remote)
281 |     const repoPath = repository.startsWith("http") ? "." : repository;
282 |     await context?.info?.(`📂 Target repository: ${repository}`);
283 | 
284 |     if (context?.meta?.progressToken) {
285 |       await context.meta.reportProgress?.({
286 |         progress: 10,
287 |         total: 100,
288 |       });
289 |     }
290 | 
291 |     // Retrieve SSG from knowledge graph if not provided
292 |     ssg = providedSSG;
293 |     if (!ssg && analysisId) {
294 |       await context?.info?.(
295 |         `🔍 Retrieving SSG recommendation from analysis ${analysisId}...`,
296 |       );
297 |       const retrievedSSG = await getSSGFromKnowledgeGraph(analysisId);
298 |       if (retrievedSSG) {
299 |         ssg = retrievedSSG as
300 |           | "jekyll"
301 |           | "hugo"
302 |           | "docusaurus"
303 |           | "mkdocs"
304 |           | "eleventy";
305 |         await context?.info?.(`✅ Found recommended SSG: ${ssg}`);
306 |       }
307 |     } else if (ssg) {
308 |       await context?.info?.(`ℹ️ Using specified SSG: ${ssg}`);
309 |     }
310 | 
311 |     if (!ssg) {
312 |       const errorResponse: MCPToolResponse = {
313 |         success: false,
314 |         error: {
315 |           code: "SSG_NOT_SPECIFIED",
316 |           message:
317 |             "SSG parameter is required. Either provide it directly or ensure analysisId points to a project with SSG recommendations.",
318 |           resolution:
319 |             "Run analyze_repository and recommend_ssg first, or specify the SSG parameter explicitly.",
320 |         },
321 |         metadata: {
322 |           toolVersion: "1.0.0",
323 |           executionTime: Date.now() - startTime,
324 |           timestamp: new Date().toISOString(),
325 |         },
326 |       };
327 |       return formatMCPResponse(errorResponse);
328 |     }
329 | 
330 |     if (context?.meta?.progressToken) {
331 |       await context.meta.reportProgress?.({
332 |         progress: 25,
333 |         total: 100,
334 |       });
335 |     }
336 | 
337 |     // Detect documentation folder
338 |     await context?.info?.("📑 Detecting documentation folder...");
339 |     const docsFolder = await detectDocsFolder(repoPath);
340 |     await context?.info?.(
341 |       `📁 Documentation folder: ${docsFolder || "root directory"}`,
342 |     );
343 | 
344 |     if (context?.meta?.progressToken) {
345 |       await context.meta.reportProgress?.({
346 |         progress: 40,
347 |         total: 100,
348 |       });
349 |     }
350 | 
351 |     // Detect build configuration
352 |     await context?.info?.(`⚙️ Detecting build configuration for ${ssg}...`);
353 |     const buildConfig = await detectBuildConfig(repoPath, ssg, docsFolder);
354 | 
355 |     if (context?.meta?.progressToken) {
356 |       await context.meta.reportProgress?.({
357 |         progress: 55,
358 |         total: 100,
359 |       });
360 |     }
361 | 
362 |     // Create .github/workflows directory
363 |     await context?.info?.("📂 Creating GitHub Actions workflow directory...");
364 |     const workflowsDir = path.join(repoPath, ".github", "workflows");
365 |     await fs.mkdir(workflowsDir, { recursive: true });
366 | 
367 |     if (context?.meta?.progressToken) {
368 |       await context.meta.reportProgress?.({
369 |         progress: 70,
370 |         total: 100,
371 |       });
372 |     }
373 | 
374 |     // Generate workflow based on SSG and build config
375 |     await context?.info?.(`✍️ Generating ${ssg} deployment workflow...`);
376 |     const workflow = generateWorkflow(ssg, branch, customDomain, buildConfig);
377 |     const workflowPath = path.join(workflowsDir, "deploy-docs.yml");
378 |     await fs.writeFile(workflowPath, workflow);
379 |     await context?.info?.(
380 |       `✅ Workflow created: .github/workflows/deploy-docs.yml`,
381 |     );
382 | 
383 |     if (context?.meta?.progressToken) {
384 |       await context.meta.reportProgress?.({
385 |         progress: 85,
386 |         total: 100,
387 |       });
388 |     }
389 | 
390 |     // Create CNAME file if custom domain is specified
391 |     let cnameCreated = false;
392 |     if (customDomain) {
393 |       await context?.info?.(
394 |         `🌐 Creating CNAME file for custom domain: ${customDomain}...`,
395 |       );
396 |       const cnamePath = path.join(repoPath, "CNAME");
397 |       await fs.writeFile(cnamePath, customDomain);
398 |       cnameCreated = true;
399 |       await context?.info?.("✅ CNAME file created");
400 |     }
401 | 
402 |     const deploymentResult = {
403 |       repository,
404 |       ssg,
405 |       branch,
406 |       customDomain,
407 |       workflowPath: "deploy-docs.yml",
408 |       cnameCreated,
409 |       repoPath,
410 |       detectedConfig: {
411 |         docsFolder: docsFolder || "root",
412 |         buildCommand: buildConfig.buildCommand,
413 |         outputPath: buildConfig.outputPath,
414 |         packageManager: buildConfig.packageManager || "npm",
415 |         workingDirectory: buildConfig.workingDirectory,
416 |       },
417 |     };
418 | 
419 |     // Phase 2.3: Track deployment setup in knowledge graph
420 |     await context?.info?.("💾 Tracking deployment in Knowledge Graph...");
421 |     try {
422 |       // Create or update project in knowledge graph
423 |       if (projectPath || projectName) {
424 |         const timestamp = new Date().toISOString();
425 |         const project = await createOrUpdateProject({
426 |           id:
427 |             analysisId ||
428 |             `deploy_${repository.replace(/[^a-zA-Z0-9]/g, "_")}_${Date.now()}`,
429 |           timestamp,
430 |           path: projectPath || repository,
431 |           projectName: projectName || repository,
432 |           structure: {
433 |             totalFiles: 0, // Unknown at this point
434 |             languages: {},
435 |             hasTests: false,
436 |             hasCI: true, // We just added CI
437 |             hasDocs: true, // Setting up docs deployment
438 |           },
439 |         });
440 | 
441 |         // Track successful deployment setup
442 |         await trackDeployment(project.id, ssg, true, {
443 |           buildTime: Date.now() - startTime,
444 |         });
445 | 
446 |         // Update user preferences with SSG usage
447 |         const userPreferenceManager = await getUserPreferenceManager(userId);
448 |         await userPreferenceManager.trackSSGUsage({
449 |           ssg,
450 |           success: true, // Setup successful
451 |           timestamp,
452 |           projectType: projectPath || repository,
453 |         });
454 |       }
455 |     } catch (trackingError) {
456 |       // Don't fail the whole deployment if tracking fails
457 |       console.warn(
458 |         "Failed to track deployment in knowledge graph:",
459 |         trackingError,
460 |       );
461 |     }
462 | 
463 |     if (context?.meta?.progressToken) {
464 |       await context.meta.reportProgress?.({
465 |         progress: 100,
466 |         total: 100,
467 |       });
468 |     }
469 | 
470 |     const executionTime = Date.now() - startTime;
471 |     await context?.info?.(
472 |       `✅ Deployment configuration complete! ${ssg} workflow created in ${Math.round(
473 |         executionTime / 1000,
474 |       )}s`,
475 |     );
476 | 
477 |     const response: MCPToolResponse<typeof deploymentResult> = {
478 |       success: true,
479 |       data: deploymentResult,
480 |       metadata: {
481 |         toolVersion: "2.0.0",
482 |         executionTime,
483 |         timestamp: new Date().toISOString(),
484 |       },
485 |       recommendations: [
486 |         {
487 |           type: "info",
488 |           title: "Deployment Workflow Created",
489 |           description: `GitHub Actions workflow configured for ${ssg} deployment to ${branch} branch`,
490 |         },
491 |         ...(!providedSSG && analysisId
492 |           ? [
493 |               {
494 |                 type: "info" as const,
495 |                 title: "SSG Auto-Detected",
496 |                 description: `Retrieved ${ssg} from knowledge graph using analysisId`,
497 |               },
498 |             ]
499 |           : []),
500 |         ...(docsFolder
501 |           ? [
502 |               {
503 |                 type: "info" as const,
504 |                 title: "Documentation Folder Detected",
505 |                 description: `Found documentation in '${docsFolder}/' folder. Workflow configured with working-directory.`,
506 |               },
507 |             ]
508 |           : []),
509 |         ...(buildConfig.packageManager !== "npm"
510 |           ? [
511 |               {
512 |                 type: "info" as const,
513 |                 title: "Package Manager Detected",
514 |                 description: `Using ${buildConfig.packageManager} based on lockfile detection`,
515 |               },
516 |             ]
517 |           : []),
518 |         ...(customDomain
519 |           ? [
520 |               {
521 |                 type: "info" as const,
522 |                 title: "Custom Domain Configured",
523 |                 description: `CNAME file created for ${customDomain}`,
524 |               },
525 |             ]
526 |           : []),
527 |       ],
528 |       nextSteps: [
529 |         {
530 |           action: "Verify Deployment Setup",
531 |           toolRequired: "verify_deployment",
532 |           description: "Check that all deployment requirements are met",
533 |           priority: "high",
534 |         },
535 |         {
536 |           action: "Commit and Push",
537 |           toolRequired: "git",
538 |           description: "Commit workflow files and push to trigger deployment",
539 |           priority: "high",
540 |         },
541 |       ],
542 |     };
543 | 
544 |     return formatMCPResponse(response);
545 |   } catch (error) {
546 |     // Phase 2.3: Track failed deployment setup
547 |     try {
548 |       if ((projectPath || projectName) && ssg) {
549 |         const timestamp = new Date().toISOString();
550 |         const project = await createOrUpdateProject({
551 |           id:
552 |             analysisId ||
553 |             `deploy_${repository.replace(/[^a-zA-Z0-9]/g, "_")}_${Date.now()}`,
554 |           timestamp,
555 |           path: projectPath || repository,
556 |           projectName: projectName || repository,
557 |           structure: {
558 |             totalFiles: 0,
559 |             languages: {},
560 |             hasTests: false,
561 |             hasCI: false,
562 |             hasDocs: false,
563 |           },
564 |         });
565 | 
566 |         // Track failed deployment (only if ssg is known)
567 |         await trackDeployment(project.id, ssg, false, {
568 |           errorMessage: String(error),
569 |         });
570 | 
571 |         // Update user preferences with failed SSG usage
572 |         const userPreferenceManager = await getUserPreferenceManager(userId);
573 |         await userPreferenceManager.trackSSGUsage({
574 |           ssg,
575 |           success: false,
576 |           timestamp,
577 |           projectType: projectPath || repository,
578 |         });
579 |       }
580 |     } catch (trackingError) {
581 |       console.warn("Failed to track deployment failure:", trackingError);
582 |     }
583 | 
584 |     const errorResponse: MCPToolResponse = {
585 |       success: false,
586 |       error: {
587 |         code: "DEPLOYMENT_SETUP_FAILED",
588 |         message: `Failed to setup deployment: ${error}`,
589 |         resolution:
590 |           "Ensure repository path is accessible and GitHub Actions are enabled",
591 |       },
592 |       metadata: {
593 |         toolVersion: "1.0.0",
594 |         executionTime: Date.now() - startTime,
595 |         timestamp: new Date().toISOString(),
596 |       },
597 |     };
598 |     return formatMCPResponse(errorResponse);
599 |   }
600 | }
601 | 
602 | function generateWorkflow(
603 |   ssg: string,
604 |   branch: string,
605 |   _customDomain: string | undefined,
606 |   buildConfig: BuildConfig,
607 | ): string {
608 |   const workingDirPrefix = buildConfig.workingDirectory
609 |     ? `      working-directory: ${buildConfig.workingDirectory}\n`
610 |     : "";
611 | 
612 |   const nodeVersion = buildConfig.nodeVersion || "20";
613 |   const packageManager = buildConfig.packageManager || "npm";
614 | 
615 |   // Helper to get install command
616 |   const getInstallCmd = () => {
617 |     if (packageManager === "yarn") return "yarn install --frozen-lockfile";
618 |     if (packageManager === "pnpm") return "pnpm install --frozen-lockfile";
619 |     return "npm ci";
620 |   };
621 | 
622 |   // Helper to add working directory to steps
623 |   // const _addWorkingDir = (step: string) => {
624 |   //   if (!buildConfig.workingDirectory) return step;
625 |   //   return step.replace(
626 |   //     /^(\s+)run:/gm,
627 |   //     `$1working-directory: ${buildConfig.workingDirectory}\n$1run:`,
628 |   //   );
629 |   // };
630 | 
631 |   const workflows: Record<string, string> = {
632 |     docusaurus: `name: Deploy Docusaurus to GitHub Pages
633 | 
634 | on:
635 |   push:
636 |     branches: [main]
637 |   workflow_dispatch:
638 | 
639 | permissions:
640 |   contents: read
641 |   pages: write
642 |   id-token: write
643 | 
644 | concurrency:
645 |   group: "pages"
646 |   cancel-in-progress: false
647 | 
648 | jobs:
649 |   build:
650 |     runs-on: ubuntu-latest
651 |     steps:
652 |       - name: Checkout
653 |         uses: actions/checkout@v4
654 | 
655 |       - name: Setup Node.js
656 |         uses: actions/setup-node@v4
657 |         with:
658 |           node-version: '${nodeVersion}'
659 |           cache: '${packageManager}'${
660 |             buildConfig.workingDirectory
661 |               ? `\n          cache-dependency-path: ${buildConfig.workingDirectory}/package-lock.json`
662 |               : ""
663 |           }
664 | 
665 |       - name: Install dependencies
666 | ${workingDirPrefix}        run: ${getInstallCmd()}
667 | 
668 |       - name: Build website
669 | ${workingDirPrefix}        run: ${buildConfig.buildCommand}
670 | 
671 |       - name: Upload artifact
672 |         uses: actions/upload-pages-artifact@v2
673 |         with:
674 |           path: ${
675 |             buildConfig.workingDirectory
676 |               ? `${buildConfig.workingDirectory}/${buildConfig.outputPath}`
677 |               : buildConfig.outputPath
678 |           }
679 | 
680 |   deploy:
681 |     environment:
682 |       name: github-pages
683 |       url: \${{ steps.deployment.outputs.page_url }}
684 |     runs-on: ubuntu-latest
685 |     needs: build
686 |     steps:
687 |       - name: Deploy to GitHub Pages
688 |         id: deployment
689 |         uses: actions/deploy-pages@v3`,
690 | 
691 |     mkdocs: `name: Deploy MkDocs to GitHub Pages
692 | 
693 | on:
694 |   push:
695 |     branches: [main]
696 |   workflow_dispatch:
697 | 
698 | permissions:
699 |   contents: write
700 | 
701 | jobs:
702 |   deploy:
703 |     runs-on: ubuntu-latest${
704 |       buildConfig.workingDirectory
705 |         ? `\n    defaults:\n      run:\n        working-directory: ${buildConfig.workingDirectory}`
706 |         : ""
707 |     }
708 |     steps:
709 |       - uses: actions/checkout@v4
710 | 
711 |       - name: Setup Python
712 |         uses: actions/setup-python@v4
713 |         with:
714 |           python-version: '3.x'
715 | 
716 |       - name: Install dependencies
717 |         run: |
718 |           pip install -r requirements.txt
719 | 
720 |       - name: Build and Deploy
721 |         run: mkdocs gh-deploy --force --branch ${branch}`,
722 | 
723 |     hugo: `name: Deploy Hugo to GitHub Pages
724 | 
725 | on:
726 |   push:
727 |     branches: [main]
728 |   workflow_dispatch:
729 | 
730 | permissions:
731 |   contents: read
732 |   pages: write
733 |   id-token: write
734 | 
735 | concurrency:
736 |   group: "pages"
737 |   cancel-in-progress: false
738 | 
739 | jobs:
740 |   build:
741 |     runs-on: ubuntu-latest${
742 |       buildConfig.workingDirectory
743 |         ? `\n    defaults:\n      run:\n        working-directory: ${buildConfig.workingDirectory}`
744 |         : ""
745 |     }
746 |     steps:
747 |       - name: Checkout
748 |         uses: actions/checkout@v4
749 |         with:
750 |           submodules: recursive
751 | 
752 |       - name: Setup Hugo
753 |         uses: peaceiris/actions-hugo@v2
754 |         with:
755 |           hugo-version: 'latest'
756 |           extended: true
757 | 
758 |       - name: Build
759 |         run: ${buildConfig.buildCommand}
760 | 
761 |       - name: Upload artifact
762 |         uses: actions/upload-pages-artifact@v2
763 |         with:
764 |           path: ${
765 |             buildConfig.workingDirectory
766 |               ? `${buildConfig.workingDirectory}/${buildConfig.outputPath}`
767 |               : buildConfig.outputPath
768 |           }
769 | 
770 |   deploy:
771 |     environment:
772 |       name: github-pages
773 |       url: \${{ steps.deployment.outputs.page_url }}
774 |     runs-on: ubuntu-latest
775 |     needs: build
776 |     steps:
777 |       - name: Deploy to GitHub Pages
778 |         id: deployment
779 |         uses: actions/deploy-pages@v3`,
780 | 
781 |     jekyll: `name: Deploy Jekyll to GitHub Pages
782 | 
783 | on:
784 |   push:
785 |     branches: [main]
786 |   workflow_dispatch:
787 | 
788 | permissions:
789 |   contents: read
790 |   pages: write
791 |   id-token: write
792 | 
793 | concurrency:
794 |   group: "pages"
795 |   cancel-in-progress: false
796 | 
797 | jobs:
798 |   build:
799 |     runs-on: ubuntu-latest${
800 |       buildConfig.workingDirectory
801 |         ? `\n    defaults:\n      run:\n        working-directory: ${buildConfig.workingDirectory}`
802 |         : ""
803 |     }
804 |     steps:
805 |       - name: Checkout
806 |         uses: actions/checkout@v4
807 | 
808 |       - name: Setup Ruby
809 |         uses: ruby/setup-ruby@v1
810 |         with:
811 |           ruby-version: '3.1'
812 |           bundler-cache: true${
813 |             buildConfig.workingDirectory
814 |               ? `\n          working-directory: ${buildConfig.workingDirectory}`
815 |               : ""
816 |           }
817 | 
818 |       - name: Build with Jekyll
819 |         run: ${buildConfig.buildCommand}
820 |         env:
821 |           JEKYLL_ENV: production
822 | 
823 |       - name: Upload artifact
824 |         uses: actions/upload-pages-artifact@v2${
825 |           buildConfig.workingDirectory
826 |             ? `\n        with:\n          path: ${buildConfig.workingDirectory}/${buildConfig.outputPath}`
827 |             : ""
828 |         }
829 | 
830 |   deploy:
831 |     environment:
832 |       name: github-pages
833 |       url: \${{ steps.deployment.outputs.page_url }}
834 |     runs-on: ubuntu-latest
835 |     needs: build
836 |     steps:
837 |       - name: Deploy to GitHub Pages
838 |         id: deployment
839 |         uses: actions/deploy-pages@v3`,
840 | 
841 |     eleventy: `name: Deploy Eleventy to GitHub Pages
842 | 
843 | on:
844 |   push:
845 |     branches: [main]
846 |   workflow_dispatch:
847 | 
848 | permissions:
849 |   contents: read
850 |   pages: write
851 |   id-token: write
852 | 
853 | concurrency:
854 |   group: "pages"
855 |   cancel-in-progress: false
856 | 
857 | jobs:
858 |   build:
859 |     runs-on: ubuntu-latest
860 |     steps:
861 |       - name: Checkout
862 |         uses: actions/checkout@v4
863 | 
864 |       - name: Setup Node.js
865 |         uses: actions/setup-node@v4
866 |         with:
867 |           node-version: '${nodeVersion}'
868 |           cache: '${packageManager}'${
869 |             buildConfig.workingDirectory
870 |               ? `\n          cache-dependency-path: ${buildConfig.workingDirectory}/package-lock.json`
871 |               : ""
872 |           }
873 | 
874 |       - name: Install dependencies
875 | ${workingDirPrefix}        run: ${getInstallCmd()}
876 | 
877 |       - name: Build site
878 | ${workingDirPrefix}        run: ${buildConfig.buildCommand}
879 | 
880 |       - name: Upload artifact
881 |         uses: actions/upload-pages-artifact@v2
882 |         with:
883 |           path: ${
884 |             buildConfig.workingDirectory
885 |               ? `${buildConfig.workingDirectory}/${buildConfig.outputPath}`
886 |               : buildConfig.outputPath
887 |           }
888 | 
889 |   deploy:
890 |     environment:
891 |       name: github-pages
892 |       url: \${{ steps.deployment.outputs.page_url }}
893 |     runs-on: ubuntu-latest
894 |     needs: build
895 |     steps:
896 |       - name: Deploy to GitHub Pages
897 |         id: deployment
898 |         uses: actions/deploy-pages@v3`,
899 |   };
900 | 
901 |   return workflows[ssg] || workflows.jekyll;
902 | }
903 | 
```

--------------------------------------------------------------------------------
/tests/tools/validate-readme-checklist.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
  2 | import { promises as fs } from "fs";
  3 | import * as path from "path";
  4 | import * as tmp from "tmp";
  5 | import {
  6 |   validateReadmeChecklist,
  7 |   ReadmeChecklistValidator,
  8 |   ValidateReadmeChecklistSchema,
  9 | } from "../../src/tools/validate-readme-checklist";
 10 | 
 11 | describe("README Checklist Validator", () => {
 12 |   let tempDir: string;
 13 |   let validator: ReadmeChecklistValidator;
 14 | 
 15 |   beforeEach(() => {
 16 |     tempDir = tmp.dirSync({ unsafeCleanup: true }).name;
 17 |     validator = new ReadmeChecklistValidator();
 18 |   });
 19 | 
 20 |   afterEach(async () => {
 21 |     try {
 22 |       await fs.rmdir(tempDir, { recursive: true });
 23 |     } catch {
 24 |       // Ignore cleanup errors
 25 |     }
 26 |   });
 27 | 
 28 |   async function createTestReadme(
 29 |     content: string,
 30 |     filename = "README.md",
 31 |   ): Promise<string> {
 32 |     const readmePath = path.join(tempDir, filename);
 33 |     await fs.writeFile(readmePath, content, "utf-8");
 34 |     return readmePath;
 35 |   }
 36 | 
 37 |   async function createProjectFile(
 38 |     filename: string,
 39 |     content = "",
 40 |   ): Promise<void> {
 41 |     await fs.writeFile(path.join(tempDir, filename), content, "utf-8");
 42 |   }
 43 | 
 44 |   describe("Input Validation", () => {
 45 |     it("should validate required fields", () => {
 46 |       expect(() => ValidateReadmeChecklistSchema.parse({})).toThrow();
 47 |       expect(() =>
 48 |         ValidateReadmeChecklistSchema.parse({
 49 |           readmePath: "",
 50 |         }),
 51 |       ).toThrow();
 52 |     });
 53 | 
 54 |     it("should accept valid input with defaults", () => {
 55 |       const input = ValidateReadmeChecklistSchema.parse({
 56 |         readmePath: "/path/to/README.md",
 57 |       });
 58 | 
 59 |       expect(input.strict).toBe(false);
 60 |       expect(input.outputFormat).toBe("console");
 61 |     });
 62 | 
 63 |     it("should validate output format options", () => {
 64 |       const validFormats = ["json", "markdown", "console"];
 65 | 
 66 |       for (const format of validFormats) {
 67 |         expect(() =>
 68 |           ValidateReadmeChecklistSchema.parse({
 69 |             readmePath: "/test/README.md",
 70 |             outputFormat: format,
 71 |           }),
 72 |         ).not.toThrow();
 73 |       }
 74 | 
 75 |       expect(() =>
 76 |         ValidateReadmeChecklistSchema.parse({
 77 |           readmePath: "/test/README.md",
 78 |           outputFormat: "invalid",
 79 |         }),
 80 |       ).toThrow();
 81 |     });
 82 |   });
 83 | 
 84 |   describe("Essential Sections Validation", () => {
 85 |     it("should detect project title", async () => {
 86 |       const goodReadme = await createTestReadme(
 87 |         "# My Project\n\nDescription here",
 88 |         "good-README.md",
 89 |       );
 90 |       const badReadme = await createTestReadme(
 91 |         "## Not a main title\n\nNo main heading",
 92 |         "bad-README.md",
 93 |       );
 94 | 
 95 |       const goodInput = ValidateReadmeChecklistSchema.parse({
 96 |         readmePath: goodReadme,
 97 |       });
 98 |       const badInput = ValidateReadmeChecklistSchema.parse({
 99 |         readmePath: badReadme,
100 |       });
101 |       const result = await validateReadmeChecklist(goodInput);
102 |       const result2 = await validateReadmeChecklist(badInput);
103 | 
104 |       const titleCheck = result.categories["Essential Sections"].results.find(
105 |         (r) => r.item.id === "title",
106 |       );
107 |       const badTitleCheck = result2.categories[
108 |         "Essential Sections"
109 |       ].results.find((r) => r.item.id === "title");
110 | 
111 |       expect(titleCheck?.passed).toBe(true);
112 |       expect(badTitleCheck?.passed).toBe(false);
113 |     });
114 | 
115 |     it("should detect project description", async () => {
116 |       const withSubtitle = await createTestReadme(
117 |         "# Project\n\n> A great project description",
118 |         "subtitle-README.md",
119 |       );
120 |       const withParagraph = await createTestReadme(
121 |         "# Project\n\nThis is a description paragraph",
122 |         "paragraph-README.md",
123 |       );
124 |       const withoutDesc = await createTestReadme(
125 |         "# Project\n\n## Installation",
126 |         "no-desc-README.md",
127 |       );
128 | 
129 |       const subtitleResult = await validateReadmeChecklist(
130 |         ValidateReadmeChecklistSchema.parse({ readmePath: withSubtitle }),
131 |       );
132 |       const paragraphResult = await validateReadmeChecklist(
133 |         ValidateReadmeChecklistSchema.parse({ readmePath: withParagraph }),
134 |       );
135 |       const noDescResult = await validateReadmeChecklist(
136 |         ValidateReadmeChecklistSchema.parse({ readmePath: withoutDesc }),
137 |       );
138 | 
139 |       const getDescCheck = (result: any) =>
140 |         result.categories["Essential Sections"].results.find(
141 |           (r: any) => r.item.id === "description",
142 |         );
143 | 
144 |       expect(getDescCheck(subtitleResult)?.passed).toBe(true);
145 |       expect(getDescCheck(paragraphResult)?.passed).toBe(true);
146 |       expect(getDescCheck(noDescResult)?.passed).toBe(false);
147 |     });
148 | 
149 |     it("should detect TL;DR section", async () => {
150 |       const withTldr = await createTestReadme(
151 |         "# Project\n\n## TL;DR\n\nQuick summary",
152 |         "tldr-README.md",
153 |       );
154 |       const withQuickStart = await createTestReadme(
155 |         "# Project\n\n## Quick Start\n\nQuick summary",
156 |         "quickstart-README.md",
157 |       );
158 |       const withoutTldr = await createTestReadme(
159 |         "# Project\n\n## Installation",
160 |         "no-tldr-README.md",
161 |       );
162 | 
163 |       const tldrInput = ValidateReadmeChecklistSchema.parse({
164 |         readmePath: withTldr,
165 |       });
166 |       const quickStartInput = ValidateReadmeChecklistSchema.parse({
167 |         readmePath: withQuickStart,
168 |       });
169 |       const noTldrInput = ValidateReadmeChecklistSchema.parse({
170 |         readmePath: withoutTldr,
171 |       });
172 |       const result = await validateReadmeChecklist(tldrInput);
173 |       const result2 = await validateReadmeChecklist(quickStartInput);
174 |       const result3 = await validateReadmeChecklist(noTldrInput);
175 | 
176 |       const getTldrCheck = (result: any) =>
177 |         result.categories["Essential Sections"].results.find(
178 |           (r: any) => r.item.id === "tldr",
179 |         );
180 | 
181 |       expect(getTldrCheck(result)?.passed).toBe(true);
182 |       expect(getTldrCheck(result2)?.passed).toBe(true);
183 |       expect(getTldrCheck(result3)?.passed).toBe(false);
184 |     });
185 | 
186 |     it("should detect installation instructions with code blocks", async () => {
187 |       const goodInstall = await createTestReadme(
188 |         `
189 | # Project
190 | ## Installation
191 | \`\`\`bash
192 | npm install project
193 | \`\`\`
194 |       `,
195 |         "good-install-README.md",
196 |       );
197 | 
198 |       const noCodeBlocks = await createTestReadme(
199 |         `
200 | # Project
201 | ## Installation
202 | Just install it somehow
203 |       `,
204 |         "no-code-README.md",
205 |       );
206 | 
207 |       const noInstallSection = await createTestReadme(
208 |         "# Project\n\nSome content",
209 |         "no-install-README.md",
210 |       );
211 | 
212 |       const goodResult = await validateReadmeChecklist(
213 |         ValidateReadmeChecklistSchema.parse({ readmePath: goodInstall }),
214 |       );
215 |       const noCodeResult = await validateReadmeChecklist(
216 |         ValidateReadmeChecklistSchema.parse({ readmePath: noCodeBlocks }),
217 |       );
218 |       const noSectionResult = await validateReadmeChecklist(
219 |         ValidateReadmeChecklistSchema.parse({ readmePath: noInstallSection }),
220 |       );
221 | 
222 |       const getInstallCheck = (result: any) =>
223 |         result.categories["Essential Sections"].results.find(
224 |           (r: any) => r.item.id === "installation",
225 |         );
226 | 
227 |       expect(getInstallCheck(goodResult)?.passed).toBe(true);
228 |       expect(getInstallCheck(noCodeResult)?.passed).toBe(true); // This should pass because it has Installation section
229 |       expect(getInstallCheck(noSectionResult)?.passed).toBe(false);
230 |     });
231 | 
232 |     it("should detect usage examples", async () => {
233 |       const goodUsage = await createTestReadme(
234 |         `
235 | # Project
236 | ## Usage
237 | \`\`\`javascript
238 | const lib = require('lib');
239 | lib.doSomething();
240 | \`\`\`
241 |       `,
242 |         "good-usage-README.md",
243 |       );
244 | 
245 |       const noUsage = await createTestReadme(
246 |         "# Project\n\nNo usage section",
247 |         "no-usage-README.md",
248 |       );
249 | 
250 |       const goodResult = await validateReadmeChecklist(
251 |         ValidateReadmeChecklistSchema.parse({ readmePath: goodUsage }),
252 |       );
253 |       const noUsageResult = await validateReadmeChecklist(
254 |         ValidateReadmeChecklistSchema.parse({ readmePath: noUsage }),
255 |       );
256 | 
257 |       const getUsageCheck = (result: any) =>
258 |         result.categories["Essential Sections"].results.find(
259 |           (r: any) => r.item.id === "usage",
260 |         );
261 | 
262 |       expect(getUsageCheck(goodResult)?.passed).toBe(true);
263 |       expect(getUsageCheck(noUsageResult)?.passed).toBe(false);
264 |     });
265 | 
266 |     it("should detect license information", async () => {
267 |       const readmeWithLicense = await createTestReadme(
268 |         "# Project\n\n## License\n\nMIT",
269 |         "license-README.md",
270 |       );
271 |       const readmeWithoutLicense = await createTestReadme(
272 |         "# Project\n\nNo license info",
273 |         "no-license-README.md",
274 |       );
275 | 
276 |       // Test without LICENSE file first
277 |       const withLicenseResult = await validateReadmeChecklist(
278 |         ValidateReadmeChecklistSchema.parse({
279 |           readmePath: readmeWithLicense,
280 |           projectPath: tempDir,
281 |         }),
282 |       );
283 |       const withoutLicenseResult = await validateReadmeChecklist(
284 |         ValidateReadmeChecklistSchema.parse({
285 |           readmePath: readmeWithoutLicense,
286 |           projectPath: tempDir,
287 |         }),
288 |       );
289 | 
290 |       // Test with LICENSE file
291 |       await createProjectFile("LICENSE", "MIT License...");
292 |       const readmeWithLicenseFile = await createTestReadme(
293 |         "# Project\n\nSome content",
294 |         "license-file-README.md",
295 |       );
296 |       const withLicenseFileResult = await validateReadmeChecklist(
297 |         ValidateReadmeChecklistSchema.parse({
298 |           readmePath: readmeWithLicenseFile,
299 |           projectPath: tempDir,
300 |         }),
301 |       );
302 | 
303 |       const getLicenseCheck = (result: any) =>
304 |         result.categories["Essential Sections"].results.find(
305 |           (r: any) => r.item.id === "license",
306 |         );
307 | 
308 |       expect(getLicenseCheck(withLicenseResult)?.passed).toBe(true);
309 |       expect(getLicenseCheck(withoutLicenseResult)?.passed).toBe(false);
310 |       expect(getLicenseCheck(withLicenseFileResult)?.passed).toBe(true);
311 |     });
312 |   });
313 | 
314 |   describe("Community Health Validation", () => {
315 |     it("should detect contributing guidelines", async () => {
316 |       const readmeWithContributing = await createTestReadme(
317 |         "# Project\n\n## Contributing\n\nSee CONTRIBUTING.md",
318 |       );
319 |       await createProjectFile("CONTRIBUTING.md", "Contributing guidelines...");
320 | 
321 |       const result = await validateReadmeChecklist(
322 |         ValidateReadmeChecklistSchema.parse({
323 |           readmePath: readmeWithContributing,
324 |           projectPath: tempDir,
325 |         }),
326 |       );
327 | 
328 |       const contributingCheck = result.categories[
329 |         "Community Health"
330 |       ].results.find((r) => r.item.id === "contributing");
331 |       expect(contributingCheck?.passed).toBe(true);
332 |     });
333 | 
334 |     it("should detect code of conduct", async () => {
335 |       await createProjectFile("CODE_OF_CONDUCT.md", "Code of conduct...");
336 |       const readme = await createTestReadme("# Project\n\nSome content");
337 | 
338 |       const result = await validateReadmeChecklist(
339 |         ValidateReadmeChecklistSchema.parse({
340 |           readmePath: readme,
341 |           projectPath: tempDir,
342 |         }),
343 |       );
344 | 
345 |       const cocCheck = result.categories["Community Health"].results.find(
346 |         (r) => r.item.id === "code-of-conduct",
347 |       );
348 |       expect(cocCheck?.passed).toBe(true);
349 |     });
350 | 
351 |     it("should detect security policy", async () => {
352 |       await createProjectFile("SECURITY.md", "Security policy...");
353 |       const readme = await createTestReadme("# Project\n\nSome content");
354 | 
355 |       const result = await validateReadmeChecklist(
356 |         ValidateReadmeChecklistSchema.parse({
357 |           readmePath: readme,
358 |           projectPath: tempDir,
359 |         }),
360 |       );
361 | 
362 |       const securityCheck = result.categories["Community Health"].results.find(
363 |         (r) => r.item.id === "security",
364 |       );
365 |       expect(securityCheck?.passed).toBe(true);
366 |     });
367 |   });
368 | 
369 |   describe("Visual Elements Validation", () => {
370 |     it("should detect status badges", async () => {
371 |       const withBadges = await createTestReadme(
372 |         `
373 | # Project
374 | [![Build Status](https://travis-ci.org/user/repo.svg?branch=main)](https://travis-ci.org/user/repo)
375 | [![npm version](https://badge.fury.io/js/package.svg)](https://badge.fury.io/js/package)
376 |       `,
377 |         "with-badges-README.md",
378 |       );
379 | 
380 |       const withoutBadges = await createTestReadme(
381 |         "# Project\n\nNo badges here",
382 |         "no-badges-README.md",
383 |       );
384 | 
385 |       const withBadgesResult = await validateReadmeChecklist(
386 |         ValidateReadmeChecklistSchema.parse({ readmePath: withBadges }),
387 |       );
388 |       const withoutBadgesResult = await validateReadmeChecklist(
389 |         ValidateReadmeChecklistSchema.parse({ readmePath: withoutBadges }),
390 |       );
391 | 
392 |       const getBadgeCheck = (result: any) =>
393 |         result.categories["Visual Elements"].results.find(
394 |           (r: any) => r.item.id === "badges",
395 |         );
396 | 
397 |       expect(getBadgeCheck(withBadgesResult)?.passed).toBe(true);
398 |       expect(getBadgeCheck(withoutBadgesResult)?.passed).toBe(false);
399 |     });
400 | 
401 |     it("should detect screenshots and images", async () => {
402 |       const withScreenshots = await createTestReadme(
403 |         `
404 | # Project
405 | ![Screenshot](screenshot.png)
406 | ![Demo](demo.gif)
407 |       `,
408 |         "with-screenshots-README.md",
409 |       );
410 | 
411 |       const withoutScreenshots = await createTestReadme(
412 |         "# Project\n\nNo images",
413 |         "no-screenshots-README.md",
414 |       );
415 | 
416 |       const withScreenshotsResult = await validateReadmeChecklist(
417 |         ValidateReadmeChecklistSchema.parse({ readmePath: withScreenshots }),
418 |       );
419 |       const withoutScreenshotsResult = await validateReadmeChecklist(
420 |         ValidateReadmeChecklistSchema.parse({ readmePath: withoutScreenshots }),
421 |       );
422 | 
423 |       const getScreenshotCheck = (result: any) =>
424 |         result.categories["Visual Elements"].results.find(
425 |           (r: any) => r.item.id === "screenshots",
426 |         );
427 | 
428 |       expect(getScreenshotCheck(withScreenshotsResult)?.passed).toBe(true);
429 |       expect(getScreenshotCheck(withoutScreenshotsResult)?.passed).toBe(false);
430 |     });
431 | 
432 |     it("should validate markdown formatting", async () => {
433 |       const goodFormatting = await createTestReadme(
434 |         `
435 | # Main Title
436 | ## Section 1
437 | ### Subsection
438 | ## Section 2
439 |       `,
440 |         "good-formatting-README.md",
441 |       );
442 | 
443 |       const poorFormatting = await createTestReadme(
444 |         `
445 | # Title
446 | #Another Title
447 | ##Poor Spacing
448 |       `,
449 |         "poor-formatting-README.md",
450 |       );
451 | 
452 |       const goodResult = await validateReadmeChecklist(
453 |         ValidateReadmeChecklistSchema.parse({ readmePath: goodFormatting }),
454 |       );
455 |       const poorResult = await validateReadmeChecklist(
456 |         ValidateReadmeChecklistSchema.parse({ readmePath: poorFormatting }),
457 |       );
458 | 
459 |       const getFormattingCheck = (result: any) =>
460 |         result.categories["Visual Elements"].results.find(
461 |           (r: any) => r.item.id === "formatting",
462 |         );
463 | 
464 |       expect(getFormattingCheck(goodResult)?.passed).toBe(true);
465 |       expect(getFormattingCheck(poorResult)?.passed).toBe(false);
466 |     });
467 |   });
468 | 
469 |   describe("Content Quality Validation", () => {
470 |     it("should detect working code examples", async () => {
471 |       const withCodeExamples = await createTestReadme(
472 |         `
473 | # Project
474 | \`\`\`javascript
475 | const lib = require('lib');
476 | lib.doSomething();
477 | \`\`\`
478 | 
479 | \`\`\`bash
480 | npm install lib
481 | \`\`\`
482 |       `,
483 |         "with-code-README.md",
484 |       );
485 | 
486 |       const withoutCodeExamples = await createTestReadme(
487 |         "# Project\n\nNo code examples",
488 |         "no-code-examples-README.md",
489 |       );
490 | 
491 |       const withCodeResult = await validateReadmeChecklist(
492 |         ValidateReadmeChecklistSchema.parse({ readmePath: withCodeExamples }),
493 |       );
494 |       const withoutCodeResult = await validateReadmeChecklist(
495 |         ValidateReadmeChecklistSchema.parse({
496 |           readmePath: withoutCodeExamples,
497 |         }),
498 |       );
499 | 
500 |       const getCodeCheck = (result: any) =>
501 |         result.categories["Content Quality"].results.find(
502 |           (r: any) => r.item.id === "working-examples",
503 |         );
504 | 
505 |       expect(getCodeCheck(withCodeResult)?.passed).toBe(true);
506 |       expect(getCodeCheck(withoutCodeResult)?.passed).toBe(false);
507 |     });
508 | 
509 |     it("should validate appropriate length", async () => {
510 |       const shortReadme = await createTestReadme(
511 |         "# Project\n\nShort content",
512 |         "short-README.md",
513 |       );
514 |       const longContent =
515 |         "# Project\n\n" + "Long line of content.\n".repeat(350);
516 |       const longReadme = await createTestReadme(longContent, "long-README.md");
517 | 
518 |       const shortResult = await validateReadmeChecklist(
519 |         ValidateReadmeChecklistSchema.parse({ readmePath: shortReadme }),
520 |       );
521 |       const longResult = await validateReadmeChecklist(
522 |         ValidateReadmeChecklistSchema.parse({ readmePath: longReadme }),
523 |       );
524 | 
525 |       const getLengthCheck = (result: any) =>
526 |         result.categories["Content Quality"].results.find(
527 |           (r: any) => r.item.id === "appropriate-length",
528 |         );
529 | 
530 |       expect(getLengthCheck(shortResult)?.passed).toBe(true);
531 |       expect(getLengthCheck(longResult)?.passed).toBe(false);
532 |     });
533 | 
534 |     it("should validate scannable structure", async () => {
535 |       const goodStructure = await createTestReadme(
536 |         `
537 | # Main Title
538 | ## Section 1
539 | ### Subsection 1.1
540 | - Item 1
541 | - Item 2
542 | ### Subsection 1.2
543 | ## Section 2
544 | ### Subsection 2.1
545 | - Another item
546 | - Yet another item
547 |       `,
548 |         "good-structure-README.md",
549 |       );
550 | 
551 |       const poorStructure = await createTestReadme(
552 |         `
553 | # Title
554 | #### Skipped levels
555 | ## Back to level 2
556 |       `,
557 |         "poor-structure-README.md",
558 |       );
559 | 
560 |       const goodResult = await validateReadmeChecklist(
561 |         ValidateReadmeChecklistSchema.parse({ readmePath: goodStructure }),
562 |       );
563 |       const poorResult = await validateReadmeChecklist(
564 |         ValidateReadmeChecklistSchema.parse({ readmePath: poorStructure }),
565 |       );
566 | 
567 |       const getStructureCheck = (result: any) =>
568 |         result.categories["Content Quality"].results.find(
569 |           (r: any) => r.item.id === "scannable-structure",
570 |         );
571 | 
572 |       expect(getStructureCheck(goodResult)?.passed).toBe(true);
573 |       expect(getStructureCheck(poorResult)?.passed).toBe(false);
574 |     });
575 |   });
576 | 
577 |   describe("Report Generation", () => {
578 |     it("should generate comprehensive report with all categories", async () => {
579 |       const readme = await createTestReadme(`
580 | # Test Project
581 | > A test project description
582 | 
583 | ## TL;DR
584 | Quick summary of the project.
585 | 
586 | ## Quick Start
587 | \`\`\`bash
588 | npm install test-project
589 | \`\`\`
590 | 
591 | ## Usage
592 | \`\`\`javascript
593 | const test = require('test-project');
594 | test.run();
595 | \`\`\`
596 | 
597 | ## License
598 | MIT
599 |       `);
600 | 
601 |       const result = await validateReadmeChecklist(
602 |         ValidateReadmeChecklistSchema.parse({ readmePath: readme }),
603 |       );
604 | 
605 |       expect(result.overallScore).toBeGreaterThan(0);
606 |       expect(result.totalItems).toBeGreaterThan(0);
607 |       expect(result.passedItems).toBeGreaterThan(0);
608 |       expect(result.categories).toHaveProperty("Essential Sections");
609 |       expect(result.categories).toHaveProperty("Community Health");
610 |       expect(result.categories).toHaveProperty("Visual Elements");
611 |       expect(result.categories).toHaveProperty("Content Quality");
612 |       expect(result.wordCount).toBeGreaterThan(0);
613 |       expect(result.estimatedReadTime).toBeGreaterThan(0);
614 |     });
615 | 
616 |     it("should calculate scores correctly", async () => {
617 |       const perfectReadme = await createTestReadme(`
618 | # Perfect Project
619 | > An amazing project that does everything right
620 | 
621 | [![Build Status](https://travis-ci.org/user/repo.svg)](https://travis-ci.org/user/repo)
622 | 
623 | ## TL;DR
624 | This project is perfect and demonstrates all best practices.
625 | 
626 | ## Quick Start
627 | \`\`\`bash
628 | npm install perfect-project
629 | \`\`\`
630 | 
631 | ## Usage
632 | \`\`\`javascript
633 | const perfect = require('perfect-project');
634 | perfect.doSomething();
635 | \`\`\`
636 | 
637 | ## Contributing
638 | See CONTRIBUTING.md for guidelines.
639 | 
640 | ## License
641 | MIT © Author
642 |       `);
643 | 
644 |       await createProjectFile("CONTRIBUTING.md", "Guidelines...");
645 |       await createProjectFile("LICENSE", "MIT License...");
646 | 
647 |       const result = await validateReadmeChecklist(
648 |         ValidateReadmeChecklistSchema.parse({
649 |           readmePath: perfectReadme,
650 |           projectPath: tempDir,
651 |         }),
652 |       );
653 | 
654 |       expect(result.overallScore).toBeGreaterThan(70);
655 |       expect(result.categories["Essential Sections"].score).toBeGreaterThan(80);
656 |     });
657 | 
658 |     it("should provide helpful recommendations", async () => {
659 |       const poorReadme = await createTestReadme(
660 |         "# Poor Project\n\nMinimal content",
661 |       );
662 | 
663 |       const result = await validateReadmeChecklist(
664 |         ValidateReadmeChecklistSchema.parse({ readmePath: poorReadme }),
665 |       );
666 | 
667 |       expect(result.recommendations.length).toBeGreaterThan(0);
668 |       expect(result.overallScore).toBeLessThan(50);
669 |     });
670 |   });
671 | 
672 |   describe("Output Formatting", () => {
673 |     it("should format console output correctly", async () => {
674 |       const readme = await createTestReadme("# Test\n\nContent");
675 |       const result = await validateReadmeChecklist(
676 |         ValidateReadmeChecklistSchema.parse({
677 |           readmePath: readme,
678 |           outputFormat: "console",
679 |         }),
680 |       );
681 | 
682 |       const formatted = validator.formatReport(result, "console");
683 | 
684 |       expect(formatted).toContain("📋 README Checklist Report");
685 |       expect(formatted).toContain("Overall Score:");
686 |       expect(formatted).toContain("Essential Sections");
687 |       expect(formatted).toContain("✅");
688 |       expect(formatted).toContain("❌");
689 |     });
690 | 
691 |     it("should format markdown output correctly", async () => {
692 |       const readme = await createTestReadme("# Test\n\nContent");
693 |       const result = await validateReadmeChecklist(
694 |         ValidateReadmeChecklistSchema.parse({
695 |           readmePath: readme,
696 |           outputFormat: "markdown",
697 |         }),
698 |       );
699 | 
700 |       const formatted = validator.formatReport(result, "markdown");
701 | 
702 |       expect(formatted).toContain("# README Checklist Report");
703 |       expect(formatted).toContain("## Overall Score:");
704 |       expect(formatted).toContain("### Essential Sections");
705 |       expect(formatted).toContain("- ✅");
706 |       expect(formatted).toContain("- ❌");
707 |     });
708 | 
709 |     it("should format JSON output correctly", async () => {
710 |       const readme = await createTestReadme("# Test\n\nContent");
711 |       const result = await validateReadmeChecklist(
712 |         ValidateReadmeChecklistSchema.parse({
713 |           readmePath: readme,
714 |           outputFormat: "json",
715 |         }),
716 |       );
717 | 
718 |       const formatted = validator.formatReport(result, "json");
719 |       const parsed = JSON.parse(formatted);
720 | 
721 |       expect(parsed).toHaveProperty("overallScore");
722 |       expect(parsed).toHaveProperty("categories");
723 |       expect(parsed).toHaveProperty("recommendations");
724 |     });
725 |   });
726 | 
727 |   describe("Error Handling", () => {
728 |     it("should handle non-existent README file", async () => {
729 |       const nonExistentPath = path.join(tempDir, "nonexistent.md");
730 | 
731 |       await expect(
732 |         validateReadmeChecklist(
733 |           ValidateReadmeChecklistSchema.parse({ readmePath: nonExistentPath }),
734 |         ),
735 |       ).rejects.toThrow();
736 |     });
737 | 
738 |     it("should handle invalid project path gracefully", async () => {
739 |       const readme = await createTestReadme("# Test\n\nContent");
740 | 
741 |       const result = await validateReadmeChecklist(
742 |         ValidateReadmeChecklistSchema.parse({
743 |           readmePath: readme,
744 |           projectPath: "/invalid/path",
745 |         }),
746 |       );
747 | 
748 |       // Should still work, just without project file context
749 |       expect(result.overallScore).toBeGreaterThan(0);
750 |     });
751 | 
752 |     it("should handle empty README file", async () => {
753 |       const emptyReadme = await createTestReadme("", "empty-README.md");
754 | 
755 |       const result = await validateReadmeChecklist(
756 |         ValidateReadmeChecklistSchema.parse({ readmePath: emptyReadme }),
757 |       );
758 | 
759 |       // Empty README should pass length test (0 words <= 300) and external links test (no links to fail)
760 |       // but fail most other tests, resulting in a low overall score
761 |       expect(result.overallScore).toBeLessThan(20); // Very low score due to missing content
762 |       expect(result.passedItems).toBe(2); // Only length and external-links should pass
763 |       expect(result.failedItems).toBe(15); // Most checks should fail
764 |     });
765 |   });
766 | 
767 |   describe("Suggestions Generation", () => {
768 |     it("should provide specific suggestions for failed checks", async () => {
769 |       const incompleteReadme = await createTestReadme(
770 |         "# Project\n\nMinimal content",
771 |       );
772 | 
773 |       const result = await validateReadmeChecklist(
774 |         ValidateReadmeChecklistSchema.parse({ readmePath: incompleteReadme }),
775 |       );
776 | 
777 |       const failedChecks = Object.values(result.categories)
778 |         .flatMap((cat) => cat.results)
779 |         .filter((r) => !r.passed && r.suggestions);
780 | 
781 |       expect(failedChecks.length).toBeGreaterThan(0);
782 | 
783 |       for (const check of failedChecks) {
784 |         expect(check.suggestions).toBeDefined();
785 |         expect(check.suggestions!.length).toBeGreaterThan(0);
786 |       }
787 |     });
788 |   });
789 | });
790 | 
```

--------------------------------------------------------------------------------
/src/memory/contextual-retrieval.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Contextual Memory Retrieval System for DocuMCP
  3 |  * Implements Issue #49: Contextual Memory Retrieval
  4 |  *
  5 |  * Provides intelligent, context-aware memory retrieval using semantic similarity,
  6 |  * temporal relevance, and user intent analysis for enhanced recommendation accuracy.
  7 |  */
  8 | 
  9 | import { MemoryManager } from "./manager.js";
 10 | import { MemoryEntry } from "./storage.js";
 11 | import { KnowledgeGraph } from "./knowledge-graph.js";
 12 | 
 13 | export interface RetrievalContext {
 14 |   currentProject?: {
 15 |     path: string;
 16 |     language: string;
 17 |     framework?: string;
 18 |     domain?: string;
 19 |     size?: "small" | "medium" | "large";
 20 |   };
 21 |   userIntent?: {
 22 |     action: "analyze" | "recommend" | "deploy" | "troubleshoot" | "learn";
 23 |     urgency: "low" | "medium" | "high";
 24 |     experience: "novice" | "intermediate" | "expert";
 25 |   };
 26 |   sessionContext?: {
 27 |     recentActions: string[];
 28 |     focusAreas: string[];
 29 |     timeConstraints?: number; // minutes
 30 |   };
 31 |   temporalContext?: {
 32 |     timeRange?: { start: string; end: string };
 33 |     recency: "recent" | "all" | "historical";
 34 |     seasonality?: boolean;
 35 |   };
 36 | }
 37 | 
 38 | export interface SemanticEmbedding {
 39 |   vector: number[];
 40 |   metadata: {
 41 |     source: string;
 42 |     confidence: number;
 43 |     generatedAt: string;
 44 |   };
 45 | }
 46 | 
 47 | export interface ContextualMatch {
 48 |   memory: MemoryEntry;
 49 |   relevanceScore: number;
 50 |   contextualFactors: {
 51 |     semantic: number;
 52 |     temporal: number;
 53 |     structural: number;
 54 |     intentional: number;
 55 |   };
 56 |   reasoning: string[];
 57 |   confidence: number;
 58 | }
 59 | 
 60 | export interface RetrievalResult {
 61 |   matches: ContextualMatch[];
 62 |   metadata: {
 63 |     queryContext: RetrievalContext;
 64 |     totalCandidates: number;
 65 |     processingTime: number;
 66 |     fallbackUsed: boolean;
 67 |   };
 68 |   insights: {
 69 |     patterns: string[];
 70 |     recommendations: string[];
 71 |     gaps: string[];
 72 |   };
 73 | }
 74 | 
 75 | export class ContextualMemoryRetrieval {
 76 |   private memoryManager: MemoryManager;
 77 |   private knowledgeGraph: KnowledgeGraph;
 78 |   private embeddingCache: Map<string, SemanticEmbedding>;
 79 |   private readonly maxCacheSize = 1000;
 80 |   private readonly similarityThreshold = 0.6;
 81 | 
 82 |   constructor(memoryManager: MemoryManager, knowledgeGraph: KnowledgeGraph) {
 83 |     this.memoryManager = memoryManager;
 84 |     this.knowledgeGraph = knowledgeGraph;
 85 |     this.embeddingCache = new Map();
 86 |   }
 87 | 
 88 |   /**
 89 |    * Retrieve contextually relevant memories
 90 |    */
 91 |   async retrieve(
 92 |     query: string,
 93 |     context: RetrievalContext,
 94 |     options?: {
 95 |       maxResults?: number;
 96 |       minRelevance?: number;
 97 |       includeReasoning?: boolean;
 98 |     },
 99 |   ): Promise<RetrievalResult> {
100 |     const startTime = Date.now();
101 |     const maxResults = options?.maxResults || 10;
102 |     const minRelevance = options?.minRelevance || 0.3;
103 | 
104 |     // Get candidate memories based on basic filtering
105 |     const candidates = await this.getCandidateMemories(query, context);
106 | 
107 |     // Score and rank candidates
108 |     const scoredMatches = await this.scoreAndRankCandidates(
109 |       candidates,
110 |       query,
111 |       context,
112 |     );
113 | 
114 |     // Filter by relevance threshold
115 |     const relevantMatches = scoredMatches
116 |       .filter((match) => match.relevanceScore >= minRelevance)
117 |       .slice(0, maxResults);
118 | 
119 |     // Generate insights from matches
120 |     const insights = await this.generateInsights(relevantMatches, context);
121 | 
122 |     const processingTime = Date.now() - startTime;
123 | 
124 |     return {
125 |       matches: relevantMatches,
126 |       metadata: {
127 |         queryContext: context,
128 |         totalCandidates: candidates.length,
129 |         processingTime,
130 |         fallbackUsed: relevantMatches.length === 0 && candidates.length > 0,
131 |       },
132 |       insights,
133 |     };
134 |   }
135 | 
136 |   /**
137 |    * Get candidate memories using multiple retrieval strategies
138 |    */
139 |   private async getCandidateMemories(
140 |     query: string,
141 |     context: RetrievalContext,
142 |   ): Promise<MemoryEntry[]> {
143 |     const candidates = new Map<string, MemoryEntry>();
144 | 
145 |     // Strategy 1: Text-based search
146 |     const textMatches = await this.memoryManager.search(query, {
147 |       sortBy: "timestamp",
148 |     });
149 |     textMatches.forEach((memory) => candidates.set(memory.id, memory));
150 | 
151 |     // Strategy 2: Context-based filtering
152 |     if (context.currentProject) {
153 |       const contextMatches = await this.getContextBasedCandidates(
154 |         context.currentProject,
155 |       );
156 |       contextMatches.forEach((memory) => candidates.set(memory.id, memory));
157 |     }
158 | 
159 |     // Strategy 3: Intent-based retrieval
160 |     if (context.userIntent) {
161 |       const intentMatches = await this.getIntentBasedCandidates(
162 |         context.userIntent,
163 |       );
164 |       intentMatches.forEach((memory) => candidates.set(memory.id, memory));
165 |     }
166 | 
167 |     // Strategy 4: Temporal filtering
168 |     if (context.temporalContext) {
169 |       const temporalMatches = await this.getTemporalCandidates(
170 |         context.temporalContext,
171 |       );
172 |       temporalMatches.forEach((memory) => candidates.set(memory.id, memory));
173 |     }
174 | 
175 |     // Strategy 5: Knowledge graph traversal
176 |     const graphMatches = await this.getGraphBasedCandidates(query, context);
177 |     graphMatches.forEach((memory) => candidates.set(memory.id, memory));
178 | 
179 |     return Array.from(candidates.values());
180 |   }
181 | 
182 |   /**
183 |    * Get candidates based on current project context
184 |    */
185 |   private async getContextBasedCandidates(
186 |     project: NonNullable<RetrievalContext["currentProject"]>,
187 |   ): Promise<MemoryEntry[]> {
188 |     const searchCriteria = [];
189 | 
190 |     // Language-based search
191 |     searchCriteria.push(
192 |       this.memoryManager
193 |         .search("", { sortBy: "timestamp" })
194 |         .then((memories) =>
195 |           memories.filter(
196 |             (m) =>
197 |               m.data.language?.primary === project.language ||
198 |               m.metadata.tags?.includes(project.language),
199 |           ),
200 |         ),
201 |     );
202 | 
203 |     // Framework-based search
204 |     if (project.framework) {
205 |       searchCriteria.push(
206 |         this.memoryManager
207 |           .search("", { sortBy: "timestamp" })
208 |           .then((memories) =>
209 |             memories.filter(
210 |               (m) =>
211 |                 m.data.framework?.name === project.framework ||
212 |                 (project.framework &&
213 |                   m.metadata.tags?.includes(project.framework)),
214 |             ),
215 |           ),
216 |       );
217 |     }
218 | 
219 |     // Project size similarity
220 |     if (project.size) {
221 |       searchCriteria.push(
222 |         this.memoryManager
223 |           .search("", { sortBy: "timestamp" })
224 |           .then((memories) =>
225 |             memories.filter(
226 |               (m) =>
227 |                 this.categorizeProjectSize(m.data.stats?.files || 0) ===
228 |                 project.size,
229 |             ),
230 |           ),
231 |       );
232 |     }
233 | 
234 |     const results = await Promise.all(searchCriteria);
235 |     const allMatches = results.flat();
236 | 
237 |     // Deduplicate
238 |     const unique = new Map<string, MemoryEntry>();
239 |     allMatches.forEach((memory) => unique.set(memory.id, memory));
240 | 
241 |     return Array.from(unique.values());
242 |   }
243 | 
244 |   /**
245 |    * Get candidates based on user intent
246 |    */
247 |   private async getIntentBasedCandidates(
248 |     intent: NonNullable<RetrievalContext["userIntent"]>,
249 |   ): Promise<MemoryEntry[]> {
250 |     const intentTypeMap = {
251 |       analyze: ["analysis", "evaluation", "assessment"],
252 |       recommend: ["recommendation", "suggestion", "advice"],
253 |       deploy: ["deployment", "publish", "release"],
254 |       troubleshoot: ["error", "issue", "problem", "debug"],
255 |       learn: ["tutorial", "guide", "example", "pattern"],
256 |     };
257 | 
258 |     const searchTerms = intentTypeMap[intent.action] || [intent.action];
259 |     const searches = searchTerms.map((term) =>
260 |       this.memoryManager.search(term, { sortBy: "timestamp" }),
261 |     );
262 | 
263 |     const results = await Promise.all(searches);
264 |     const allMatches = results.flat();
265 | 
266 |     // Filter by experience level
267 |     return allMatches.filter((memory) => {
268 |       if (intent.experience === "novice") {
269 |         return (
270 |           !memory.metadata.tags?.includes("advanced") &&
271 |           !memory.metadata.tags?.includes("expert")
272 |         );
273 |       } else if (intent.experience === "expert") {
274 |         return (
275 |           memory.metadata.tags?.includes("advanced") ||
276 |           memory.metadata.tags?.includes("expert") ||
277 |           memory.data.complexity === "complex"
278 |         );
279 |       }
280 |       return true; // intermediate gets all
281 |     });
282 |   }
283 | 
284 |   /**
285 |    * Get candidates based on temporal context
286 |    */
287 |   private async getTemporalCandidates(
288 |     temporal: NonNullable<RetrievalContext["temporalContext"]>,
289 |   ): Promise<MemoryEntry[]> {
290 |     const searchOptions: any = { sortBy: "timestamp" };
291 | 
292 |     if (temporal.timeRange) {
293 |       // Use memory manager's built-in time filtering
294 |       const allMemories = await this.memoryManager.search("", searchOptions);
295 | 
296 |       return allMemories.filter((memory) => {
297 |         const memoryTime = new Date(memory.timestamp);
298 |         const start = new Date(temporal.timeRange!.start);
299 |         const end = new Date(temporal.timeRange!.end);
300 |         return memoryTime >= start && memoryTime <= end;
301 |       });
302 |     }
303 | 
304 |     if (temporal.recency === "recent") {
305 |       const cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); // Last 7 days
306 |       const allMemories = await this.memoryManager.search("", searchOptions);
307 | 
308 |       return allMemories.filter(
309 |         (memory) => new Date(memory.timestamp) > cutoff,
310 |       );
311 |     }
312 | 
313 |     if (temporal.recency === "historical") {
314 |       const cutoff = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000); // Older than 90 days
315 |       const allMemories = await this.memoryManager.search("", searchOptions);
316 | 
317 |       return allMemories.filter(
318 |         (memory) => new Date(memory.timestamp) < cutoff,
319 |       );
320 |     }
321 | 
322 |     return this.memoryManager.search("", searchOptions);
323 |   }
324 | 
325 |   /**
326 |    * Get candidates using knowledge graph traversal
327 |    */
328 |   private async getGraphBasedCandidates(
329 |     query: string,
330 |     context: RetrievalContext,
331 |   ): Promise<MemoryEntry[]> {
332 |     if (!context.currentProject) return [];
333 | 
334 |     // Find relevant nodes in the knowledge graph
335 |     const graphQuery = {
336 |       nodeTypes: ["project", "technology"],
337 |       properties: context.currentProject.language
338 |         ? {
339 |             language: context.currentProject.language,
340 |           }
341 |         : undefined,
342 |       maxDepth: 2,
343 |     };
344 | 
345 |     const graphResult = this.knowledgeGraph.query(graphQuery);
346 |     const relevantNodeIds = graphResult.nodes.map((node) => node.id);
347 | 
348 |     // Find memories associated with these nodes
349 |     const memories: MemoryEntry[] = [];
350 |     const allMemories = await this.memoryManager.search("", {
351 |       sortBy: "timestamp",
352 |     });
353 | 
354 |     for (const memory of allMemories) {
355 |       const projectNodeId = `project:${memory.metadata.projectId}`;
356 |       const techNodeId = memory.metadata.ssg
357 |         ? `tech:${memory.metadata.ssg}`
358 |         : null;
359 | 
360 |       if (
361 |         relevantNodeIds.includes(projectNodeId) ||
362 |         (techNodeId && relevantNodeIds.includes(techNodeId))
363 |       ) {
364 |         memories.push(memory);
365 |       }
366 |     }
367 | 
368 |     return memories;
369 |   }
370 | 
371 |   /**
372 |    * Score and rank candidates based on contextual relevance
373 |    */
374 |   private async scoreAndRankCandidates(
375 |     candidates: MemoryEntry[],
376 |     query: string,
377 |     context: RetrievalContext,
378 |   ): Promise<ContextualMatch[]> {
379 |     const matches: ContextualMatch[] = [];
380 | 
381 |     for (const memory of candidates) {
382 |       const contextualFactors = await this.calculateContextualFactors(
383 |         memory,
384 |         query,
385 |         context,
386 |       );
387 | 
388 |       const relevanceScore = this.calculateOverallRelevance(contextualFactors);
389 |       const reasoning = this.generateReasoning(
390 |         memory,
391 |         contextualFactors,
392 |         context,
393 |       );
394 |       const confidence = this.calculateConfidence(contextualFactors, memory);
395 | 
396 |       matches.push({
397 |         memory,
398 |         relevanceScore,
399 |         contextualFactors,
400 |         reasoning,
401 |         confidence,
402 |       });
403 |     }
404 | 
405 |     // Sort by relevance score (descending)
406 |     return matches.sort((a, b) => b.relevanceScore - a.relevanceScore);
407 |   }
408 | 
409 |   /**
410 |    * Calculate contextual factors for scoring
411 |    */
412 |   private async calculateContextualFactors(
413 |     memory: MemoryEntry,
414 |     query: string,
415 |     context: RetrievalContext,
416 |   ): Promise<ContextualMatch["contextualFactors"]> {
417 |     const semantic = await this.calculateSemanticSimilarity(memory, query);
418 |     const temporal = await this.calculateTemporalRelevance(memory, context);
419 |     const structural = this.calculateStructuralRelevance(memory, context);
420 |     const intentional = this.calculateIntentionalRelevance(memory, context);
421 | 
422 |     return { semantic, temporal, structural, intentional };
423 |   }
424 | 
425 |   /**
426 |    * Calculate semantic similarity using simple text matching
427 |    * (In a production system, this would use embeddings)
428 |    */
429 |   private async calculateSemanticSimilarity(
430 |     memory: MemoryEntry,
431 |     query: string,
432 |   ): Promise<number> {
433 |     const queryTerms = query.toLowerCase().split(/\s+/);
434 |     const memoryText = JSON.stringify(memory.data).toLowerCase();
435 |     const metadataText = JSON.stringify(memory.metadata).toLowerCase();
436 | 
437 |     let matches = 0;
438 |     for (const term of queryTerms) {
439 |       if (memoryText.includes(term) || metadataText.includes(term)) {
440 |         matches++;
441 |       }
442 |     }
443 | 
444 |     return queryTerms.length > 0 ? matches / queryTerms.length : 0;
445 |   }
446 | 
447 |   /**
448 |    * Calculate temporal relevance based on recency and context
449 |    */
450 |   private calculateTemporalRelevance(
451 |     memory: MemoryEntry,
452 |     context: RetrievalContext,
453 |   ): Promise<number> {
454 |     const memoryDate = new Date(memory.timestamp);
455 |     const now = new Date();
456 |     const daysSince =
457 |       (now.getTime() - memoryDate.getTime()) / (1000 * 60 * 60 * 24);
458 | 
459 |     // Base score decreases with age
460 |     let score = Math.exp(-daysSince / 30); // Half-life of 30 days
461 | 
462 |     // Boost for explicit temporal preferences
463 |     if (context.temporalContext?.recency === "recent" && daysSince <= 7) {
464 |       score *= 1.5;
465 |     } else if (
466 |       context.temporalContext?.recency === "historical" &&
467 |       daysSince >= 90
468 |     ) {
469 |       score *= 1.3;
470 |     }
471 | 
472 |     // Consider time constraints
473 |     if (context.sessionContext?.timeConstraints) {
474 |       const urgencyMultiplier =
475 |         context.userIntent?.urgency === "high" ? 1.2 : 1.0;
476 |       score *= urgencyMultiplier;
477 |     }
478 | 
479 |     return Promise.resolve(Math.min(score, 1.0));
480 |   }
481 | 
482 |   /**
483 |    * Calculate structural relevance based on project similarity
484 |    */
485 |   private calculateStructuralRelevance(
486 |     memory: MemoryEntry,
487 |     context: RetrievalContext,
488 |   ): number {
489 |     if (!context.currentProject) return 0.5; // Neutral when no project context
490 | 
491 |     let score = 0;
492 |     let factors = 0;
493 | 
494 |     // Language match
495 |     if (memory.data.language?.primary === context.currentProject.language) {
496 |       score += 0.4;
497 |     }
498 |     factors++;
499 | 
500 |     // Framework match
501 |     if (
502 |       context.currentProject.framework &&
503 |       memory.data.framework?.name === context.currentProject.framework
504 |     ) {
505 |       score += 0.3;
506 |     }
507 |     factors++;
508 | 
509 |     // Size similarity
510 |     if (context.currentProject.size) {
511 |       const memorySize = this.categorizeProjectSize(
512 |         memory.data.stats?.files || 0,
513 |       );
514 |       if (memorySize === context.currentProject.size) {
515 |         score += 0.2;
516 |       }
517 |     }
518 |     factors++;
519 | 
520 |     // Type relevance
521 |     if (
522 |       memory.type === "analysis" &&
523 |       context.userIntent?.action === "analyze"
524 |     ) {
525 |       score += 0.1;
526 |     } else if (
527 |       memory.type === "recommendation" &&
528 |       context.userIntent?.action === "recommend"
529 |     ) {
530 |       score += 0.1;
531 |     }
532 |     factors++;
533 | 
534 |     return factors > 0 ? score / factors : 0;
535 |   }
536 | 
537 |   /**
538 |    * Calculate intentional relevance based on user intent
539 |    */
540 |   private calculateIntentionalRelevance(
541 |     memory: MemoryEntry,
542 |     context: RetrievalContext,
543 |   ): number {
544 |     if (!context.userIntent) return 0.5; // Neutral when no intent
545 | 
546 |     let score = 0;
547 | 
548 |     // Action alignment
549 |     const actionTypeMap = {
550 |       analyze: ["analysis", "evaluation"],
551 |       recommend: ["recommendation"],
552 |       deploy: ["deployment"],
553 |       troubleshoot: ["deployment", "configuration"],
554 |       learn: ["analysis", "recommendation"],
555 |     };
556 | 
557 |     const relevantTypes = actionTypeMap[context.userIntent.action] || [];
558 |     if (relevantTypes.includes(memory.type)) {
559 |       score += 0.5;
560 |     }
561 | 
562 |     // Experience level alignment
563 |     if (context.userIntent.experience === "novice") {
564 |       // Prefer simpler, more successful cases
565 |       if (
566 |         memory.data.status === "success" ||
567 |         memory.data.complexity !== "complex"
568 |       ) {
569 |         score += 0.3;
570 |       }
571 |     } else if (context.userIntent.experience === "expert") {
572 |       // Prefer complex or edge cases
573 |       if (
574 |         memory.data.complexity === "complex" ||
575 |         memory.metadata.tags?.includes("advanced")
576 |       ) {
577 |         score += 0.3;
578 |       }
579 |     }
580 | 
581 |     // Urgency consideration
582 |     if (context.userIntent.urgency === "high") {
583 |       // Prefer recent, successful cases
584 |       const daysSince =
585 |         (Date.now() - new Date(memory.timestamp).getTime()) /
586 |         (1000 * 60 * 60 * 24);
587 |       if (daysSince <= 7 && memory.data.status === "success") {
588 |         score += 0.2;
589 |       }
590 |     }
591 | 
592 |     return Math.min(score, 1.0);
593 |   }
594 | 
595 |   /**
596 |    * Calculate overall relevance score
597 |    */
598 |   private calculateOverallRelevance(
599 |     factors: ContextualMatch["contextualFactors"],
600 |   ): number {
601 |     // Weighted combination of factors
602 |     const weights = {
603 |       semantic: 0.3,
604 |       temporal: 0.2,
605 |       structural: 0.3,
606 |       intentional: 0.2,
607 |     };
608 | 
609 |     return (
610 |       factors.semantic * weights.semantic +
611 |       factors.temporal * weights.temporal +
612 |       factors.structural * weights.structural +
613 |       factors.intentional * weights.intentional
614 |     );
615 |   }
616 | 
617 |   /**
618 |    * Generate reasoning for why a memory was selected
619 |    */
620 |   private generateReasoning(
621 |     memory: MemoryEntry,
622 |     factors: ContextualMatch["contextualFactors"],
623 |     context: RetrievalContext,
624 |   ): string[] {
625 |     const reasoning: string[] = [];
626 | 
627 |     if (factors.semantic > 0.7) {
628 |       reasoning.push("High semantic similarity to query");
629 |     }
630 | 
631 |     if (factors.temporal > 0.8) {
632 |       reasoning.push("Recently relevant information");
633 |     }
634 | 
635 |     if (factors.structural > 0.6) {
636 |       reasoning.push(
637 |         `Similar project structure (${
638 |           memory.data.language?.primary || "unknown"
639 |         })`,
640 |       );
641 |     }
642 | 
643 |     if (factors.intentional > 0.7) {
644 |       reasoning.push(
645 |         `Matches user intent for ${
646 |           context.userIntent?.action || "general"
647 |         } action`,
648 |       );
649 |     }
650 | 
651 |     if (
652 |       memory.data.status === "success" &&
653 |       context.userIntent?.urgency === "high"
654 |     ) {
655 |       reasoning.push("Proven successful approach for urgent needs");
656 |     }
657 | 
658 |     if (memory.metadata.ssg && context.currentProject?.framework) {
659 |       reasoning.push(
660 |         `Experience with ${memory.metadata.ssg} for similar projects`,
661 |       );
662 |     }
663 | 
664 |     return reasoning.length > 0 ? reasoning : ["General relevance to query"];
665 |   }
666 | 
667 |   /**
668 |    * Calculate confidence in the match
669 |    */
670 |   private calculateConfidence(
671 |     factors: ContextualMatch["contextualFactors"],
672 |     memory: MemoryEntry,
673 |   ): number {
674 |     let confidence = (factors.semantic + factors.structural) / 2;
675 | 
676 |     // Boost confidence for successful outcomes
677 |     if (memory.data.status === "success") {
678 |       confidence *= 1.2;
679 |     }
680 | 
681 |     // Boost confidence for recent data
682 |     const daysSince =
683 |       (Date.now() - new Date(memory.timestamp).getTime()) /
684 |       (1000 * 60 * 60 * 24);
685 |     if (daysSince <= 30) {
686 |       confidence *= 1.1;
687 |     }
688 | 
689 |     // Boost confidence for rich metadata
690 |     if (memory.metadata.tags && memory.metadata.tags.length > 2) {
691 |       confidence *= 1.05;
692 |     }
693 | 
694 |     return Math.min(confidence, 1.0);
695 |   }
696 | 
697 |   /**
698 |    * Generate insights from retrieved matches
699 |    */
700 |   private async generateInsights(
701 |     matches: ContextualMatch[],
702 |     context: RetrievalContext,
703 |   ): Promise<RetrievalResult["insights"]> {
704 |     const patterns: string[] = [];
705 |     const recommendations: string[] = [];
706 |     const gaps: string[] = [];
707 | 
708 |     if (matches.length === 0) {
709 |       gaps.push("No relevant memories found for current context");
710 |       recommendations.push(
711 |         "Consider expanding search criteria or building more experience",
712 |       );
713 |       return { patterns, recommendations, gaps };
714 |     }
715 | 
716 |     // Analyze patterns in successful matches
717 |     const successfulMatches = matches.filter(
718 |       (m) => m.memory.data.status === "success" && m.relevanceScore > 0.6,
719 |     );
720 | 
721 |     if (successfulMatches.length >= 2) {
722 |       // Find common SSGs
723 |       const ssgs = new Map<string, number>();
724 |       successfulMatches.forEach((match) => {
725 |         if (match.memory.metadata.ssg) {
726 |           ssgs.set(
727 |             match.memory.metadata.ssg,
728 |             (ssgs.get(match.memory.metadata.ssg) || 0) + 1,
729 |           );
730 |         }
731 |       });
732 | 
733 |       if (ssgs.size > 0) {
734 |         const topSSG = Array.from(ssgs.entries()).sort(
735 |           ([, a], [, b]) => b - a,
736 |         )[0];
737 |         patterns.push(
738 |           `${topSSG[0]} appears in ${topSSG[1]} successful similar projects`,
739 |         );
740 |         recommendations.push(
741 |           `Consider ${topSSG[0]} based on successful precedents`,
742 |         );
743 |       }
744 | 
745 |       // Find common success factors
746 |       const commonFactors = this.findCommonSuccessFactors(successfulMatches);
747 |       patterns.push(...commonFactors);
748 |     }
749 | 
750 |     // Identify gaps
751 |     if (
752 |       context.userIntent?.action === "deploy" &&
753 |       matches.filter((m) => m.memory.type === "deployment").length === 0
754 |     ) {
755 |       gaps.push("Limited deployment experience for similar projects");
756 |       recommendations.push(
757 |         "Proceed cautiously with deployment and document the process",
758 |       );
759 |     }
760 | 
761 |     if (
762 |       context.userIntent?.experience === "novice" &&
763 |       matches.every((m) => m.confidence < 0.7)
764 |     ) {
765 |       gaps.push("Limited beginner-friendly resources for this context");
766 |       recommendations.push(
767 |         "Consider consulting documentation or seeking expert guidance",
768 |       );
769 |     }
770 | 
771 |     return { patterns, recommendations, gaps };
772 |   }
773 | 
774 |   /**
775 |    * Find common success factors across matches
776 |    */
777 |   private findCommonSuccessFactors(matches: ContextualMatch[]): string[] {
778 |     const factors: string[] = [];
779 | 
780 |     const hasTests = matches.filter(
781 |       (m) => m.memory.data.testing?.hasTests,
782 |     ).length;
783 |     if (hasTests / matches.length > 0.7) {
784 |       factors.push("Projects with testing have higher success rates");
785 |     }
786 | 
787 |     const hasCI = matches.filter((m) => m.memory.data.ci?.hasCI).length;
788 |     if (hasCI / matches.length > 0.6) {
789 |       factors.push("CI/CD adoption correlates with deployment success");
790 |     }
791 | 
792 |     const simpleProjects = matches.filter(
793 |       (m) => m.memory.data.complexity !== "complex",
794 |     ).length;
795 |     if (simpleProjects / matches.length > 0.8) {
796 |       factors.push("Simpler project structures show more reliable outcomes");
797 |     }
798 | 
799 |     return factors;
800 |   }
801 | 
802 |   /**
803 |    * Categorize project size for comparison
804 |    */
805 |   private categorizeProjectSize(
806 |     fileCount: number,
807 |   ): "small" | "medium" | "large" {
808 |     if (fileCount < 50) return "small";
809 |     if (fileCount < 200) return "medium";
810 |     return "large";
811 |   }
812 | 
813 |   /**
814 |    * Get contextual suggestions for improving retrieval
815 |    */
816 |   async getSuggestions(context: RetrievalContext): Promise<{
817 |     queryImprovements: string[];
818 |     contextEnhancements: string[];
819 |     learningOpportunities: string[];
820 |   }> {
821 |     const suggestions = {
822 |       queryImprovements: [] as string[],
823 |       contextEnhancements: [] as string[],
824 |       learningOpportunities: [] as string[],
825 |     };
826 | 
827 |     // Analyze current context completeness
828 |     if (!context.currentProject) {
829 |       suggestions.contextEnhancements.push(
830 |         "Provide current project information for better matches",
831 |       );
832 |     }
833 | 
834 |     if (!context.userIntent) {
835 |       suggestions.contextEnhancements.push(
836 |         "Specify your intent (analyze, recommend, deploy, etc.) for targeted results",
837 |       );
838 |     }
839 | 
840 |     if (!context.temporalContext) {
841 |       suggestions.contextEnhancements.push(
842 |         "Set temporal preferences (recent vs. historical) for relevance",
843 |       );
844 |     }
845 | 
846 |     // Analyze retrieval patterns
847 |     const recentSearches = await this.memoryManager.search("search", {
848 |       sortBy: "timestamp",
849 |     });
850 |     if (recentSearches.length < 5) {
851 |       suggestions.learningOpportunities.push(
852 |         "System will improve with more usage and data",
853 |       );
854 |     }
855 | 
856 |     // Check for data gaps
857 |     if (context.currentProject?.language) {
858 |       const languageMemories = await this.memoryManager.search(
859 |         context.currentProject.language,
860 |       );
861 |       if (languageMemories.length < 3) {
862 |         suggestions.learningOpportunities.push(
863 |           `More experience needed with ${context.currentProject.language} projects`,
864 |         );
865 |       }
866 |     }
867 | 
868 |     return suggestions;
869 |   }
870 | 
871 |   /**
872 |    * Clear embedding cache
873 |    */
874 |   clearCache(): void {
875 |     this.embeddingCache.clear();
876 |   }
877 | 
878 |   /**
879 |    * Get retrieval statistics
880 |    */
881 |   getStatistics(): {
882 |     cacheSize: number;
883 |     cacheHitRate: number;
884 |     averageRetrievalTime: number;
885 |     commonContextTypes: Record<string, number>;
886 |   } {
887 |     // This would track actual usage statistics in a real implementation
888 |     return {
889 |       cacheSize: this.embeddingCache.size,
890 |       cacheHitRate: 0.85, // Placeholder
891 |       averageRetrievalTime: 150, // ms
892 |       commonContextTypes: {
893 |         project_analysis: 45,
894 |         ssg_recommendation: 38,
895 |         deployment_troubleshooting: 12,
896 |         learning_assistance: 5,
897 |       },
898 |     };
899 |   }
900 | }
901 | 
902 | export default ContextualMemoryRetrieval;
903 | 
```

--------------------------------------------------------------------------------
/docs/api/index.html:
--------------------------------------------------------------------------------

```html
  1 | <!DOCTYPE html><html class="default" lang="en" data-base="./"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>DocuMCP API Documentation - v0.4.1</title><meta name="description" content="Documentation for DocuMCP API Documentation"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="assets/style.css"/><link rel="stylesheet" href="assets/highlight.css"/><script defer src="assets/main.js"></script><script async src="assets/icons.js" id="tsd-icons-script"></script><script async src="assets/search.js" id="tsd-search-script"></script><script async src="assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="index.html" class="title">DocuMCP API Documentation - v0.4.1</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><h1>DocuMCP API Documentation - v0.4.1</h1></div><div class="tsd-panel tsd-typography"><h1 id="documcp---intelligent-documentation-deployment-mcp-server" class="tsd-anchor-link">DocuMCP - Intelligent Documentation Deployment MCP Server<a href="#documcp---intelligent-documentation-deployment-mcp-server" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h1><p><a href="https://github.com/tosin2013/documcp/actions/workflows/ci.yml"><img src="https://github.com/tosin2013/documcp/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
  2 | <a href="https://github.com/tosin2013/documcp/actions/workflows/codeql.yml"><img src="https://github.com/tosin2013/documcp/actions/workflows/codeql.yml/badge.svg" alt="CodeQL"></a>
  3 | <a href="https://codecov.io/gh/tosin2013/documcp"><img src="https://codecov.io/gh/tosin2013/documcp/branch/main/graph/badge.svg" alt="Coverage"></a>
  4 | <a href="https://badge.fury.io/js/documcp"><img src="https://badge.fury.io/js/documcp.svg" alt="npm version"></a></p>
  5 | <p>DocuMCP is an intelligent Model Context Protocol (MCP) server that revolutionizes documentation deployment for open-source projects. It provides deep repository analysis, intelligent static site generator recommendations, and automated GitHub Pages deployment workflows.</p>
  6 | <h2 id="tldr" class="tsd-anchor-link">TL;DR<a href="#tldr" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><p>DocuMCP analyzes your repository, recommends the perfect static site generator (Jekyll, Hugo, Docusaurus, MkDocs, or Eleventy), creates professional documentation structure following Diataxis principles, and deploys it automatically to GitHub Pages. Just say &quot;analyze my repository and deploy documentation&quot; to get started.</p>
  7 | <h2 id="features" class="tsd-anchor-link">Features<a href="#features" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><h3 id="core-capabilities" class="tsd-anchor-link">Core Capabilities<a href="#core-capabilities" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><ul>
  8 | <li>🔍 <strong>Repository Analysis</strong>: Deep multi-layered analysis of project structure, dependencies, and documentation needs</li>
  9 | <li>🎯 <strong>SSG Recommendations</strong>: Data-driven recommendations for Jekyll, Hugo, Docusaurus, MkDocs, or Eleventy</li>
 10 | <li>📚 <strong>Diataxis Framework</strong>: Automatic creation of well-structured documentation following proven principles</li>
 11 | <li>🚀 <strong>GitHub Pages Deployment</strong>: Automated workflow generation with SSG-specific optimizations</li>
 12 | <li>✅ <strong>Deployment Verification</strong>: Comprehensive checks and troubleshooting for successful deployments</li>
 13 | </ul>
 14 | <h3 id="intelligence--learning-phase-2" class="tsd-anchor-link">Intelligence &amp; Learning (Phase 2)<a href="#intelligence--learning-phase-2" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><ul>
 15 | <li>🧠 <strong>Historical Intelligence</strong>: Learns from past deployments to improve recommendations</li>
 16 | <li>👤 <strong>User Preferences</strong>: Personalized recommendations based on your preferences and patterns</li>
 17 | <li>📊 <strong>Deployment Analytics</strong>: Comprehensive insights into deployment patterns and success rates</li>
 18 | <li>🎯 <strong>Smart Scoring</strong>: Intelligent SSG scoring based on success rates from similar projects</li>
 19 | <li>📈 <strong>Trend Analysis</strong>: Identifies deployment trends and provides health scores</li>
 20 | </ul>
 21 | <h2 id="requirements" class="tsd-anchor-link">Requirements<a href="#requirements" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><ul>
 22 | <li><strong>Node.js</strong>: 20.0.0 or higher</li>
 23 | <li><strong>npm</strong>: Latest stable version</li>
 24 | </ul>
 25 | <h2 id="installation" class="tsd-anchor-link">Installation<a href="#installation" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><pre><code class="bash"><span class="hl-0"># Clone the repository</span><br/><span class="hl-1">git</span><span class="hl-2"> </span><span class="hl-3">clone</span><span class="hl-2"> </span><span class="hl-3">https://github.com/tosin2013/documcp.git</span><br/><span class="hl-1">cd</span><span class="hl-2"> </span><span class="hl-3">documcp</span><br/><br/><span class="hl-0"># Install dependencies</span><br/><span class="hl-1">npm</span><span class="hl-2"> </span><span class="hl-3">install</span><br/><br/><span class="hl-0"># Build the project</span><br/><span class="hl-1">npm</span><span class="hl-2"> </span><span class="hl-3">run</span><span class="hl-2"> </span><span class="hl-3">build</span>
 26 | </code><button type="button">Copy</button></pre>
 27 | 
 28 | <h2 id="mcp-client-setup" class="tsd-anchor-link">MCP Client Setup<a href="#mcp-client-setup" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><p>DocuMCP works with various MCP-enabled clients. Here's how to configure it:</p>
 29 | <h3 id="claude-desktop" class="tsd-anchor-link">Claude Desktop<a href="#claude-desktop" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><ol>
 30 | <li>
 31 | <p><strong>Locate Claude Desktop's configuration file</strong>:</p>
 32 | <ul>
 33 | <li><strong>macOS</strong>: <code>~/Library/Application Support/Claude/claude_desktop_config.json</code></li>
 34 | <li><strong>Windows</strong>: <code>%APPDATA%\Claude\claude_desktop_config.json</code></li>
 35 | <li><strong>Linux</strong>: <code>~/.config/claude/claude_desktop_config.json</code></li>
 36 | </ul>
 37 | </li>
 38 | <li>
 39 | <p><strong>Add documcp server configuration</strong>:</p>
 40 | <pre><code class="json"><span class="hl-2">{</span><br/><span class="hl-2">  </span><span class="hl-4">&quot;mcpServers&quot;</span><span class="hl-2">: {</span><br/><span class="hl-2">    </span><span class="hl-4">&quot;documcp&quot;</span><span class="hl-2">: {</span><br/><span class="hl-2">      </span><span class="hl-4">&quot;command&quot;</span><span class="hl-2">: </span><span class="hl-3">&quot;npx&quot;</span><span class="hl-2">,</span><br/><span class="hl-2">      </span><span class="hl-4">&quot;args&quot;</span><span class="hl-2">: [</span><span class="hl-3">&quot;documcp&quot;</span><span class="hl-2">]</span><br/><span class="hl-2">    }</span><br/><span class="hl-2">  }</span><br/><span class="hl-2">}</span>
 41 | </code><button type="button">Copy</button></pre>
 42 | 
 43 | </li>
 44 | <li>
 45 | <p><strong>Restart Claude Desktop</strong> to load the configuration.</p>
 46 | </li>
 47 | </ol>
 48 | <h3 id="vs-code-with-github-copilot" class="tsd-anchor-link">VS Code with GitHub Copilot<a href="#vs-code-with-github-copilot" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><ol>
 49 | <li><strong>Install MCP extension</strong> for VS Code</li>
 50 | <li><strong>Configure in VS Code settings.json</strong>:<pre><code class="json"><span class="hl-2">{</span><br/><span class="hl-2">  </span><span class="hl-4">&quot;mcp.servers&quot;</span><span class="hl-2">: {</span><br/><span class="hl-2">    </span><span class="hl-4">&quot;documcp&quot;</span><span class="hl-2">: {</span><br/><span class="hl-2">      </span><span class="hl-4">&quot;command&quot;</span><span class="hl-2">: </span><span class="hl-3">&quot;npx&quot;</span><span class="hl-2">,</span><br/><span class="hl-2">      </span><span class="hl-4">&quot;args&quot;</span><span class="hl-2">: [</span><span class="hl-3">&quot;documcp&quot;</span><span class="hl-2">]</span><br/><span class="hl-2">    }</span><br/><span class="hl-2">  }</span><br/><span class="hl-2">}</span>
 51 | </code><button type="button">Copy</button></pre>
 52 | 
 53 | </li>
 54 | </ol>
 55 | <h3 id="cursor-editor" class="tsd-anchor-link">Cursor Editor<a href="#cursor-editor" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><ol>
 56 | <li><strong>Configure in Cursor settings</strong>:<pre><code class="json"><span class="hl-2">{</span><br/><span class="hl-2">  </span><span class="hl-4">&quot;mcpServers&quot;</span><span class="hl-2">: {</span><br/><span class="hl-2">    </span><span class="hl-4">&quot;documcp&quot;</span><span class="hl-2">: {</span><br/><span class="hl-2">      </span><span class="hl-4">&quot;command&quot;</span><span class="hl-2">: </span><span class="hl-3">&quot;npx&quot;</span><span class="hl-2">,</span><br/><span class="hl-2">      </span><span class="hl-4">&quot;args&quot;</span><span class="hl-2">: [</span><span class="hl-3">&quot;documcp&quot;</span><span class="hl-2">]</span><br/><span class="hl-2">    }</span><br/><span class="hl-2">  }</span><br/><span class="hl-2">}</span>
 57 | </code><button type="button">Copy</button></pre>
 58 | 
 59 | </li>
 60 | </ol>
 61 | <h3 id="gemini-code-assist" class="tsd-anchor-link">Gemini Code Assist<a href="#gemini-code-assist" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><ol>
 62 | <li><strong>Check Gemini documentation</strong> for MCP server configuration</li>
 63 | <li><strong>Add similar configuration</strong> as above</li>
 64 | </ol>
 65 | <h3 id="troubleshooting" class="tsd-anchor-link">Troubleshooting<a href="#troubleshooting" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><ul>
 66 | <li>Ensure <code>npx</code> is available in your PATH</li>
 67 | <li>For global installations, use the full path:<pre><code class="json"><span class="hl-2">{</span><br/><span class="hl-2">  </span><span class="hl-4">&quot;command&quot;</span><span class="hl-2">: </span><span class="hl-3">&quot;node&quot;</span><span class="hl-2">,</span><br/><span class="hl-2">  </span><span class="hl-4">&quot;args&quot;</span><span class="hl-2">: [</span><span class="hl-3">&quot;/usr/local/lib/node_modules/documcp/dist/index.js&quot;</span><span class="hl-2">]</span><br/><span class="hl-2">}</span>
 68 | </code><button type="button">Copy</button></pre>
 69 | 
 70 | </li>
 71 | <li>Find installation path: <code>npm list -g documcp</code></li>
 72 | </ul>
 73 | <h2 id="quick-start" class="tsd-anchor-link">Quick Start<a href="#quick-start" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Once configured with your MCP client, just prompt DocuMCP with natural language:</p>
 74 | <pre><code class="bash"><span class="hl-0"># Complete workflow</span><br/><span class="hl-1">&quot;analyze my repository and deploy documentation to GitHub Pages&quot;</span><br/><br/><span class="hl-0"># Step by step</span><br/><span class="hl-1">&quot;analyze my repository for documentation needs&quot;</span><br/><span class="hl-1">&quot;recommend the best static site generator for my project&quot;</span><br/><span class="hl-1">&quot;set up documentation structure and deploy to GitHub Pages&quot;</span>
 75 | </code><button type="button">Copy</button></pre>
 76 | 
 77 | <p>DocuMCP provides 30+ tools including repository analysis, intelligent SSG recommendations, content generation, deployment automation with tracking, validation, user preference management, deployment analytics, and memory-enhanced insights. See the <a href="media/index.md">complete documentation</a> for detailed tool reference.</p>
 78 | <h2 id="key-tools" class="tsd-anchor-link">Key Tools<a href="#key-tools" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><h3 id="analysis--recommendations" class="tsd-anchor-link">Analysis &amp; Recommendations<a href="#analysis--recommendations" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><ul>
 79 | <li><code>analyze_repository</code> - Deep repository structure and dependency analysis</li>
 80 | <li><code>recommend_ssg</code> - Intelligent SSG recommendations with historical data and user preferences</li>
 81 | <li><code>detect_gaps</code> - Identify missing documentation sections</li>
 82 | </ul>
 83 | <h3 id="deployment--tracking" class="tsd-anchor-link">Deployment &amp; Tracking<a href="#deployment--tracking" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><ul>
 84 | <li><code>deploy_pages</code> - Automated GitHub Pages deployment with outcome tracking</li>
 85 | <li><code>verify_deployment</code> - Comprehensive deployment validation</li>
 86 | <li><code>analyze_deployments</code> - Analytics and insights from deployment history</li>
 87 | </ul>
 88 | <h3 id="user-preferences--learning" class="tsd-anchor-link">User Preferences &amp; Learning<a href="#user-preferences--learning" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><ul>
 89 | <li><code>manage_preferences</code> - Manage user preferences for personalized recommendations</li>
 90 | <li>View historical success rates and deployment patterns</li>
 91 | <li>Get recommendations based on similar projects' success</li>
 92 | </ul>
 93 | <h2 id="development" class="tsd-anchor-link">Development<a href="#development" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><pre><code class="bash"><span class="hl-0"># Run in development mode</span><br/><span class="hl-1">npm</span><span class="hl-2"> </span><span class="hl-3">run</span><span class="hl-2"> </span><span class="hl-3">dev</span><br/><br/><span class="hl-0"># Run tests</span><br/><span class="hl-1">npm</span><span class="hl-2"> </span><span class="hl-3">test</span><br/><br/><span class="hl-0"># Lint code</span><br/><span class="hl-1">npm</span><span class="hl-2"> </span><span class="hl-3">run</span><span class="hl-2"> </span><span class="hl-3">lint</span><br/><br/><span class="hl-0"># Type check</span><br/><span class="hl-1">npm</span><span class="hl-2"> </span><span class="hl-3">run</span><span class="hl-2"> </span><span class="hl-3">typecheck</span>
 94 | </code><button type="button">Copy</button></pre>
 95 | 
 96 | <h2 id="architecture" class="tsd-anchor-link">Architecture<a href="#architecture" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><p>DocuMCP follows a modular, stateless architecture:</p>
 97 | <ul>
 98 | <li><strong>TypeScript-based</strong> implementation using the official MCP SDK</li>
 99 | <li><strong>Stateless operation</strong> for consistency and reliability</li>
100 | <li><strong>Modular design</strong> with clear separation of concerns</li>
101 | <li><strong>Progressive complexity</strong> allowing users to start simple</li>
102 | </ul>
103 | <h2 id="documentation-structure-diataxis" class="tsd-anchor-link">Documentation Structure (Diataxis)<a href="#documentation-structure-diataxis" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><p>DocuMCP automatically creates documentation following the Diataxis framework:</p>
104 | <ul>
105 | <li><strong>Tutorials</strong>: Learning-oriented guides for newcomers</li>
106 | <li><strong>How-To Guides</strong>: Task-oriented recipes for specific goals</li>
107 | <li><strong>Reference</strong>: Information-oriented technical descriptions</li>
108 | <li><strong>Explanation</strong>: Understanding-oriented conceptual discussions</li>
109 | </ul>
110 | <h2 id="contributing" class="tsd-anchor-link">Contributing<a href="#contributing" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><p>We welcome contributions! Please see our <a href="media/CONTRIBUTING.md">Contributing Guide</a> for details.</p>
111 | <h3 id="first-time-contributors" class="tsd-anchor-link">First Time Contributors<a href="#first-time-contributors" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Look for issues labeled &quot;good first issue&quot; to get started with the project. We welcome contributions from developers of all experience levels.</p>
112 | <h3 id="reporting-issues" class="tsd-anchor-link">Reporting Issues<a href="#reporting-issues" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Please use our <a href="media/ISSUE_TEMPLATE">issue templates</a> when reporting bugs or requesting features.</p>
113 | <h2 id="code-of-conduct" class="tsd-anchor-link">Code of Conduct<a href="#code-of-conduct" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><p>This project adheres to the <a href="media/CODE_OF_CONDUCT.md">Contributor Covenant Code of Conduct</a>. By participating, you are expected to uphold this code.</p>
114 | <h2 id="security" class="tsd-anchor-link">Security<a href="#security" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Please see our <a href="media/SECURITY.md">Security Policy</a> for reporting vulnerabilities and security-related issues.</p>
115 | <h2 id="license" class="tsd-anchor-link">License<a href="#license" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><p>MIT License - see <a href="media/LICENSE">LICENSE</a> for details.</p>
116 | <h2 id="acknowledgments" class="tsd-anchor-link">Acknowledgments<a href="#acknowledgments" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><ul>
117 | <li>Built on the <a href="https://modelcontextprotocol.io/">Model Context Protocol</a></li>
118 | <li>Follows the <a href="https://diataxis.fr/">Diataxis Framework</a></li>
119 | <li>Inspired by the need for better documentation in open-source projects</li>
120 | </ul>
121 | </div></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg><h3>On This Page</h3></summary><div class="tsd-accordion-details"><a href="#documcp---intelligent-documentation-deployment-mcp-server"><span>Docu<wbr/>MCP -<wbr/> <wbr/>Intelligent <wbr/>Documentation <wbr/>Deployment <wbr/>MCP <wbr/>Server</span></a><ul><li><a href="#tldr"><span>TL;<wbr/>DR</span></a></li><li><a href="#features"><span>Features</span></a></li><li><ul><li><a href="#core-capabilities"><span>Core <wbr/>Capabilities</span></a></li><li><a href="#intelligence--learning-phase-2"><span>Intelligence &amp; <wbr/>Learning (<wbr/>Phase 2)</span></a></li></ul></li><li><a href="#requirements"><span>Requirements</span></a></li><li><a href="#installation"><span>Installation</span></a></li><li><a href="#mcp-client-setup"><span>MCP <wbr/>Client <wbr/>Setup</span></a></li><li><ul><li><a href="#claude-desktop"><span>Claude <wbr/>Desktop</span></a></li><li><a href="#vs-code-with-github-copilot"><span>VS <wbr/>Code with <wbr/>Git<wbr/>Hub <wbr/>Copilot</span></a></li><li><a href="#cursor-editor"><span>Cursor <wbr/>Editor</span></a></li><li><a href="#gemini-code-assist"><span>Gemini <wbr/>Code <wbr/>Assist</span></a></li><li><a href="#troubleshooting"><span>Troubleshooting</span></a></li></ul></li><li><a href="#quick-start"><span>Quick <wbr/>Start</span></a></li><li><a href="#key-tools"><span>Key <wbr/>Tools</span></a></li><li><ul><li><a href="#analysis--recommendations"><span>Analysis &amp; <wbr/>Recommendations</span></a></li><li><a href="#deployment--tracking"><span>Deployment &amp; <wbr/>Tracking</span></a></li><li><a href="#user-preferences--learning"><span>User <wbr/>Preferences &amp; <wbr/>Learning</span></a></li></ul></li><li><a href="#development"><span>Development</span></a></li><li><a href="#architecture"><span>Architecture</span></a></li><li><a href="#documentation-structure-diataxis"><span>Documentation <wbr/>Structure (<wbr/>Diataxis)</span></a></li><li><a href="#contributing"><span>Contributing</span></a></li><li><ul><li><a href="#first-time-contributors"><span>First <wbr/>Time <wbr/>Contributors</span></a></li><li><a href="#reporting-issues"><span>Reporting <wbr/>Issues</span></a></li></ul></li><li><a href="#code-of-conduct"><span>Code of <wbr/>Conduct</span></a></li><li><a href="#security"><span>Security</span></a></li><li><a href="#license"><span>License</span></a></li><li><a href="#acknowledgments"><span>Acknowledgments</span></a></li></ul></div></details></div><div class="site-menu"><nav class="tsd-navigation"><a href="modules.html">DocuMCP API Documentation - v0.4.1</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
122 | 
```

--------------------------------------------------------------------------------
/tests/performance/memory-load-testing.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Memory System Performance and Load Testing
  3 |  * Tests performance, scalability, and resource usage of memory system
  4 |  * Part of Issue #57 - Memory System Performance and Load Testing
  5 |  */
  6 | 
  7 | import { promises as fs } from "fs";
  8 | import path from "path";
  9 | import os from "os";
 10 | import { performance } from "perf_hooks";
 11 | import { MemoryManager } from "../../src/memory/manager.js";
 12 | import { EnhancedMemoryManager } from "../../src/memory/enhanced-manager.js";
 13 | import { IncrementalLearningSystem } from "../../src/memory/learning.js";
 14 | import { KnowledgeGraph } from "../../src/memory/knowledge-graph.js";
 15 | import {
 16 |   initializeMemory,
 17 |   rememberAnalysis,
 18 |   rememberRecommendation,
 19 |   getSimilarProjects,
 20 | } from "../../src/memory/integration.js";
 21 | 
 22 | interface PerformanceMetrics {
 23 |   operationTime: number;
 24 |   memoryUsed: number;
 25 |   operationsPerSecond: number;
 26 |   throughput: number;
 27 | }
 28 | 
 29 | describe("Memory System Performance and Load Testing", () => {
 30 |   let tempDir: string;
 31 |   let memoryManager: MemoryManager;
 32 | 
 33 |   beforeEach(async () => {
 34 |     tempDir = path.join(
 35 |       os.tmpdir(),
 36 |       `memory-performance-test-${Date.now()}-${Math.random()
 37 |         .toString(36)
 38 |         .substr(2, 9)}`,
 39 |     );
 40 |     await fs.mkdir(tempDir, { recursive: true });
 41 | 
 42 |     memoryManager = new MemoryManager(tempDir);
 43 |     await memoryManager.initialize();
 44 |   });
 45 | 
 46 |   afterEach(async () => {
 47 |     try {
 48 |       await fs.rm(tempDir, { recursive: true, force: true });
 49 |     } catch (error) {
 50 |       // Ignore cleanup errors
 51 |     }
 52 |   });
 53 | 
 54 |   function measurePerformance<T>(
 55 |     operation: () => Promise<T>,
 56 |   ): Promise<{ result: T; metrics: PerformanceMetrics }> {
 57 |     return new Promise(async (resolve) => {
 58 |       const startTime = performance.now();
 59 |       const startMemory = process.memoryUsage();
 60 | 
 61 |       const result = await operation();
 62 | 
 63 |       const endTime = performance.now();
 64 |       const endMemory = process.memoryUsage();
 65 | 
 66 |       const operationTime = endTime - startTime;
 67 |       const memoryUsed = endMemory.heapUsed - startMemory.heapUsed;
 68 | 
 69 |       resolve({
 70 |         result,
 71 |         metrics: {
 72 |           operationTime,
 73 |           memoryUsed,
 74 |           operationsPerSecond: 1000 / operationTime,
 75 |           throughput: 1000 / operationTime,
 76 |         },
 77 |       });
 78 |     });
 79 |   }
 80 | 
 81 |   describe("Basic Operations Performance", () => {
 82 |     test("should perform single memory operations efficiently", async () => {
 83 |       memoryManager.setContext({ projectId: "performance-single" });
 84 | 
 85 |       const testData = {
 86 |         projectId: "performance-single",
 87 |         language: { primary: "typescript" },
 88 |         framework: { name: "react" },
 89 |         stats: { files: 100, lines: 10000 },
 90 |       };
 91 | 
 92 |       const { metrics: createMetrics } = await measurePerformance(async () => {
 93 |         return await memoryManager.remember("analysis", testData);
 94 |       });
 95 | 
 96 |       const memoryId = (await memoryManager.remember("analysis", testData)).id;
 97 | 
 98 |       const { metrics: readMetrics } = await measurePerformance(async () => {
 99 |         return await memoryManager.recall(memoryId);
100 |       });
101 | 
102 |       const { metrics: searchMetrics } = await measurePerformance(async () => {
103 |         return await memoryManager.search({ projectId: "performance-single" });
104 |       });
105 | 
106 |       // Performance expectations (adjust based on system capabilities)
107 |       expect(createMetrics.operationTime).toBeLessThan(100); // 100ms
108 |       expect(readMetrics.operationTime).toBeLessThan(50); // 50ms
109 |       expect(searchMetrics.operationTime).toBeLessThan(100); // 100ms
110 | 
111 |       // Memory usage should be reasonable
112 |       expect(createMetrics.memoryUsed).toBeLessThan(10 * 1024 * 1024); // 10MB
113 |       expect(readMetrics.memoryUsed).toBeLessThan(1 * 1024 * 1024); // 1MB
114 | 
115 |       console.log("Single Operation Performance:");
116 |       console.log(
117 |         `Create: ${createMetrics.operationTime.toFixed(2)}ms, Memory: ${(
118 |           createMetrics.memoryUsed / 1024
119 |         ).toFixed(2)}KB`,
120 |       );
121 |       console.log(
122 |         `Read: ${readMetrics.operationTime.toFixed(2)}ms, Memory: ${(
123 |           readMetrics.memoryUsed / 1024
124 |         ).toFixed(2)}KB`,
125 |       );
126 |       console.log(
127 |         `Search: ${searchMetrics.operationTime.toFixed(2)}ms, Memory: ${(
128 |           searchMetrics.memoryUsed / 1024
129 |         ).toFixed(2)}KB`,
130 |       );
131 |     });
132 | 
133 |     test("should handle batch operations efficiently", async () => {
134 |       memoryManager.setContext({ projectId: "performance-batch" });
135 | 
136 |       const batchSize = 100;
137 |       const testData = Array.from({ length: batchSize }, (_, i) => ({
138 |         projectId: "performance-batch",
139 |         index: i,
140 |         language: { primary: i % 2 === 0 ? "typescript" : "javascript" },
141 |         framework: {
142 |           name: i % 3 === 0 ? "react" : i % 3 === 1 ? "vue" : "angular",
143 |         },
144 |         stats: { files: 10 + i, lines: 1000 + i * 100 },
145 |       }));
146 | 
147 |       const { metrics: batchCreateMetrics } = await measurePerformance(
148 |         async () => {
149 |           const promises = testData.map((data) =>
150 |             memoryManager.remember("analysis", data),
151 |           );
152 |           return await Promise.all(promises);
153 |         },
154 |       );
155 | 
156 |       const { metrics: batchSearchMetrics } = await measurePerformance(
157 |         async () => {
158 |           return await memoryManager.search({ projectId: "performance-batch" });
159 |         },
160 |       );
161 | 
162 |       // Batch operations should be efficient
163 |       expect(batchCreateMetrics.operationTime).toBeLessThan(5000); // 5 seconds for 100 items
164 |       expect(batchSearchMetrics.operationTime).toBeLessThan(1000); // 1 second to search 100 items
165 | 
166 |       // Calculate throughput
167 |       const createThroughput =
168 |         batchSize / (batchCreateMetrics.operationTime / 1000);
169 |       const searchThroughput =
170 |         batchSize / (batchSearchMetrics.operationTime / 1000);
171 | 
172 |       expect(createThroughput).toBeGreaterThan(20); // At least 20 ops/sec
173 |       expect(searchThroughput).toBeGreaterThan(100); // At least 100 searches/sec
174 | 
175 |       console.log("Batch Operation Performance:");
176 |       console.log(
177 |         `Create ${batchSize} items: ${batchCreateMetrics.operationTime.toFixed(
178 |           2,
179 |         )}ms (${createThroughput.toFixed(2)} ops/sec)`,
180 |       );
181 |       console.log(
182 |         `Search ${batchSize} items: ${batchSearchMetrics.operationTime.toFixed(
183 |           2,
184 |         )}ms (${searchThroughput.toFixed(2)} ops/sec)`,
185 |       );
186 |     });
187 |   });
188 | 
189 |   describe("Scalability Testing", () => {
190 |     test("should scale linearly with data size", async () => {
191 |       memoryManager.setContext({ projectId: "scalability-test" });
192 | 
193 |       const testSizes = [10, 50, 100, 500];
194 |       const results: Array<{
195 |         size: number;
196 |         createTime: number;
197 |         searchTime: number;
198 |       }> = [];
199 | 
200 |       for (const size of testSizes) {
201 |         const testData = Array.from({ length: size }, (_, i) => ({
202 |           projectId: "scalability-test",
203 |           index: i,
204 |           data: `test-data-${i}`,
205 |           timestamp: new Date().toISOString(),
206 |         }));
207 | 
208 |         // Measure creation time
209 |         const { metrics: createMetrics } = await measurePerformance(
210 |           async () => {
211 |             const promises = testData.map((data) =>
212 |               memoryManager.remember("analysis", data),
213 |             );
214 |             return await Promise.all(promises);
215 |           },
216 |         );
217 | 
218 |         // Measure search time
219 |         const { metrics: searchMetrics } = await measurePerformance(
220 |           async () => {
221 |             return await memoryManager.search({
222 |               projectId: "scalability-test",
223 |             });
224 |           },
225 |         );
226 | 
227 |         results.push({
228 |           size,
229 |           createTime: createMetrics.operationTime,
230 |           searchTime: searchMetrics.operationTime,
231 |         });
232 |       }
233 | 
234 |       // Verify roughly linear scaling (allow for some variance)
235 |       for (let i = 1; i < results.length; i++) {
236 |         const prev = results[i - 1];
237 |         const curr = results[i];
238 | 
239 |         const sizeRatio = curr.size / prev.size;
240 |         const createTimeRatio = curr.createTime / prev.createTime;
241 |         const searchTimeRatio = curr.searchTime / prev.searchTime;
242 | 
243 |         // Create time should scale roughly linearly (within 3x of size ratio)
244 |         expect(createTimeRatio).toBeLessThan(sizeRatio * 3);
245 | 
246 |         // Search time should not degrade too badly (within 2x of size ratio)
247 |         expect(searchTimeRatio).toBeLessThan(sizeRatio * 2);
248 |       }
249 | 
250 |       console.log("Scalability Results:");
251 |       results.forEach((result) => {
252 |         console.log(
253 |           `Size ${result.size}: Create ${result.createTime.toFixed(
254 |             2,
255 |           )}ms, Search ${result.searchTime.toFixed(2)}ms`,
256 |         );
257 |       });
258 |     });
259 | 
260 |     test("should handle large individual memories efficiently", async () => {
261 |       memoryManager.setContext({ projectId: "large-memory-test" });
262 | 
263 |       const sizes = [
264 |         { name: "small", data: "x".repeat(1000) }, // 1KB
265 |         { name: "medium", data: "x".repeat(10000) }, // 10KB
266 |         { name: "large", data: "x".repeat(100000) }, // 100KB
267 |         { name: "xlarge", data: "x".repeat(1000000) }, // 1MB
268 |       ];
269 | 
270 |       const results: Array<{
271 |         name: string;
272 |         createTime: number;
273 |         readTime: number;
274 |       }> = [];
275 | 
276 |       for (const size of sizes) {
277 |         const testData = {
278 |           projectId: "large-memory-test",
279 |           size: size.name,
280 |           content: size.data,
281 |           metadata: { size: size.data.length },
282 |         };
283 | 
284 |         // Measure creation time
285 |         const { result: memory, metrics: createMetrics } =
286 |           await measurePerformance(async () => {
287 |             return await memoryManager.remember("analysis", testData);
288 |           });
289 | 
290 |         // Measure read time
291 |         const { metrics: readMetrics } = await measurePerformance(async () => {
292 |           return await memoryManager.recall(memory.id);
293 |         });
294 | 
295 |         results.push({
296 |           name: size.name,
297 |           createTime: createMetrics.operationTime,
298 |           readTime: readMetrics.operationTime,
299 |         });
300 | 
301 |         // Large memories should still be handled within reasonable time
302 |         expect(createMetrics.operationTime).toBeLessThan(5000); // 5 seconds
303 |         expect(readMetrics.operationTime).toBeLessThan(1000); // 1 second
304 |       }
305 | 
306 |       console.log("Large Memory Performance:");
307 |       results.forEach((result) => {
308 |         console.log(
309 |           `${result.name}: Create ${result.createTime.toFixed(
310 |             2,
311 |           )}ms, Read ${result.readTime.toFixed(2)}ms`,
312 |         );
313 |       });
314 |     });
315 |   });
316 | 
317 |   describe("Concurrent Operations Performance", () => {
318 |     test("should handle concurrent read/write operations", async () => {
319 |       memoryManager.setContext({ projectId: "concurrent-test" });
320 | 
321 |       // Pre-populate with some data
322 |       const initialData = Array.from({ length: 50 }, (_, i) => ({
323 |         projectId: "concurrent-test",
324 |         index: i,
325 |         data: `initial-data-${i}`,
326 |       }));
327 | 
328 |       const initialMemories = await Promise.all(
329 |         initialData.map((data) => memoryManager.remember("analysis", data)),
330 |       );
331 | 
332 |       const concurrentOperations = 20;
333 | 
334 |       const { metrics: concurrentMetrics } = await measurePerformance(
335 |         async () => {
336 |           const operations = Array.from(
337 |             { length: concurrentOperations },
338 |             async (_, i) => {
339 |               if (i % 3 === 0) {
340 |                 // Create new memory
341 |                 return await memoryManager.remember("analysis", {
342 |                   projectId: "concurrent-test",
343 |                   index: 100 + i,
344 |                   data: `concurrent-data-${i}`,
345 |                 });
346 |               } else if (i % 3 === 1) {
347 |                 // Read existing memory
348 |                 const randomMemory =
349 |                   initialMemories[
350 |                     Math.floor(Math.random() * initialMemories.length)
351 |                   ];
352 |                 return await memoryManager.recall(randomMemory.id);
353 |               } else {
354 |                 // Search memories
355 |                 return await memoryManager.search({
356 |                   projectId: "concurrent-test",
357 |                 });
358 |               }
359 |             },
360 |           );
361 | 
362 |           return await Promise.all(operations);
363 |         },
364 |       );
365 | 
366 |       expect(concurrentMetrics.operationTime).toBeLessThan(3000); // 3 seconds for 20 concurrent ops
367 | 
368 |       const throughput =
369 |         concurrentOperations / (concurrentMetrics.operationTime / 1000);
370 |       expect(throughput).toBeGreaterThan(5); // At least 5 concurrent ops/sec
371 | 
372 |       console.log("Concurrent Operations Performance:");
373 |       console.log(
374 |         `${concurrentOperations} concurrent ops: ${concurrentMetrics.operationTime.toFixed(
375 |           2,
376 |         )}ms (${throughput.toFixed(2)} ops/sec)`,
377 |       );
378 |     });
379 | 
380 |     test("should maintain performance under sustained load", async () => {
381 |       memoryManager.setContext({ projectId: "sustained-load-test" });
382 | 
383 |       const testDuration = 3000; // 3 seconds
384 |       const operationInterval = 100; // Every 100ms
385 |       const results: number[] = [];
386 | 
387 |       const startTime = Date.now();
388 |       let operationCount = 0;
389 | 
390 |       while (Date.now() - startTime < testDuration) {
391 |         const { metrics } = await measurePerformance(async () => {
392 |           return await memoryManager.remember("analysis", {
393 |             projectId: "sustained-load-test",
394 |             index: operationCount++,
395 |             timestamp: Date.now(),
396 |             data: `sustained-load-data-${operationCount}`,
397 |           });
398 |         });
399 | 
400 |         results.push(metrics.operationTime);
401 | 
402 |         // Wait for next interval
403 |         await new Promise((resolve) => setTimeout(resolve, operationInterval));
404 |       }
405 | 
406 |       const avgTime =
407 |         results.reduce((sum, time) => sum + time, 0) / results.length;
408 |       const maxTime = Math.max(...results);
409 |       const minTime = Math.min(...results);
410 | 
411 |       // Performance should remain consistent under sustained load
412 |       expect(avgTime).toBeLessThan(200); // Average operation time < 200ms
413 |       expect(maxTime).toBeLessThan(1000); // No single operation > 1 second
414 | 
415 |       // Performance degradation should be minimal
416 |       const firstHalf = results.slice(0, Math.floor(results.length / 2));
417 |       const secondHalf = results.slice(Math.floor(results.length / 2));
418 | 
419 |       const firstHalfAvg =
420 |         firstHalf.reduce((sum, time) => sum + time, 0) / firstHalf.length;
421 |       const secondHalfAvg =
422 |         secondHalf.reduce((sum, time) => sum + time, 0) / secondHalf.length;
423 | 
424 |       const degradation = secondHalfAvg / firstHalfAvg;
425 |       expect(degradation).toBeLessThan(2); // Less than 2x degradation
426 | 
427 |       console.log("Sustained Load Performance:");
428 |       console.log(
429 |         `Operations: ${results.length}, Avg: ${avgTime.toFixed(
430 |           2,
431 |         )}ms, Min: ${minTime.toFixed(2)}ms, Max: ${maxTime.toFixed(2)}ms`,
432 |       );
433 |       console.log(
434 |         `Performance degradation: ${((degradation - 1) * 100).toFixed(1)}%`,
435 |       );
436 |     });
437 |   });
438 | 
439 |   describe("Memory Resource Usage", () => {
440 |     test("should manage memory usage efficiently", async () => {
441 |       memoryManager.setContext({ projectId: "memory-usage-test" });
442 | 
443 |       const initialMemory = process.memoryUsage();
444 |       const memorySnapshots: Array<{ count: number; heapUsed: number }> = [];
445 | 
446 |       // Add memories in batches and monitor memory usage
447 |       for (let batch = 0; batch < 10; batch++) {
448 |         const batchSize = 100;
449 |         const batchData = Array.from({ length: batchSize }, (_, i) => ({
450 |           projectId: "memory-usage-test",
451 |           batch,
452 |           index: i,
453 |           data: "x".repeat(1000), // 1KB per memory
454 |           timestamp: new Date().toISOString(),
455 |         }));
456 | 
457 |         await Promise.all(
458 |           batchData.map((data) => memoryManager.remember("analysis", data)),
459 |         );
460 | 
461 |         const currentMemory = process.memoryUsage();
462 |         memorySnapshots.push({
463 |           count: (batch + 1) * batchSize,
464 |           heapUsed: currentMemory.heapUsed - initialMemory.heapUsed,
465 |         });
466 | 
467 |         // Force garbage collection if available
468 |         if (global.gc) {
469 |           global.gc();
470 |         }
471 |       }
472 | 
473 |       // Memory usage should be reasonable
474 |       const finalMemoryUsage = memorySnapshots[memorySnapshots.length - 1];
475 |       const memoryPerItem = finalMemoryUsage.heapUsed / finalMemoryUsage.count;
476 | 
477 |       expect(memoryPerItem).toBeLessThan(50 * 1024); // Less than 50KB per memory item (including overhead)
478 |       expect(finalMemoryUsage.heapUsed).toBeLessThan(100 * 1024 * 1024); // Less than 100MB total
479 | 
480 |       console.log("Memory Usage Analysis:");
481 |       console.log(
482 |         `Total items: ${finalMemoryUsage.count}, Total memory: ${(
483 |           finalMemoryUsage.heapUsed /
484 |           1024 /
485 |           1024
486 |         ).toFixed(2)}MB`,
487 |       );
488 |       console.log(`Memory per item: ${(memoryPerItem / 1024).toFixed(2)}KB`);
489 |     });
490 | 
491 |     test("should not leak memory on cleanup operations", async () => {
492 |       memoryManager.setContext({ projectId: "memory-leak-test" });
493 | 
494 |       const initialMemory = process.memoryUsage();
495 | 
496 |       // Create and delete memories multiple times
497 |       for (let cycle = 0; cycle < 5; cycle++) {
498 |         const memories = [];
499 | 
500 |         // Create memories
501 |         for (let i = 0; i < 100; i++) {
502 |           const memory = await memoryManager.remember("analysis", {
503 |             projectId: "memory-leak-test",
504 |             cycle,
505 |             index: i,
506 |             data: "x".repeat(1000),
507 |           });
508 |           memories.push(memory);
509 |         }
510 | 
511 |         // Delete all memories
512 |         for (const memory of memories) {
513 |           await memoryManager.forget(memory.id);
514 |         }
515 | 
516 |         // Force garbage collection
517 |         if (global.gc) {
518 |           global.gc();
519 |         }
520 |       }
521 | 
522 |       const finalMemory = process.memoryUsage();
523 |       const memoryDifference = finalMemory.heapUsed - initialMemory.heapUsed;
524 | 
525 |       // Memory usage should return close to initial levels
526 |       expect(memoryDifference).toBeLessThan(15 * 1024 * 1024); // Less than 15MB difference
527 | 
528 |       console.log("Memory Leak Test:");
529 |       console.log(
530 |         `Memory difference: ${(memoryDifference / 1024 / 1024).toFixed(2)}MB`,
531 |       );
532 |     });
533 |   });
534 | 
535 |   describe("Enhanced Components Performance", () => {
536 |     test("should benchmark enhanced memory manager performance", async () => {
537 |       const enhancedTempDir = path.join(tempDir, "enhanced");
538 |       await fs.mkdir(enhancedTempDir, { recursive: true });
539 | 
540 |       const enhancedManager = new EnhancedMemoryManager(enhancedTempDir);
541 |       await enhancedManager.initialize();
542 | 
543 |       enhancedManager.setContext({ projectId: "enhanced-performance" });
544 | 
545 |       const projectFeatures: import("../../src/memory/learning.js").ProjectFeatures =
546 |         {
547 |           language: "typescript",
548 |           framework: "react",
549 |           size: "medium",
550 |           complexity: "moderate",
551 |           hasTests: true,
552 |           hasCI: true,
553 |           hasDocs: true,
554 |           isOpenSource: true,
555 |         };
556 | 
557 |       const baseRecommendation = {
558 |         recommended: "docusaurus",
559 |         confidence: 0.8,
560 |         score: 0.85,
561 |       };
562 | 
563 |       // Benchmark enhanced recommendation
564 |       const { metrics: enhancedMetrics } = await measurePerformance(
565 |         async () => {
566 |           return await enhancedManager.getEnhancedRecommendation(
567 |             "/test/enhanced-performance",
568 |             baseRecommendation,
569 |             projectFeatures,
570 |           );
571 |         },
572 |       );
573 | 
574 |       expect(enhancedMetrics.operationTime).toBeLessThan(5000); // 5 seconds
575 | 
576 |       // Benchmark intelligent analysis
577 |       const analysisData = {
578 |         language: "typescript",
579 |         framework: "react",
580 |         size: "medium",
581 |         hasTests: true,
582 |         hasCI: true,
583 |       };
584 | 
585 |       const { metrics: analysisMetrics } = await measurePerformance(
586 |         async () => {
587 |           return await enhancedManager.getIntelligentAnalysis(
588 |             "/test/enhanced-performance",
589 |             analysisData,
590 |           );
591 |         },
592 |       );
593 | 
594 |       expect(analysisMetrics.operationTime).toBeLessThan(3000); // 3 seconds
595 | 
596 |       console.log("Enhanced Components Performance:");
597 |       console.log(
598 |         `Enhanced recommendation: ${enhancedMetrics.operationTime.toFixed(
599 |           2,
600 |         )}ms`,
601 |       );
602 |       console.log(
603 |         `Intelligent analysis: ${analysisMetrics.operationTime.toFixed(2)}ms`,
604 |       );
605 |     });
606 | 
607 |     test("should benchmark learning system performance", async () => {
608 |       const learningTempDir = path.join(tempDir, "learning");
609 |       await fs.mkdir(learningTempDir, { recursive: true });
610 | 
611 |       const tempLearningManager = new MemoryManager(learningTempDir);
612 |       await tempLearningManager.initialize();
613 |       const learningSystem = new IncrementalLearningSystem(tempLearningManager);
614 |       await learningSystem.initialize();
615 | 
616 |       const projectFeatures: import("../../src/memory/learning.js").ProjectFeatures =
617 |         {
618 |           language: "python",
619 |           framework: "django",
620 |           size: "large",
621 |           complexity: "complex",
622 |           hasTests: true,
623 |           hasCI: true,
624 |           hasDocs: false,
625 |           isOpenSource: true,
626 |         };
627 | 
628 |       const baseRecommendation = {
629 |         recommended: "sphinx",
630 |         confidence: 0.7,
631 |       };
632 | 
633 |       // Add training data through memory manager (learning system learns from stored memories)
634 |       for (let i = 0; i < 50; i++) {
635 |         await tempLearningManager.remember("analysis", {
636 |           ...projectFeatures,
637 |           index: i,
638 |           feedback: {
639 |             rating: 3 + (i % 3),
640 |             helpful: i % 2 === 0,
641 |             comments: `Training feedback ${i}`,
642 |           },
643 |         });
644 |       }
645 | 
646 |       // Benchmark improved recommendation
647 |       const { metrics: improveMetrics } = await measurePerformance(async () => {
648 |         return await learningSystem.getImprovedRecommendation(
649 |           projectFeatures,
650 |           baseRecommendation,
651 |         );
652 |       });
653 | 
654 |       expect(improveMetrics.operationTime).toBeLessThan(1000); // 1 second
655 | 
656 |       // Benchmark pattern detection
657 |       const { metrics: patternMetrics } = await measurePerformance(async () => {
658 |         return await learningSystem.getPatterns();
659 |       });
660 | 
661 |       expect(patternMetrics.operationTime).toBeLessThan(2000); // 2 seconds
662 | 
663 |       console.log("Learning System Performance:");
664 |       console.log(
665 |         `Improved recommendation: ${improveMetrics.operationTime.toFixed(2)}ms`,
666 |       );
667 |       console.log(
668 |         `Pattern detection: ${patternMetrics.operationTime.toFixed(2)}ms`,
669 |       );
670 |     });
671 | 
672 |     test("should benchmark knowledge graph performance", async () => {
673 |       const graphTempDir = path.join(tempDir, "graph");
674 |       await fs.mkdir(graphTempDir, { recursive: true });
675 | 
676 |       const tempGraphManager = new MemoryManager(graphTempDir);
677 |       await tempGraphManager.initialize();
678 |       const knowledgeGraph = new KnowledgeGraph(tempGraphManager);
679 |       await knowledgeGraph.initialize();
680 | 
681 |       // Add nodes and edges
682 |       const nodeCount = 100;
683 |       const edgeCount = 200;
684 | 
685 |       const { metrics: buildMetrics } = await measurePerformance(async () => {
686 |         // Add nodes
687 |         for (let i = 0; i < nodeCount; i++) {
688 |           knowledgeGraph.addNode({
689 |             id: `node-${i}`,
690 |             type:
691 |               i % 3 === 0 ? "project" : i % 3 === 1 ? "technology" : "pattern",
692 |             label: `Node ${i}`,
693 |             weight: 1.0,
694 |             properties: {
695 |               name: `Node ${i}`,
696 |               category: i % 5 === 0 ? "frontend" : "backend",
697 |             },
698 |           });
699 |         }
700 | 
701 |         // Add edges
702 |         for (let i = 0; i < edgeCount; i++) {
703 |           const sourceId = `node-${Math.floor(Math.random() * nodeCount)}`;
704 |           const targetId = `node-${Math.floor(Math.random() * nodeCount)}`;
705 | 
706 |           if (sourceId !== targetId) {
707 |             knowledgeGraph.addEdge({
708 |               source: sourceId,
709 |               target: targetId,
710 |               type: i % 2 === 0 ? "uses" : "similar_to",
711 |               weight: Math.random(),
712 |               properties: {},
713 |               confidence: Math.random(),
714 |             });
715 |           }
716 |         }
717 |       });
718 | 
719 |       expect(buildMetrics.operationTime).toBeLessThan(5000); // 5 seconds to build graph
720 | 
721 |       // Benchmark pathfinding
722 |       const { metrics: pathMetrics } = await measurePerformance(async () => {
723 |         return knowledgeGraph.findPath("node-0", "node-50");
724 |       });
725 | 
726 |       expect(pathMetrics.operationTime).toBeLessThan(500); // 500ms for pathfinding
727 | 
728 |       // Benchmark node queries (using available methods)
729 |       const { metrics: queryMetrics } = await measurePerformance(async () => {
730 |         return knowledgeGraph.getAllNodes();
731 |       });
732 | 
733 |       expect(queryMetrics.operationTime).toBeLessThan(1000); // 1 second for node queries
734 | 
735 |       console.log("Knowledge Graph Performance:");
736 |       console.log(
737 |         `Build graph (${nodeCount} nodes, ${edgeCount} edges): ${buildMetrics.operationTime.toFixed(
738 |           2,
739 |         )}ms`,
740 |       );
741 |       console.log(`Find path: ${pathMetrics.operationTime.toFixed(2)}ms`);
742 |       console.log(`Nodes query: ${queryMetrics.operationTime.toFixed(2)}ms`);
743 |     });
744 |   });
745 | 
746 |   describe("Integration Performance", () => {
747 |     test("should benchmark MCP integration performance", async () => {
748 |       // Test memory integration functions
749 |       const analysisData = {
750 |         projectId: "integration-performance",
751 |         language: { primary: "go" },
752 |         framework: { name: "gin" },
753 |         stats: { files: 200, lines: 50000 },
754 |       };
755 | 
756 |       const { metrics: analysisMetrics } = await measurePerformance(
757 |         async () => {
758 |           return await rememberAnalysis(
759 |             "/test/integration-performance",
760 |             analysisData,
761 |           );
762 |         },
763 |       );
764 | 
765 |       const { metrics: recommendationMetrics } = await measurePerformance(
766 |         async () => {
767 |           return await rememberRecommendation("analysis-id", {
768 |             recommended: "hugo",
769 |             confidence: 0.9,
770 |           });
771 |         },
772 |       );
773 | 
774 |       const { metrics: similarMetrics } = await measurePerformance(async () => {
775 |         return await getSimilarProjects(analysisData, 5);
776 |       });
777 | 
778 |       expect(analysisMetrics.operationTime).toBeLessThan(1000); // 1 second
779 |       expect(recommendationMetrics.operationTime).toBeLessThan(1000); // 1 second
780 |       expect(similarMetrics.operationTime).toBeLessThan(2000); // 2 seconds
781 | 
782 |       console.log("MCP Integration Performance:");
783 |       console.log(
784 |         `Remember analysis: ${analysisMetrics.operationTime.toFixed(2)}ms`,
785 |       );
786 |       console.log(
787 |         `Remember recommendation: ${recommendationMetrics.operationTime.toFixed(
788 |           2,
789 |         )}ms`,
790 |       );
791 |       console.log(
792 |         `Get similar projects: ${similarMetrics.operationTime.toFixed(2)}ms`,
793 |       );
794 |     });
795 |   });
796 | });
797 | 
```
Page 18/29FirstPrevNextLast