This is page 25 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/tools/validate-content.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Tool } from "@modelcontextprotocol/sdk/types.js";
2 | import * as fs from "fs/promises";
3 | import * as path from "path";
4 | import { exec } from "child_process";
5 | import { promisify } from "util";
6 | import { handleMemoryRecall } from "../memory/index.js";
7 | // ESM-compatible dirname replacement - fallback for test environments
8 | function getDirname(): string {
9 | // Use process.cwd() as fallback for all environments to avoid import.meta issues
10 | return process.cwd();
11 | }
12 |
13 | const currentDir = getDirname();
14 |
15 | const execAsync = promisify(exec);
16 |
17 | interface ValidationOptions {
18 | contentPath: string;
19 | analysisId?: string;
20 | validationType: "accuracy" | "completeness" | "compliance" | "all";
21 | includeCodeValidation: boolean;
22 | confidence: "strict" | "moderate" | "permissive";
23 | }
24 |
25 | interface ConfidenceMetrics {
26 | overall: number;
27 | breakdown: {
28 | technologyDetection: number;
29 | frameworkVersionAccuracy: number;
30 | codeExampleRelevance: number;
31 | architecturalAssumptions: number;
32 | businessContextAlignment: number;
33 | };
34 | riskFactors: RiskFactor[];
35 | }
36 |
37 | interface RiskFactor {
38 | type: "high" | "medium" | "low";
39 | category: string;
40 | description: string;
41 | impact: string;
42 | mitigation: string;
43 | }
44 |
45 | interface UncertaintyFlag {
46 | area: string;
47 | severity: "low" | "medium" | "high" | "critical";
48 | description: string;
49 | potentialImpact: string;
50 | clarificationNeeded: string;
51 | fallbackStrategy: string;
52 | }
53 |
54 | interface ValidationIssue {
55 | type: "error" | "warning" | "info";
56 | category: "accuracy" | "completeness" | "compliance" | "performance";
57 | location: {
58 | file: string;
59 | line?: number;
60 | section?: string;
61 | };
62 | description: string;
63 | evidence: string[];
64 | suggestedFix: string;
65 | confidence: number;
66 | }
67 |
68 | interface CodeValidationResult {
69 | overallSuccess: boolean;
70 | exampleResults: ExampleValidation[];
71 | confidence: number;
72 | }
73 |
74 | interface ExampleValidation {
75 | example: string;
76 | compilationSuccess: boolean;
77 | executionSuccess: boolean;
78 | issues: ValidationIssue[];
79 | confidence: number;
80 | }
81 |
82 | export interface ValidationResult {
83 | success: boolean;
84 | confidence: ConfidenceMetrics;
85 | issues: ValidationIssue[];
86 | uncertainties: UncertaintyFlag[];
87 | codeValidation?: CodeValidationResult;
88 | recommendations: string[];
89 | nextSteps: string[];
90 | }
91 |
92 | class ContentAccuracyValidator {
93 | private projectContext: any;
94 | private tempDir: string;
95 |
96 | constructor() {
97 | this.tempDir = path.join(currentDir, ".tmp");
98 | }
99 |
100 | async validateContent(
101 | options: ValidationOptions,
102 | context?: any,
103 | ): Promise<ValidationResult> {
104 | if (context?.meta?.progressToken) {
105 | await context.meta.reportProgress?.({ progress: 0, total: 100 });
106 | }
107 |
108 | const result: ValidationResult = {
109 | success: false,
110 | confidence: this.initializeConfidenceMetrics(),
111 | issues: [],
112 | uncertainties: [],
113 | recommendations: [],
114 | nextSteps: [],
115 | };
116 |
117 | // Load project context if analysis ID provided
118 | if (options.analysisId) {
119 | await context?.info?.("📊 Loading project context...");
120 | this.projectContext = await this.loadProjectContext(options.analysisId);
121 | }
122 |
123 | if (context?.meta?.progressToken) {
124 | await context.meta.reportProgress?.({ progress: 20, total: 100 });
125 | }
126 |
127 | // Determine if we should analyze application code vs documentation
128 | await context?.info?.("🔎 Analyzing content type...");
129 | const isApplicationValidation = await this.shouldAnalyzeApplicationCode(
130 | options.contentPath,
131 | );
132 |
133 | if (context?.meta?.progressToken) {
134 | await context.meta.reportProgress?.({ progress: 40, total: 100 });
135 | }
136 |
137 | // Perform different types of validation based on request
138 | if (
139 | options.validationType === "all" ||
140 | options.validationType === "accuracy"
141 | ) {
142 | await this.validateAccuracy(options.contentPath, result);
143 | }
144 |
145 | if (
146 | options.validationType === "all" ||
147 | options.validationType === "completeness"
148 | ) {
149 | await this.validateCompleteness(options.contentPath, result);
150 | }
151 |
152 | if (
153 | options.validationType === "all" ||
154 | options.validationType === "compliance"
155 | ) {
156 | if (isApplicationValidation) {
157 | await this.validateApplicationStructureCompliance(
158 | options.contentPath,
159 | result,
160 | );
161 | } else {
162 | await this.validateDiataxisCompliance(options.contentPath, result);
163 | }
164 | }
165 |
166 | // Code validation if requested
167 | if (options.includeCodeValidation) {
168 | result.codeValidation = await this.validateCodeExamples(
169 | options.contentPath,
170 | );
171 | // Set code example relevance confidence based on code validation results
172 | if (result.codeValidation) {
173 | const successRate =
174 | result.codeValidation.exampleResults.length > 0
175 | ? result.codeValidation.exampleResults.filter(
176 | (e) => e.compilationSuccess,
177 | ).length / result.codeValidation.exampleResults.length
178 | : 1;
179 | result.confidence.breakdown.codeExampleRelevance = Math.round(
180 | successRate * 100,
181 | );
182 | }
183 | } else {
184 | // If code validation is skipped, assume reasonable confidence
185 | result.confidence.breakdown.codeExampleRelevance = 75;
186 | }
187 |
188 | // Set framework version accuracy based on technology detection confidence
189 | result.confidence.breakdown.frameworkVersionAccuracy = Math.min(
190 | 90,
191 | result.confidence.breakdown.technologyDetection + 10,
192 | );
193 |
194 | // Set architectural assumptions confidence based on file structure and content analysis
195 | const filesAnalyzed = await this.getMarkdownFiles(options.contentPath);
196 | const hasStructuredContent = filesAnalyzed.length > 3; // Basic heuristic
197 | result.confidence.breakdown.architecturalAssumptions = hasStructuredContent
198 | ? 80
199 | : 60;
200 |
201 | // Calculate overall confidence and success
202 | this.calculateOverallMetrics(result);
203 |
204 | // Generate recommendations and next steps
205 | this.generateRecommendations(result, options);
206 |
207 | if (context?.meta?.progressToken) {
208 | await context.meta.reportProgress?.({ progress: 100, total: 100 });
209 | }
210 |
211 | const status = result.success ? "PASSED" : "ISSUES FOUND";
212 | await context?.info?.(
213 | `✅ Validation complete! Status: ${status} (${result.confidence.overall}% confidence, ${result.issues.length} issue(s))`,
214 | );
215 |
216 | return result;
217 | }
218 |
219 | private initializeConfidenceMetrics(): ConfidenceMetrics {
220 | return {
221 | overall: 0,
222 | breakdown: {
223 | technologyDetection: 0,
224 | frameworkVersionAccuracy: 0,
225 | codeExampleRelevance: 0,
226 | architecturalAssumptions: 0,
227 | businessContextAlignment: 0,
228 | },
229 | riskFactors: [],
230 | };
231 | }
232 |
233 | private async loadProjectContext(analysisId: string): Promise<any> {
234 | // Try to get analysis from memory system first
235 | try {
236 | const memoryRecall = await handleMemoryRecall({
237 | query: analysisId,
238 | type: "analysis",
239 | limit: 1,
240 | });
241 |
242 | // Handle the memory recall result structure
243 | if (
244 | memoryRecall &&
245 | memoryRecall.memories &&
246 | memoryRecall.memories.length > 0
247 | ) {
248 | const memory = memoryRecall.memories[0];
249 |
250 | // Handle wrapped content structure
251 | if (
252 | memory.data &&
253 | memory.data.content &&
254 | Array.isArray(memory.data.content)
255 | ) {
256 | // Extract the JSON from the first text content
257 | const firstContent = memory.data.content[0];
258 | if (
259 | firstContent &&
260 | firstContent.type === "text" &&
261 | firstContent.text
262 | ) {
263 | try {
264 | return JSON.parse(firstContent.text);
265 | } catch (parseError) {
266 | console.warn(
267 | "Failed to parse analysis content from memory:",
268 | parseError,
269 | );
270 | }
271 | }
272 | }
273 |
274 | // Try direct content or data access
275 | if (memory.content) {
276 | return memory.content;
277 | }
278 | if (memory.data) {
279 | return memory.data;
280 | }
281 | }
282 | } catch (error) {
283 | console.warn("Failed to retrieve from memory system:", error);
284 | }
285 |
286 | // Fallback to reading from cached analysis file
287 | try {
288 | const analysisPath = path.join(
289 | ".documcp",
290 | "analyses",
291 | `${analysisId}.json`,
292 | );
293 | const content = await fs.readFile(analysisPath, "utf-8");
294 | return JSON.parse(content);
295 | } catch {
296 | // Return default context if no analysis found
297 | return {
298 | metadata: { projectName: "unknown", primaryLanguage: "JavaScript" },
299 | technologies: {},
300 | dependencies: { packages: [] },
301 | };
302 | }
303 | }
304 |
305 | private async validateAccuracy(
306 | contentPath: string,
307 | result: ValidationResult,
308 | ): Promise<void> {
309 | const files = await this.getMarkdownFiles(contentPath);
310 |
311 | for (const file of files) {
312 | const content = await fs.readFile(file, "utf-8");
313 |
314 | // Check for common accuracy issues
315 | await this.checkTechnicalAccuracy(file, content, result);
316 | await this.checkFrameworkVersionCompatibility(file, content, result);
317 | await this.checkCommandAccuracy(file, content, result);
318 | await this.checkLinkValidity(file, content, result);
319 | }
320 |
321 | // Update confidence based on findings
322 | this.updateAccuracyConfidence(result);
323 | }
324 |
325 | private async checkTechnicalAccuracy(
326 | filePath: string,
327 | content: string,
328 | result: ValidationResult,
329 | ): Promise<void> {
330 | // Check for deprecated patterns
331 | const deprecatedPatterns = [
332 | {
333 | pattern: /npm install -g/,
334 | suggestion: "Use npx instead of global installs",
335 | },
336 | { pattern: /var\s+\w+/, suggestion: "Use const or let instead of var" },
337 | { pattern: /function\(\)/, suggestion: "Consider using arrow functions" },
338 | { pattern: /http:\/\//, suggestion: "Use HTTPS URLs for security" },
339 | ];
340 |
341 | for (const { pattern, suggestion } of deprecatedPatterns) {
342 | if (pattern.test(content)) {
343 | result.issues.push({
344 | type: "warning",
345 | category: "accuracy",
346 | location: { file: path.basename(filePath) },
347 | description: `Potentially outdated pattern detected: ${pattern.source}`,
348 | evidence: [content.match(pattern)?.[0] || ""],
349 | suggestedFix: suggestion,
350 | confidence: 80,
351 | });
352 | }
353 | }
354 |
355 | // Check for missing error handling in code examples
356 | const codeBlocks = this.extractCodeBlocks(content);
357 | for (const block of codeBlocks) {
358 | if (block.language === "javascript" || block.language === "typescript") {
359 | if (
360 | block.code.includes("await") &&
361 | !block.code.includes("try") &&
362 | !block.code.includes("catch")
363 | ) {
364 | result.issues.push({
365 | type: "warning",
366 | category: "accuracy",
367 | location: { file: path.basename(filePath) },
368 | description: "Async code without error handling",
369 | evidence: [block.code.substring(0, 100)],
370 | suggestedFix: "Add try-catch blocks for async operations",
371 | confidence: 90,
372 | });
373 | }
374 | }
375 | }
376 | }
377 |
378 | private async checkFrameworkVersionCompatibility(
379 | filePath: string,
380 | content: string,
381 | result: ValidationResult,
382 | ): Promise<void> {
383 | if (!this.projectContext) return;
384 |
385 | // Check if mentioned versions align with project dependencies
386 | const versionPattern = /@(\d+\.\d+\.\d+)/g;
387 | const matches = content.match(versionPattern);
388 |
389 | if (matches) {
390 | for (const match of matches) {
391 | const version = match.replace("@", "");
392 |
393 | result.uncertainties.push({
394 | area: "version-compatibility",
395 | severity: "medium",
396 | description: `Version ${version} mentioned in documentation`,
397 | potentialImpact: "May not match actual project dependencies",
398 | clarificationNeeded: "Verify version compatibility with project",
399 | fallbackStrategy: "Use generic version-agnostic examples",
400 | });
401 | }
402 | }
403 | }
404 |
405 | private async checkCommandAccuracy(
406 | filePath: string,
407 | content: string,
408 | result: ValidationResult,
409 | ): Promise<void> {
410 | const codeBlocks = this.extractCodeBlocks(content);
411 |
412 | for (const block of codeBlocks) {
413 | if (block.language === "bash" || block.language === "sh") {
414 | // Check for common command issues
415 | const commands = block.code
416 | .split("\n")
417 | .filter((line) => line.trim() && !line.startsWith("#"));
418 |
419 | for (const command of commands) {
420 | // Check for potentially dangerous commands
421 | const dangerousPatterns = [
422 | /rm -rf \//,
423 | /sudo rm/,
424 | /chmod 777/,
425 | /> \/dev\/null 2>&1/,
426 | ];
427 |
428 | for (const pattern of dangerousPatterns) {
429 | if (pattern.test(command)) {
430 | result.issues.push({
431 | type: "error",
432 | category: "accuracy",
433 | location: { file: path.basename(filePath) },
434 | description: "Potentially dangerous command in documentation",
435 | evidence: [command],
436 | suggestedFix: "Review and provide safer alternative",
437 | confidence: 95,
438 | });
439 | }
440 | }
441 |
442 | // Check for non-portable commands (Windows vs Unix)
443 | if (command.includes("\\") && command.includes("/")) {
444 | result.issues.push({
445 | type: "warning",
446 | category: "accuracy",
447 | location: { file: path.basename(filePath) },
448 | description: "Mixed path separators in command",
449 | evidence: [command],
450 | suggestedFix:
451 | "Use consistent path separators or provide OS-specific examples",
452 | confidence: 85,
453 | });
454 | }
455 | }
456 | }
457 | }
458 | }
459 |
460 | private async checkLinkValidity(
461 | filePath: string,
462 | content: string,
463 | result: ValidationResult,
464 | ): Promise<void> {
465 | const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
466 | const links: Array<{ text: string; url: string }> = [];
467 | let match;
468 |
469 | while ((match = linkPattern.exec(content)) !== null) {
470 | links.push({ text: match[1], url: match[2] });
471 | }
472 |
473 | for (const link of links) {
474 | // Check internal links
475 | if (!link.url.startsWith("http")) {
476 | const targetPath = path.resolve(path.dirname(filePath), link.url);
477 | try {
478 | await fs.access(targetPath);
479 | } catch {
480 | result.issues.push({
481 | type: "error",
482 | category: "accuracy",
483 | location: { file: path.basename(filePath) },
484 | description: `Broken internal link: ${link.url}`,
485 | evidence: [link.text],
486 | suggestedFix: "Fix the link path or create the missing file",
487 | confidence: 100,
488 | });
489 | }
490 | }
491 |
492 | // Flag external links for manual verification
493 | if (link.url.startsWith("http")) {
494 | result.uncertainties.push({
495 | area: "external-links",
496 | severity: "low",
497 | description: `External link: ${link.url}`,
498 | potentialImpact: "Link may become outdated or broken",
499 | clarificationNeeded: "Verify link is still valid",
500 | fallbackStrategy: "Archive important external content locally",
501 | });
502 | }
503 | }
504 | }
505 |
506 | private async validateCompleteness(
507 | contentPath: string,
508 | result: ValidationResult,
509 | ): Promise<void> {
510 | const files = await this.getMarkdownFiles(contentPath);
511 | const structure = await this.analyzeDiataxisStructure(contentPath);
512 |
513 | // Check for missing essential sections
514 | const requiredSections = [
515 | "tutorials",
516 | "how-to",
517 | "reference",
518 | "explanation",
519 | ];
520 | const missingSections = requiredSections.filter(
521 | (section) => !structure.sections.includes(section),
522 | );
523 |
524 | if (missingSections.length > 0) {
525 | result.issues.push({
526 | type: "warning",
527 | category: "completeness",
528 | location: { file: "documentation structure" },
529 | description: `Missing Diataxis sections: ${missingSections.join(", ")}`,
530 | evidence: structure.sections,
531 | suggestedFix:
532 | "Add missing Diataxis sections for complete documentation",
533 | confidence: 100,
534 | });
535 | }
536 |
537 | // Check content depth in each section
538 | for (const section of structure.sections) {
539 | const sectionFiles = files.filter((f) => f.includes(`/${section}/`));
540 | if (sectionFiles.length < 2) {
541 | result.issues.push({
542 | type: "info",
543 | category: "completeness",
544 | location: { file: section },
545 | description: `Limited content in ${section} section`,
546 | evidence: [`Only ${sectionFiles.length} files`],
547 | suggestedFix: "Consider adding more comprehensive coverage",
548 | confidence: 75,
549 | });
550 | }
551 | }
552 |
553 | // Update completeness confidence
554 | result.confidence.breakdown.businessContextAlignment = Math.max(
555 | 0,
556 | 100 - missingSections.length * 25,
557 | );
558 | }
559 |
560 | private async validateDiataxisCompliance(
561 | contentPath: string,
562 | result: ValidationResult,
563 | ): Promise<void> {
564 | const files = await this.getMarkdownFiles(contentPath);
565 |
566 | for (const file of files) {
567 | const content = await fs.readFile(file, "utf-8");
568 | const section = this.identifyDiataxisSection(file);
569 |
570 | if (section) {
571 | await this.checkSectionCompliance(file, content, section, result);
572 | }
573 | }
574 | }
575 |
576 | private async validateApplicationStructureCompliance(
577 | contentPath: string,
578 | result: ValidationResult,
579 | ): Promise<void> {
580 | // Analyze application source code for Diataxis compliance
581 | await this.validateSourceCodeDocumentation(contentPath, result);
582 | await this.validateApplicationArchitecture(contentPath, result);
583 | await this.validateInlineDocumentationPatterns(contentPath, result);
584 | }
585 |
586 | private async validateSourceCodeDocumentation(
587 | contentPath: string,
588 | result: ValidationResult,
589 | ): Promise<void> {
590 | const sourceFiles = await this.getSourceFiles(contentPath);
591 |
592 | for (const file of sourceFiles) {
593 | const content = await fs.readFile(file, "utf-8");
594 |
595 | // Check for proper JSDoc/TSDoc documentation
596 | await this.checkInlineDocumentationQuality(file, content, result);
597 |
598 | // Check for README files and their structure
599 | if (file.endsWith("README.md")) {
600 | await this.validateReadmeStructure(file, content, result);
601 | }
602 |
603 | // Check for proper module/class documentation
604 | await this.checkModuleDocumentation(file, content, result);
605 | }
606 | }
607 |
608 | private async validateApplicationArchitecture(
609 | contentPath: string,
610 | result: ValidationResult,
611 | ): Promise<void> {
612 | // Check if the application structure supports different types of documentation
613 | const hasToolsDir = await this.pathExists(path.join(contentPath, "tools"));
614 | const hasTypesDir = await this.pathExists(path.join(contentPath, "types"));
615 | // Check for workflows directory (currently not used but may be useful for future validation)
616 | // const hasWorkflowsDir = await this.pathExists(path.join(contentPath, 'workflows'));
617 |
618 | if (!hasToolsDir) {
619 | result.issues.push({
620 | type: "warning",
621 | category: "compliance",
622 | location: { file: "application structure" },
623 | description:
624 | "No dedicated tools directory found - may impact reference documentation organization",
625 | evidence: ["Missing /tools directory"],
626 | suggestedFix:
627 | "Organize tools into dedicated directory for better reference documentation",
628 | confidence: 80,
629 | });
630 | }
631 |
632 | if (!hasTypesDir) {
633 | result.issues.push({
634 | type: "info",
635 | category: "compliance",
636 | location: { file: "application structure" },
637 | description:
638 | "No types directory found - may impact API reference documentation",
639 | evidence: ["Missing /types directory"],
640 | suggestedFix: "Consider organizing types for better API documentation",
641 | confidence: 70,
642 | });
643 | }
644 | }
645 |
646 | private async validateInlineDocumentationPatterns(
647 | contentPath: string,
648 | result: ValidationResult,
649 | ): Promise<void> {
650 | const sourceFiles = await this.getSourceFiles(contentPath);
651 |
652 | for (const file of sourceFiles) {
653 | const content = await fs.readFile(file, "utf-8");
654 |
655 | // Check for proper function documentation that could support tutorials
656 | const functions = this.extractFunctions(content);
657 | for (const func of functions) {
658 | if (func.isExported && !func.hasDocumentation) {
659 | result.issues.push({
660 | type: "warning",
661 | category: "compliance",
662 | location: { file: path.basename(file), line: func.line },
663 | description: `Exported function '${func.name}' lacks documentation`,
664 | evidence: [func.signature],
665 | suggestedFix:
666 | "Add JSDoc/TSDoc documentation to support tutorial and reference content",
667 | confidence: 85,
668 | });
669 | }
670 | }
671 |
672 | // Check for proper error handling documentation
673 | const errorPatterns = content.match(/throw new \w*Error/g);
674 | if (errorPatterns && errorPatterns.length > 0) {
675 | const hasErrorDocs =
676 | content.includes("@throws") || content.includes("@error");
677 | if (!hasErrorDocs) {
678 | result.issues.push({
679 | type: "info",
680 | category: "compliance",
681 | location: { file: path.basename(file) },
682 | description:
683 | "Error throwing code found without error documentation",
684 | evidence: errorPatterns,
685 | suggestedFix:
686 | "Document error conditions to support troubleshooting guides",
687 | confidence: 75,
688 | });
689 | }
690 | }
691 | }
692 | }
693 |
694 | private identifyDiataxisSection(filePath: string): string | null {
695 | const sections = ["tutorials", "how-to", "reference", "explanation"];
696 |
697 | for (const section of sections) {
698 | if (filePath.includes(`/${section}/`)) {
699 | return section;
700 | }
701 | }
702 |
703 | return null;
704 | }
705 |
706 | private async checkSectionCompliance(
707 | filePath: string,
708 | content: string,
709 | section: string,
710 | result: ValidationResult,
711 | ): Promise<void> {
712 | const complianceRules = this.getDiataxisComplianceRules(section);
713 |
714 | for (const rule of complianceRules) {
715 | if (!rule.check(content)) {
716 | result.issues.push({
717 | type: "warning",
718 | category: "compliance",
719 | location: { file: path.basename(filePath), section },
720 | description: rule.message,
721 | evidence: [rule.evidence?.(content) || ""],
722 | suggestedFix: rule.fix,
723 | confidence: rule.confidence,
724 | });
725 | }
726 | }
727 | }
728 |
729 | private getDiataxisComplianceRules(section: string) {
730 | const rules: any = {
731 | tutorials: [
732 | {
733 | check: (content: string) =>
734 | content.includes("## Prerequisites") ||
735 | content.includes("## Requirements"),
736 | message: "Tutorial should include prerequisites section",
737 | fix: "Add a prerequisites or requirements section",
738 | confidence: 90,
739 | },
740 | {
741 | check: (content: string) => /step|Step|STEP/.test(content),
742 | message: "Tutorial should be organized in clear steps",
743 | fix: "Structure content with numbered steps or clear progression",
744 | confidence: 85,
745 | },
746 | {
747 | check: (content: string) => content.includes("```"),
748 | message: "Tutorial should include practical code examples",
749 | fix: "Add code blocks with working examples",
750 | confidence: 80,
751 | },
752 | ],
753 | "how-to": [
754 | {
755 | check: (content: string) => /how to|How to|HOW TO/.test(content),
756 | message: "How-to guide should focus on specific tasks",
757 | fix: "Frame content around achieving specific goals",
758 | confidence: 75,
759 | },
760 | {
761 | check: (content: string) => content.length > 500,
762 | message: "How-to guide should provide detailed guidance",
763 | fix: "Expand with more detailed instructions",
764 | confidence: 70,
765 | },
766 | ],
767 | reference: [
768 | {
769 | check: (content: string) => /##|###/.test(content),
770 | message: "Reference should be well-structured with clear sections",
771 | fix: "Add proper headings and organization",
772 | confidence: 95,
773 | },
774 | {
775 | check: (content: string) => /\|.*\|/.test(content),
776 | message: "Reference should include tables for structured information",
777 | fix: "Consider using tables for parameters, options, etc.",
778 | confidence: 60,
779 | },
780 | ],
781 | explanation: [
782 | {
783 | check: (content: string) =>
784 | content.includes("why") || content.includes("Why"),
785 | message: 'Explanation should address the "why" behind concepts',
786 | fix: "Include rationale and context for decisions/concepts",
787 | confidence: 80,
788 | },
789 | {
790 | check: (content: string) => content.length > 800,
791 | message: "Explanation should provide in-depth coverage",
792 | fix: "Expand with more comprehensive explanation",
793 | confidence: 70,
794 | },
795 | ],
796 | };
797 |
798 | return rules[section] || [];
799 | }
800 |
801 | private async validateCodeExamples(
802 | contentPath: string,
803 | ): Promise<CodeValidationResult> {
804 | const files = await this.getMarkdownFiles(contentPath);
805 | const allExamples: ExampleValidation[] = [];
806 |
807 | for (const file of files) {
808 | const content = await fs.readFile(file, "utf-8");
809 | const codeBlocks = this.extractCodeBlocks(content);
810 |
811 | for (const block of codeBlocks) {
812 | if (this.isValidatableLanguage(block.language)) {
813 | const validation = await this.validateCodeBlock(block, file);
814 | allExamples.push(validation);
815 | }
816 | }
817 | }
818 |
819 | return {
820 | overallSuccess: allExamples.every((e) => e.compilationSuccess),
821 | exampleResults: allExamples,
822 | confidence: this.calculateCodeValidationConfidence(allExamples),
823 | };
824 | }
825 |
826 | private extractCodeBlocks(
827 | content: string,
828 | ): Array<{ language: string; code: string; id: string }> {
829 | const codeBlockPattern = /```(\w+)?\n([\s\S]*?)```/g;
830 | const blocks: Array<{ language: string; code: string; id: string }> = [];
831 | let match;
832 | let index = 0;
833 |
834 | while ((match = codeBlockPattern.exec(content)) !== null) {
835 | blocks.push({
836 | language: match[1] || "text",
837 | code: match[2].trim(),
838 | id: `block-${index++}`,
839 | });
840 | }
841 |
842 | return blocks;
843 | }
844 |
845 | private isValidatableLanguage(language: string): boolean {
846 | const validatable = [
847 | "javascript",
848 | "typescript",
849 | "js",
850 | "ts",
851 | "json",
852 | "bash",
853 | "sh",
854 | ];
855 | return validatable.includes(language.toLowerCase());
856 | }
857 |
858 | private async validateCodeBlock(
859 | block: { language: string; code: string; id: string },
860 | filePath: string,
861 | ): Promise<ExampleValidation> {
862 | const validation: ExampleValidation = {
863 | example: block.id,
864 | compilationSuccess: false,
865 | executionSuccess: false,
866 | issues: [],
867 | confidence: 0,
868 | };
869 |
870 | try {
871 | if (block.language === "typescript" || block.language === "ts") {
872 | await this.validateTypeScriptCode(block.code, validation);
873 | } else if (block.language === "javascript" || block.language === "js") {
874 | await this.validateJavaScriptCode(block.code, validation);
875 | } else if (block.language === "json") {
876 | await this.validateJSONCode(block.code, validation);
877 | } else if (block.language === "bash" || block.language === "sh") {
878 | await this.validateBashCode(block.code, validation);
879 | }
880 | } catch (error: any) {
881 | validation.issues.push({
882 | type: "error",
883 | category: "accuracy",
884 | location: { file: path.basename(filePath) },
885 | description: `Code validation failed: ${error.message}`,
886 | evidence: [block.code.substring(0, 100)],
887 | suggestedFix: "Review and fix syntax errors",
888 | confidence: 95,
889 | });
890 | }
891 |
892 | return validation;
893 | }
894 |
895 | private async validateTypeScriptCode(
896 | code: string,
897 | validation: ExampleValidation,
898 | ): Promise<void> {
899 | // Ensure temp directory exists
900 | await fs.mkdir(this.tempDir, { recursive: true });
901 |
902 | const tempFile = path.join(this.tempDir, `temp-${Date.now()}.ts`);
903 |
904 | try {
905 | // Write code to temporary file
906 | await fs.writeFile(tempFile, code, "utf-8");
907 |
908 | // Try to compile with TypeScript
909 | const { stderr } = await execAsync(
910 | `npx tsc --noEmit --skipLibCheck ${tempFile}`,
911 | );
912 |
913 | if (stderr && stderr.includes("error")) {
914 | validation.issues.push({
915 | type: "error",
916 | category: "accuracy",
917 | location: { file: "code-example" },
918 | description: "TypeScript compilation error",
919 | evidence: [stderr],
920 | suggestedFix: "Fix TypeScript syntax and type errors",
921 | confidence: 90,
922 | });
923 | } else {
924 | validation.compilationSuccess = true;
925 | validation.confidence = 85;
926 | }
927 | } catch (error: any) {
928 | if (error.stderr && error.stderr.includes("error")) {
929 | validation.issues.push({
930 | type: "error",
931 | category: "accuracy",
932 | location: { file: "code-example" },
933 | description: "TypeScript compilation failed",
934 | evidence: [error.stderr],
935 | suggestedFix: "Fix compilation errors",
936 | confidence: 95,
937 | });
938 | }
939 | } finally {
940 | // Clean up temp file
941 | try {
942 | await fs.unlink(tempFile);
943 | } catch {
944 | // Ignore cleanup errors
945 | }
946 | }
947 | }
948 |
949 | private async validateJavaScriptCode(
950 | code: string,
951 | validation: ExampleValidation,
952 | ): Promise<void> {
953 | try {
954 | // Basic syntax check using Node.js
955 | new Function(code);
956 | validation.compilationSuccess = true;
957 | validation.confidence = 75;
958 | } catch (error: any) {
959 | validation.issues.push({
960 | type: "error",
961 | category: "accuracy",
962 | location: { file: "code-example" },
963 | description: `JavaScript syntax error: ${error.message}`,
964 | evidence: [code.substring(0, 100)],
965 | suggestedFix: "Fix JavaScript syntax errors",
966 | confidence: 90,
967 | });
968 | }
969 | }
970 |
971 | private async validateJSONCode(
972 | code: string,
973 | validation: ExampleValidation,
974 | ): Promise<void> {
975 | try {
976 | JSON.parse(code);
977 | validation.compilationSuccess = true;
978 | validation.confidence = 95;
979 | } catch (error: any) {
980 | validation.issues.push({
981 | type: "error",
982 | category: "accuracy",
983 | location: { file: "code-example" },
984 | description: `Invalid JSON: ${error.message}`,
985 | evidence: [code.substring(0, 100)],
986 | suggestedFix: "Fix JSON syntax errors",
987 | confidence: 100,
988 | });
989 | }
990 | }
991 |
992 | private async validateBashCode(
993 | code: string,
994 | validation: ExampleValidation,
995 | ): Promise<void> {
996 | // Basic bash syntax validation
997 | const lines = code
998 | .split("\n")
999 | .filter((line) => line.trim() && !line.startsWith("#"));
1000 |
1001 | for (const line of lines) {
1002 | // Check for basic syntax issues
1003 | if (line.includes("&&") && line.includes("||")) {
1004 | validation.issues.push({
1005 | type: "warning",
1006 | category: "accuracy",
1007 | location: { file: "code-example" },
1008 | description: "Complex command chaining may be confusing",
1009 | evidence: [line],
1010 | suggestedFix:
1011 | "Consider breaking into separate commands or adding explanation",
1012 | confidence: 60,
1013 | });
1014 | }
1015 |
1016 | // Check for unquoted variables in dangerous contexts
1017 | if (line.includes("rm") && /\$\w+/.test(line) && !/'.*\$.*'/.test(line)) {
1018 | validation.issues.push({
1019 | type: "warning",
1020 | category: "accuracy",
1021 | location: { file: "code-example" },
1022 | description: "Unquoted variable in potentially dangerous command",
1023 | evidence: [line],
1024 | suggestedFix: "Quote variables to prevent word splitting",
1025 | confidence: 80,
1026 | });
1027 | }
1028 | }
1029 |
1030 | validation.compilationSuccess =
1031 | validation.issues.filter((i) => i.type === "error").length === 0;
1032 | validation.confidence = validation.compilationSuccess ? 70 : 20;
1033 | }
1034 |
1035 | private calculateCodeValidationConfidence(
1036 | examples: ExampleValidation[],
1037 | ): number {
1038 | if (examples.length === 0) return 0;
1039 |
1040 | const totalConfidence = examples.reduce(
1041 | (sum, ex) => sum + ex.confidence,
1042 | 0,
1043 | );
1044 | return Math.round(totalConfidence / examples.length);
1045 | }
1046 |
1047 | public async getMarkdownFiles(
1048 | contentPath: string,
1049 | maxDepth: number = 5,
1050 | ): Promise<string[]> {
1051 | const files: string[] = [];
1052 | const excludedDirs = new Set([
1053 | "node_modules",
1054 | ".git",
1055 | "dist",
1056 | "build",
1057 | ".next",
1058 | ".nuxt",
1059 | "coverage",
1060 | ".tmp",
1061 | "tmp",
1062 | ".cache",
1063 | ".vscode",
1064 | ".idea",
1065 | "logs",
1066 | ".logs",
1067 | ".npm",
1068 | ".yarn",
1069 | ]);
1070 |
1071 | const scan = async (
1072 | dir: string,
1073 | currentDepth: number = 0,
1074 | ): Promise<void> => {
1075 | if (currentDepth > maxDepth) return;
1076 |
1077 | try {
1078 | const entries = await fs.readdir(dir, { withFileTypes: true });
1079 |
1080 | for (const entry of entries) {
1081 | const fullPath = path.join(dir, entry.name);
1082 |
1083 | if (entry.isDirectory()) {
1084 | // Skip excluded directories
1085 | if (excludedDirs.has(entry.name) || entry.name.startsWith(".")) {
1086 | continue;
1087 | }
1088 |
1089 | // Prevent symlink loops
1090 | try {
1091 | const stats = await fs.lstat(fullPath);
1092 | if (stats.isSymbolicLink()) {
1093 | continue;
1094 | }
1095 | } catch {
1096 | continue;
1097 | }
1098 |
1099 | await scan(fullPath, currentDepth + 1);
1100 | } else if (entry.name.endsWith(".md")) {
1101 | files.push(fullPath);
1102 |
1103 | // Limit total files to prevent memory issues
1104 | if (files.length > 500) {
1105 | console.warn("Markdown file limit reached (500), stopping scan");
1106 | return;
1107 | }
1108 | }
1109 | }
1110 | } catch (error) {
1111 | // Skip directories that can't be read
1112 | console.warn(`Warning: Could not read directory ${dir}:`, error);
1113 | }
1114 | };
1115 |
1116 | try {
1117 | await scan(contentPath);
1118 | } catch (error) {
1119 | console.warn("Error scanning directory:", error);
1120 | }
1121 |
1122 | return files;
1123 | }
1124 |
1125 | private async getSourceFiles(
1126 | contentPath: string,
1127 | maxDepth: number = 5,
1128 | ): Promise<string[]> {
1129 | const files: string[] = [];
1130 | const excludedDirs = new Set([
1131 | "node_modules",
1132 | ".git",
1133 | "dist",
1134 | "build",
1135 | ".next",
1136 | ".nuxt",
1137 | "coverage",
1138 | ".tmp",
1139 | "tmp",
1140 | ".cache",
1141 | ".vscode",
1142 | ".idea",
1143 | "logs",
1144 | ".logs",
1145 | ".npm",
1146 | ".yarn",
1147 | ]);
1148 |
1149 | const scan = async (
1150 | dir: string,
1151 | currentDepth: number = 0,
1152 | ): Promise<void> => {
1153 | if (currentDepth > maxDepth) return;
1154 |
1155 | try {
1156 | const entries = await fs.readdir(dir, { withFileTypes: true });
1157 |
1158 | for (const entry of entries) {
1159 | const fullPath = path.join(dir, entry.name);
1160 |
1161 | if (entry.isDirectory()) {
1162 | // Skip excluded directories
1163 | if (excludedDirs.has(entry.name) || entry.name.startsWith(".")) {
1164 | continue;
1165 | }
1166 |
1167 | // Prevent symlink loops
1168 | try {
1169 | const stats = await fs.lstat(fullPath);
1170 | if (stats.isSymbolicLink()) {
1171 | continue;
1172 | }
1173 | } catch {
1174 | continue;
1175 | }
1176 |
1177 | await scan(fullPath, currentDepth + 1);
1178 | } else if (
1179 | entry.name.endsWith(".ts") ||
1180 | entry.name.endsWith(".js") ||
1181 | entry.name.endsWith(".md")
1182 | ) {
1183 | files.push(fullPath);
1184 |
1185 | // Limit total files to prevent memory issues
1186 | if (files.length > 1000) {
1187 | console.warn("File limit reached (1000), stopping scan");
1188 | return;
1189 | }
1190 | }
1191 | }
1192 | } catch (error) {
1193 | // Skip directories that can't be read
1194 | console.warn(`Warning: Could not read directory ${dir}:`, error);
1195 | }
1196 | };
1197 |
1198 | try {
1199 | await scan(contentPath);
1200 | } catch (error) {
1201 | console.warn("Error scanning directory:", error);
1202 | }
1203 |
1204 | return files;
1205 | }
1206 |
1207 | private async pathExists(filePath: string): Promise<boolean> {
1208 | try {
1209 | await fs.access(filePath);
1210 | return true;
1211 | } catch {
1212 | return false;
1213 | }
1214 | }
1215 |
1216 | private extractFunctions(content: string): Array<{
1217 | name: string;
1218 | line: number;
1219 | signature: string;
1220 | isExported: boolean;
1221 | hasDocumentation: boolean;
1222 | }> {
1223 | const functions: Array<{
1224 | name: string;
1225 | line: number;
1226 | signature: string;
1227 | isExported: boolean;
1228 | hasDocumentation: boolean;
1229 | }> = [];
1230 | const lines = content.split("\n");
1231 |
1232 | for (let i = 0; i < lines.length; i++) {
1233 | const line = lines[i];
1234 |
1235 | // Match function declarations and exports
1236 | const functionMatch = line.match(
1237 | /^(export\s+)?(async\s+)?function\s+(\w+)/,
1238 | );
1239 | const arrowMatch = line.match(
1240 | /^(export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(async\s+)?\(/,
1241 | );
1242 |
1243 | if (functionMatch) {
1244 | const name = functionMatch[3];
1245 | const isExported = !!functionMatch[1];
1246 | const hasDocumentation = this.checkForDocumentation(lines, i);
1247 |
1248 | functions.push({
1249 | name,
1250 | line: i + 1,
1251 | signature: line.trim(),
1252 | isExported,
1253 | hasDocumentation,
1254 | });
1255 | } else if (arrowMatch) {
1256 | const name = arrowMatch[2];
1257 | const isExported = !!arrowMatch[1];
1258 | const hasDocumentation = this.checkForDocumentation(lines, i);
1259 |
1260 | functions.push({
1261 | name,
1262 | line: i + 1,
1263 | signature: line.trim(),
1264 | isExported,
1265 | hasDocumentation,
1266 | });
1267 | }
1268 | }
1269 |
1270 | return functions;
1271 | }
1272 |
1273 | private async checkInlineDocumentationQuality(
1274 | _file: string,
1275 | _content: string,
1276 | _result: ValidationResult,
1277 | ): Promise<void> {
1278 | // Implementation for checking JSDoc/TSDoc quality
1279 | // This could check for proper parameter documentation, return types, etc.
1280 | }
1281 |
1282 | private async validateReadmeStructure(
1283 | _file: string,
1284 | content: string,
1285 | result: ValidationResult,
1286 | ): Promise<void> {
1287 | // Check if README follows good structure
1288 | const hasTitle = /^#\s+/.test(content);
1289 | const hasDescription =
1290 | content.includes("## Description") || content.includes("## Overview");
1291 | const hasInstallation =
1292 | content.includes("## Installation") || content.includes("## Setup");
1293 | const hasUsage =
1294 | content.includes("## Usage") || content.includes("## Getting Started");
1295 |
1296 | if (!hasTitle) {
1297 | result.issues.push({
1298 | type: "warning",
1299 | category: "compliance",
1300 | location: { file: "README.md" },
1301 | description: "README missing clear title",
1302 | evidence: ["No H1 heading found"],
1303 | suggestedFix: "Add clear title with # heading",
1304 | confidence: 90,
1305 | });
1306 | }
1307 |
1308 | if (!hasDescription && !hasInstallation && !hasUsage) {
1309 | result.issues.push({
1310 | type: "warning",
1311 | category: "compliance",
1312 | location: { file: "README.md" },
1313 | description:
1314 | "README lacks essential sections (description, installation, usage)",
1315 | evidence: ["Missing standard README sections"],
1316 | suggestedFix:
1317 | "Add sections for description, installation, and usage following Diataxis principles",
1318 | confidence: 85,
1319 | });
1320 | }
1321 | }
1322 |
1323 | private async checkModuleDocumentation(
1324 | _file: string,
1325 | _content: string,
1326 | _result: ValidationResult,
1327 | ): Promise<void> {
1328 | // Implementation for checking module-level documentation
1329 | // This could check for file-level JSDoc, proper exports documentation, etc.
1330 | }
1331 |
1332 | private checkForDocumentation(
1333 | lines: string[],
1334 | functionLineIndex: number,
1335 | ): boolean {
1336 | // Look backwards from the function line to find documentation
1337 | let checkIndex = functionLineIndex - 1;
1338 |
1339 | // Skip empty lines
1340 | while (checkIndex >= 0 && lines[checkIndex].trim() === "") {
1341 | checkIndex--;
1342 | }
1343 |
1344 | // Check if we found the end of a JSDoc comment
1345 | if (checkIndex >= 0 && lines[checkIndex].trim() === "*/") {
1346 | // Look backwards to find the start of the JSDoc block
1347 | let jsDocStart = checkIndex;
1348 | while (jsDocStart >= 0) {
1349 | const line = lines[jsDocStart].trim();
1350 | if (line.startsWith("/**")) {
1351 | return true; // Found complete JSDoc block
1352 | }
1353 | if (!line.startsWith("*") && line !== "*/") {
1354 | break; // Not part of JSDoc block
1355 | }
1356 | jsDocStart--;
1357 | }
1358 | }
1359 |
1360 | // Also check for single-line JSDoc comments
1361 | if (
1362 | checkIndex >= 0 &&
1363 | lines[checkIndex].trim().startsWith("/**") &&
1364 | lines[checkIndex].includes("*/")
1365 | ) {
1366 | return true;
1367 | }
1368 |
1369 | return false;
1370 | }
1371 |
1372 | private async shouldAnalyzeApplicationCode(
1373 | contentPath: string,
1374 | ): Promise<boolean> {
1375 | // Check if the path contains application source code vs documentation
1376 | const hasSrcDir = await this.pathExists(path.join(contentPath, "src"));
1377 | const hasPackageJson = await this.pathExists(
1378 | path.join(contentPath, "package.json"),
1379 | );
1380 | const hasTypescriptFiles = (await this.getSourceFiles(contentPath)).some(
1381 | (file) => file.endsWith(".ts"),
1382 | );
1383 |
1384 | // If path ends with 'src' or is a project root with src/, analyze as application code
1385 | if (
1386 | contentPath.endsWith("/src") ||
1387 | contentPath.endsWith("\\src") ||
1388 | (hasSrcDir && hasPackageJson)
1389 | ) {
1390 | return true;
1391 | }
1392 |
1393 | // If path contains TypeScript/JavaScript files and package.json, treat as application code
1394 | if (hasTypescriptFiles && hasPackageJson) {
1395 | return true;
1396 | }
1397 |
1398 | // If path is specifically a documentation directory, analyze as documentation
1399 | if (contentPath.includes("/docs") || contentPath.includes("\\docs")) {
1400 | return false;
1401 | }
1402 |
1403 | return false;
1404 | }
1405 |
1406 | private async analyzeDiataxisStructure(
1407 | contentPath: string,
1408 | ): Promise<{ sections: string[] }> {
1409 | const sections: string[] = [];
1410 |
1411 | try {
1412 | const entries = await fs.readdir(contentPath, { withFileTypes: true });
1413 |
1414 | for (const entry of entries) {
1415 | if (entry.isDirectory()) {
1416 | const dirName = entry.name;
1417 | if (
1418 | ["tutorials", "how-to", "reference", "explanation"].includes(
1419 | dirName,
1420 | )
1421 | ) {
1422 | sections.push(dirName);
1423 | }
1424 | }
1425 | }
1426 | } catch {
1427 | // Directory doesn't exist
1428 | }
1429 |
1430 | return { sections };
1431 | }
1432 |
1433 | private updateAccuracyConfidence(result: ValidationResult): void {
1434 | const errorCount = result.issues.filter((i) => i.type === "error").length;
1435 | const warningCount = result.issues.filter(
1436 | (i) => i.type === "warning",
1437 | ).length;
1438 |
1439 | // Base confidence starts high and decreases with issues
1440 | let confidence = 95;
1441 | confidence -= errorCount * 20;
1442 | confidence -= warningCount * 5;
1443 | confidence = Math.max(0, confidence);
1444 |
1445 | result.confidence.breakdown.technologyDetection = confidence;
1446 | }
1447 |
1448 | private calculateOverallMetrics(result: ValidationResult): void {
1449 | const breakdown = result.confidence.breakdown;
1450 | const values = Object.values(breakdown).filter((v) => v > 0);
1451 |
1452 | if (values.length > 0) {
1453 | result.confidence.overall = Math.round(
1454 | values.reduce((a, b) => a + b, 0) / values.length,
1455 | );
1456 | }
1457 |
1458 | // Determine overall success
1459 | const criticalIssues = result.issues.filter(
1460 | (i) => i.type === "error",
1461 | ).length;
1462 | result.success = criticalIssues === 0;
1463 |
1464 | // Add risk factors based on issues
1465 | if (criticalIssues > 0) {
1466 | result.confidence.riskFactors.push({
1467 | type: "high",
1468 | category: "accuracy",
1469 | description: `${criticalIssues} critical accuracy issues found`,
1470 | impact: "Users may encounter broken examples or incorrect information",
1471 | mitigation: "Fix all critical issues before publication",
1472 | });
1473 | }
1474 |
1475 | const uncertaintyCount = result.uncertainties.length;
1476 | if (uncertaintyCount > 5) {
1477 | result.confidence.riskFactors.push({
1478 | type: "medium",
1479 | category: "completeness",
1480 | description: `${uncertaintyCount} areas requiring clarification`,
1481 | impact: "Documentation may lack specificity for user context",
1482 | mitigation: "Address high-priority uncertainties with user input",
1483 | });
1484 | }
1485 | }
1486 |
1487 | private generateRecommendations(
1488 | result: ValidationResult,
1489 | _options: ValidationOptions,
1490 | ): void {
1491 | const recommendations: string[] = [];
1492 | const nextSteps: string[] = [];
1493 |
1494 | // Generate recommendations based on issues found
1495 | const errorCount = result.issues.filter((i) => i.type === "error").length;
1496 | if (errorCount > 0) {
1497 | recommendations.push(
1498 | `Fix ${errorCount} critical accuracy issues before publication`,
1499 | );
1500 | nextSteps.push("Review and resolve all error-level validation issues");
1501 | }
1502 |
1503 | const warningCount = result.issues.filter(
1504 | (i) => i.type === "warning",
1505 | ).length;
1506 | if (warningCount > 0) {
1507 | recommendations.push(
1508 | `Address ${warningCount} potential accuracy concerns`,
1509 | );
1510 | nextSteps.push(
1511 | "Review warning-level issues and apply fixes where appropriate",
1512 | );
1513 | }
1514 |
1515 | const uncertaintyCount = result.uncertainties.filter(
1516 | (u) => u.severity === "high" || u.severity === "critical",
1517 | ).length;
1518 | if (uncertaintyCount > 0) {
1519 | recommendations.push(
1520 | `Clarify ${uncertaintyCount} high-uncertainty areas`,
1521 | );
1522 | nextSteps.push("Gather user input on areas flagged for clarification");
1523 | }
1524 |
1525 | // Code validation recommendations
1526 | if (result.codeValidation && !result.codeValidation.overallSuccess) {
1527 | recommendations.push(
1528 | "Fix code examples that fail compilation or execution tests",
1529 | );
1530 | nextSteps.push(
1531 | "Test all code examples in appropriate development environment",
1532 | );
1533 | }
1534 |
1535 | // Completeness recommendations
1536 | const missingCompliance = result.issues.filter(
1537 | (i) => i.category === "compliance",
1538 | ).length;
1539 | if (missingCompliance > 0) {
1540 | recommendations.push(
1541 | "Improve Diataxis framework compliance for better user experience",
1542 | );
1543 | nextSteps.push(
1544 | "Restructure content to better align with Diataxis principles",
1545 | );
1546 | }
1547 |
1548 | // General recommendations based on confidence level
1549 | if (result.confidence.overall < 70) {
1550 | recommendations.push(
1551 | "Overall confidence is below recommended threshold - consider comprehensive review",
1552 | );
1553 | nextSteps.push(
1554 | "Conduct manual review of generated content before publication",
1555 | );
1556 | }
1557 |
1558 | if (recommendations.length === 0) {
1559 | recommendations.push("Content validation passed - ready for publication");
1560 | nextSteps.push("Deploy documentation and monitor for user feedback");
1561 | }
1562 |
1563 | result.recommendations = recommendations;
1564 | result.nextSteps = nextSteps;
1565 | }
1566 | }
1567 |
1568 | export const validateDiataxisContent: Tool = {
1569 | name: "validate_diataxis_content",
1570 | description:
1571 | "Validate the accuracy, completeness, and compliance of generated Diataxis documentation",
1572 | inputSchema: {
1573 | type: "object",
1574 | properties: {
1575 | contentPath: {
1576 | type: "string",
1577 | description: "Path to the documentation directory to validate",
1578 | },
1579 | analysisId: {
1580 | type: "string",
1581 | description:
1582 | "Optional repository analysis ID for context-aware validation",
1583 | },
1584 | validationType: {
1585 | type: "string",
1586 | enum: ["accuracy", "completeness", "compliance", "all"],
1587 | default: "all",
1588 | description: "Type of validation to perform",
1589 | },
1590 | includeCodeValidation: {
1591 | type: "boolean",
1592 | default: true,
1593 | description: "Whether to validate code examples for correctness",
1594 | },
1595 | confidence: {
1596 | type: "string",
1597 | enum: ["strict", "moderate", "permissive"],
1598 | default: "moderate",
1599 | description:
1600 | "Validation confidence level - stricter levels catch more issues",
1601 | },
1602 | },
1603 | required: ["contentPath"],
1604 | },
1605 | };
1606 |
1607 | /**
1608 | * Validates Diataxis-compliant documentation content for accuracy, completeness, and compliance.
1609 | *
1610 | * Performs comprehensive validation of documentation content including accuracy verification,
1611 | * completeness assessment, compliance checking, and code example validation. Uses advanced
1612 | * confidence scoring and risk assessment to provide detailed validation results with
1613 | * actionable recommendations.
1614 | *
1615 | * @param args - The validation parameters
1616 | * @param args.contentPath - Path to the documentation content directory
1617 | * @param args.analysisId - Optional repository analysis ID for context-aware validation
1618 | * @param args.validationType - Type of validation to perform: "accuracy", "completeness", "compliance", or "all"
1619 | * @param args.includeCodeValidation - Whether to validate code examples and syntax
1620 | * @param args.confidence - Validation confidence level: "strict", "moderate", or "permissive"
1621 | *
1622 | * @returns Promise resolving to comprehensive validation results
1623 | * @returns success - Whether validation passed overall
1624 | * @returns confidence - Confidence metrics and risk assessment
1625 | * @returns issues - Array of validation issues found
1626 | * @returns uncertainties - Areas requiring clarification
1627 | * @returns codeValidation - Code example validation results
1628 | * @returns recommendations - Suggested improvements
1629 | * @returns nextSteps - Recommended next actions
1630 | *
1631 | * @throws {Error} When content path is inaccessible
1632 | * @throws {Error} When validation processing fails
1633 | *
1634 | * @example
1635 | * ```typescript
1636 | * // Comprehensive validation
1637 | * const result = await handleValidateDiataxisContent({
1638 | * contentPath: "./docs",
1639 | * validationType: "all",
1640 | * includeCodeValidation: true,
1641 | * confidence: "moderate"
1642 | * });
1643 | *
1644 | * console.log(`Validation success: ${result.success}`);
1645 | * console.log(`Overall confidence: ${result.confidence.overall}%`);
1646 | * console.log(`Issues found: ${result.issues.length}`);
1647 | *
1648 | * // Strict accuracy validation
1649 | * const accuracy = await handleValidateDiataxisContent({
1650 | * contentPath: "./docs",
1651 | * validationType: "accuracy",
1652 | * confidence: "strict"
1653 | * });
1654 | * ```
1655 | *
1656 | * @since 1.0.0
1657 | */
1658 | export async function handleValidateDiataxisContent(
1659 | args: any,
1660 | context?: any,
1661 | ): Promise<ValidationResult> {
1662 | await context?.info?.("🔍 Starting Diataxis content validation...");
1663 |
1664 | const validator = new ContentAccuracyValidator();
1665 |
1666 | // Add timeout protection to prevent infinite hangs
1667 | const timeoutMs = 120000; // 2 minutes
1668 | let timeoutHandle: NodeJS.Timeout;
1669 | const timeoutPromise = new Promise<ValidationResult>((_, reject) => {
1670 | timeoutHandle = setTimeout(() => {
1671 | reject(
1672 | new Error(
1673 | `Validation timed out after ${
1674 | timeoutMs / 1000
1675 | } seconds. This may be due to a large directory structure. Try validating a smaller subset or specific directory.`,
1676 | ),
1677 | );
1678 | }, timeoutMs);
1679 | });
1680 |
1681 | const validationPromise = validator.validateContent(args, context);
1682 |
1683 | try {
1684 | const result = await Promise.race([validationPromise, timeoutPromise]);
1685 | clearTimeout(timeoutHandle!);
1686 | return result;
1687 | } catch (error: any) {
1688 | clearTimeout(timeoutHandle!);
1689 | // Return a partial result with error information
1690 | return {
1691 | success: false,
1692 | confidence: {
1693 | overall: 0,
1694 | breakdown: {
1695 | technologyDetection: 0,
1696 | frameworkVersionAccuracy: 0,
1697 | codeExampleRelevance: 0,
1698 | architecturalAssumptions: 0,
1699 | businessContextAlignment: 0,
1700 | },
1701 | riskFactors: [
1702 | {
1703 | type: "high",
1704 | category: "validation",
1705 | description: "Validation process failed or timed out",
1706 | impact: "Unable to complete content validation",
1707 | mitigation:
1708 | "Try validating a smaller directory or specific subset of files",
1709 | },
1710 | ],
1711 | },
1712 | issues: [],
1713 | uncertainties: [],
1714 | recommendations: [
1715 | "Validation failed or timed out",
1716 | "Consider validating smaller directory subsets",
1717 | "Check for very large files or deep directory structures",
1718 | `Error: ${error.message}`,
1719 | ],
1720 | nextSteps: [
1721 | "Verify the content path is correct and accessible",
1722 | "Try validating specific subdirectories instead of the entire project",
1723 | "Check for circular symlinks or very deep directory structures",
1724 | ],
1725 | };
1726 | }
1727 | }
1728 |
1729 | interface GeneralValidationResult {
1730 | success: boolean;
1731 | linksChecked: number;
1732 | brokenLinks: string[];
1733 | codeBlocksValidated: number;
1734 | codeErrors: string[];
1735 | recommendations: string[];
1736 | summary: string;
1737 | }
1738 |
1739 | export async function validateGeneralContent(
1740 | args: any,
1741 | ): Promise<GeneralValidationResult> {
1742 | const {
1743 | contentPath,
1744 | validationType = "all",
1745 | includeCodeValidation = true,
1746 | followExternalLinks = false,
1747 | } = args;
1748 |
1749 | const result: GeneralValidationResult = {
1750 | success: true,
1751 | linksChecked: 0,
1752 | brokenLinks: [],
1753 | codeBlocksValidated: 0,
1754 | codeErrors: [],
1755 | recommendations: [],
1756 | summary: "",
1757 | };
1758 |
1759 | try {
1760 | // Get all markdown files
1761 | const validator = new ContentAccuracyValidator();
1762 | const files = await validator.getMarkdownFiles(contentPath);
1763 |
1764 | // Check links if requested
1765 | if (validationType === "all" || validationType === "links") {
1766 | for (const file of files) {
1767 | const content = await fs.readFile(file, "utf-8");
1768 | const links = extractLinksFromMarkdown(content);
1769 |
1770 | for (const link of links) {
1771 | result.linksChecked++;
1772 |
1773 | // Skip external links unless explicitly requested
1774 | if (link.startsWith("http") && !followExternalLinks) continue;
1775 |
1776 | // Check internal links
1777 | if (!link.startsWith("http")) {
1778 | const fullPath = path.resolve(path.dirname(file), link);
1779 | try {
1780 | await fs.access(fullPath);
1781 | } catch {
1782 | result.brokenLinks.push(`${path.basename(file)}: ${link}`);
1783 | result.success = false;
1784 | }
1785 | }
1786 | }
1787 | }
1788 | }
1789 |
1790 | // Validate code blocks if requested
1791 | if (
1792 | includeCodeValidation &&
1793 | (validationType === "all" || validationType === "code")
1794 | ) {
1795 | for (const file of files) {
1796 | const content = await fs.readFile(file, "utf-8");
1797 | const codeBlocks = extractCodeBlocks(content);
1798 |
1799 | for (const block of codeBlocks) {
1800 | result.codeBlocksValidated++;
1801 |
1802 | // Basic syntax validation
1803 | if (block.language && block.code.trim()) {
1804 | if (block.language === "javascript" || block.language === "js") {
1805 | try {
1806 | // Basic JS syntax check - look for common issues
1807 | if (
1808 | block.code.includes("console.log") &&
1809 | !block.code.includes(";")
1810 | ) {
1811 | result.codeErrors.push(
1812 | `${path.basename(file)}: Missing semicolon in JS code`,
1813 | );
1814 | }
1815 | } catch (error) {
1816 | result.codeErrors.push(
1817 | `${path.basename(file)}: JS syntax error - ${error}`,
1818 | );
1819 | result.success = false;
1820 | }
1821 | }
1822 | }
1823 | }
1824 | }
1825 | }
1826 |
1827 | // Generate recommendations
1828 | if (result.brokenLinks.length > 0) {
1829 | result.recommendations.push(
1830 | `Fix ${result.brokenLinks.length} broken internal links`,
1831 | );
1832 | result.recommendations.push(
1833 | "Run documentation build to catch additional link issues",
1834 | );
1835 | }
1836 |
1837 | if (result.codeErrors.length > 0) {
1838 | result.recommendations.push(
1839 | `Review and fix ${result.codeErrors.length} code syntax issues`,
1840 | );
1841 | }
1842 |
1843 | if (result.success) {
1844 | result.recommendations.push(
1845 | "Content validation passed - no critical issues found",
1846 | );
1847 | }
1848 |
1849 | // Create summary
1850 | result.summary = `Validated ${files.length} files, ${
1851 | result.linksChecked
1852 | } links, ${result.codeBlocksValidated} code blocks. ${
1853 | result.success
1854 | ? "PASSED"
1855 | : `ISSUES FOUND: ${
1856 | result.brokenLinks.length + result.codeErrors.length
1857 | }`
1858 | }`;
1859 |
1860 | return result;
1861 | } catch (error) {
1862 | result.success = false;
1863 | result.recommendations.push(`Validation failed: ${error}`);
1864 | result.summary = `Validation error: ${error}`;
1865 | return result;
1866 | }
1867 | }
1868 |
1869 | // Helper function to extract links from markdown
1870 | function extractLinksFromMarkdown(content: string): string[] {
1871 | const linkRegex = /\[([^\]]*)\]\(([^)]+)\)/g;
1872 | const links: string[] = [];
1873 | let match;
1874 |
1875 | while ((match = linkRegex.exec(content)) !== null) {
1876 | links.push(match[2]); // The URL part
1877 | }
1878 |
1879 | return links;
1880 | }
1881 |
1882 | // Helper function to extract code blocks from markdown
1883 | function extractCodeBlocks(
1884 | content: string,
1885 | ): { language: string; code: string }[] {
1886 | const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
1887 | const blocks: { language: string; code: string }[] = [];
1888 | let match;
1889 |
1890 | while ((match = codeBlockRegex.exec(content)) !== null) {
1891 | blocks.push({
1892 | language: match[1] || "text",
1893 | code: match[2],
1894 | });
1895 | }
1896 |
1897 | return blocks;
1898 | }
1899 |
```