#
tokens: 35779/50000 6/88 files (page 3/4)
lines: off (toggle) GitHub
raw markdown copy
This is page 3 of 4. Use http://codebase.md/marianfoo/mcp-sap-docs?page={x} to view the full context.

# Directory Structure

```
├── .cursor
│   └── rules
│       ├── 00-overview.mdc
│       ├── 10-search-stack.mdc
│       ├── 20-tools-and-apis.mdc
│       ├── 30-tests-and-output.mdc
│       ├── 40-deploy.mdc
│       ├── 50-metadata-config.mdc
│       ├── 60-adding-github-sources.mdc
│       ├── 70-tool-usage-guide.mdc
│       └── 80-abap-integration.mdc
├── .cursorignore
├── .gitattributes
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── config.yml
│   │   ├── missing-documentation.yml
│   │   └── new-documentation-source.yml
│   └── workflows
│       ├── deploy-mcp-sap-docs.yml
│       ├── test-pr.yml
│       └── update-submodules.yml
├── .gitignore
├── .gitmodules
├── .npmignore
├── .vscode
│   ├── extensions.json
│   └── settings.json
├── docs
│   ├── ABAP-INTEGRATION-SUMMARY.md
│   ├── ABAP-MULTI-VERSION-INTEGRATION.md
│   ├── ABAP-STANDARD-INTEGRATION.md
│   ├── ABAP-USAGE-GUIDE.md
│   ├── ARCHITECTURE.md
│   ├── COMMUNITY-SEARCH-IMPLEMENTATION.md
│   ├── CONTENT-SIZE-LIMITS.md
│   ├── CURSOR-SETUP.md
│   ├── DEV.md
│   ├── FTS5-IMPLEMENTATION-COMPLETE.md
│   ├── LLM-FRIENDLY-IMPROVEMENTS.md
│   ├── METADATA-CONSOLIDATION.md
│   ├── TEST-SEARCH.md
│   └── TESTS.md
├── ecosystem.config.cjs
├── index.html
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── REMOTE_SETUP.md
├── scripts
│   ├── build-fts.ts
│   ├── build-index.ts
│   ├── check-version.js
│   └── summarize-src.js
├── server.json
├── setup.sh
├── src
│   ├── global.d.ts
│   ├── http-server.ts
│   ├── lib
│   │   ├── BaseServerHandler.ts
│   │   ├── communityBestMatch.ts
│   │   ├── config.ts
│   │   ├── localDocs.ts
│   │   ├── logger.ts
│   │   ├── metadata.ts
│   │   ├── sapHelp.ts
│   │   ├── search.ts
│   │   ├── searchDb.ts
│   │   ├── truncate.ts
│   │   ├── types.ts
│   │   └── url-generation
│   │       ├── abap.ts
│   │       ├── BaseUrlGenerator.ts
│   │       ├── cap.ts
│   │       ├── cloud-sdk.ts
│   │       ├── dsag.ts
│   │       ├── GenericUrlGenerator.ts
│   │       ├── index.ts
│   │       ├── README.md
│   │       ├── sapui5.ts
│   │       ├── utils.ts
│   │       └── wdi5.ts
│   ├── metadata.json
│   ├── server.ts
│   └── streamable-http-server.ts
├── test
│   ├── _utils
│   │   ├── httpClient.js
│   │   └── parseResults.js
│   ├── community-search.ts
│   ├── comprehensive-url-generation.test.ts
│   ├── performance
│   │   └── README.md
│   ├── prompts.test.ts
│   ├── quick-url-test.ts
│   ├── README.md
│   ├── tools
│   │   ├── run-tests.js
│   │   ├── sap_docs_search
│   │   │   ├── search-cap-docs.js
│   │   │   ├── search-cloud-sdk-ai.js
│   │   │   ├── search-cloud-sdk-js.js
│   │   │   └── search-sapui5-docs.js
│   │   ├── search-url-verification.js
│   │   ├── search.generic.spec.js
│   │   └── search.smoke.js
│   ├── url-status.ts
│   └── validate-urls.ts
├── test-community-search.js
├── test-search-interactive.ts
├── test-search.http
├── test-search.ts
├── tsconfig.json
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/scripts/summarize-src.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Configuration
const SRC_DIR = path.join(__dirname, '..', 'src');
const TEST_DIR = path.join(__dirname, '..', 'test');
const SRC_OUTPUT_FILE = path.join(__dirname, '..', 'src_context.txt');
const TEST_OUTPUT_FILE = path.join(__dirname, '..', 'test_context.txt');

// Content inclusion settings
const INCLUDE_CONTENT = true;
const MAX_CONTENT_SIZE = 50000; // Max characters per file to include
const CONTENT_PREVIEW_SIZE = 500; // Characters for preview when file is too large

// File type patterns
const FILE_PATTERNS = {
  typescript: /\.(ts|tsx)$/,
  javascript: /\.(js|jsx)$/,
  json: /\.json$/,
  markdown: /\.(md|mdx)$/,
  yaml: /\.(yml|yaml)$/,
  xml: /\.xml$/,
  html: /\.html$/,
  css: /\.css$/,
  scss: /\.scss$/,
  sql: /\.sql$/,
  config: /\.(config|conf)$/
};

// Function to get file stats
async function getFileStats(filePath) {
  try {
    const stats = await fs.stat(filePath);
    return {
      size: stats.size,
      created: stats.birthtime,
      modified: stats.mtime,
      isDirectory: stats.isDirectory()
    };
  } catch (error) {
    return null;
  }
}

// Function to get file type
function getFileType(filename) {
  for (const [type, pattern] of Object.entries(FILE_PATTERNS)) {
    if (pattern.test(filename)) {
      return type;
    }
  }
  return 'other';
}

// Function to count lines in a file
async function countLines(filePath) {
  try {
    const content = await fs.readFile(filePath, 'utf-8');
    return content.split('\n').length;
  } catch (error) {
    return 0;
  }
}

// Function to extract imports from TypeScript/JavaScript files
async function extractImports(filePath) {
  try {
    const content = await fs.readFile(filePath, 'utf-8');
    const importRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)(?:\s*,\s*(?:\{[^}]*\}|\*\s+as\s+\w+|\w+))*\s+from\s+)?['"`]([^'"`]+)['"`]/g;
    const imports = [];
    let match;
    
    while ((match = importRegex.exec(content)) !== null) {
      imports.push(match[1]);
    }
    
    return imports;
  } catch (error) {
    return [];
  }
}

// Function to extract exports from TypeScript/JavaScript files
async function extractExports(filePath) {
  try {
    const content = await fs.readFile(filePath, 'utf-8');
    const exports = [];
    
    // Named exports: export { name1, name2 }
    const namedExportRegex = /export\s*\{\s*([^}]+)\s*\}/g;
    let match;
    while ((match = namedExportRegex.exec(content)) !== null) {
      const names = match[1].split(',').map(n => n.trim().split(' as ')[0].trim());
      exports.push(...names);
    }
    
    // Function/class exports: export function name() {} or export class Name {}
    const functionClassRegex = /export\s+(?:async\s+)?(?:function|class)\s+(\w+)/g;
    while ((match = functionClassRegex.exec(content)) !== null) {
      exports.push(match[1]);
    }
    
    // Variable exports: export const name = ...
    const variableRegex = /export\s+(?:const|let|var)\s+(\w+)/g;
    while ((match = variableRegex.exec(content)) !== null) {
      exports.push(match[1]);
    }
    
    // Default export
    if (content.includes('export default')) {
      exports.push('default');
    }
    
    return [...new Set(exports)]; // Remove duplicates
  } catch (error) {
    return [];
  }
}

// Function to read file content with size limit
async function getFileContent(filePath, maxSize = MAX_CONTENT_SIZE) {
  try {
    const content = await fs.readFile(filePath, 'utf-8');
    
    if (content.length <= maxSize) {
      return {
        content,
        truncated: false,
        originalSize: content.length
      };
    } else {
      return {
        content: content.substring(0, CONTENT_PREVIEW_SIZE) + '\n\n... [Content truncated - file too large] ...\n\n' + content.substring(content.length - CONTENT_PREVIEW_SIZE),
        truncated: true,
        originalSize: content.length
      };
    }
  } catch (error) {
    return {
      content: `[Error reading file: ${error.message}]`,
      truncated: false,
      originalSize: 0
    };
  }
}

// Function to extract metadata from file content
function extractMetadata(content, fileType, filePath) {
  const metadata = {
    functions: [],
    classes: [],
    interfaces: [],
    types: [],
    constants: [],
    comments: []
  };
  
  if (fileType === 'typescript' || fileType === 'javascript') {
    // Extract functions
    const functionRegex = /(?:export\s+)?(?:async\s+)?function\s+(\w+)/g;
    let match;
    while ((match = functionRegex.exec(content)) !== null) {
      metadata.functions.push(match[1]);
    }
    
    // Extract arrow functions
    const arrowFunctionRegex = /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/g;
    while ((match = arrowFunctionRegex.exec(content)) !== null) {
      metadata.functions.push(match[1]);
    }
    
    // Extract classes
    const classRegex = /(?:export\s+)?class\s+(\w+)/g;
    while ((match = classRegex.exec(content)) !== null) {
      metadata.classes.push(match[1]);
    }
    
    // Extract interfaces (TypeScript)
    if (fileType === 'typescript') {
      const interfaceRegex = /(?:export\s+)?interface\s+(\w+)/g;
      while ((match = interfaceRegex.exec(content)) !== null) {
        metadata.interfaces.push(match[1]);
      }
      
      // Extract type aliases
      const typeRegex = /(?:export\s+)?type\s+(\w+)/g;
      while ((match = typeRegex.exec(content)) !== null) {
        metadata.types.push(match[1]);
      }
    }
    
    // Extract constants
    const constRegex = /(?:export\s+)?const\s+([A-Z_][A-Z0-9_]*)\s*=/g;
    while ((match = constRegex.exec(content)) !== null) {
      metadata.constants.push(match[1]);
    }
    
    // Extract JSDoc comments
    const jsdocRegex = /\/\*\*[\s\S]*?\*\//g;
    const jsdocMatches = content.match(jsdocRegex);
    if (jsdocMatches) {
      metadata.comments = jsdocMatches.slice(0, 3); // First 3 JSDoc comments
    }
  }
  
  return metadata;
}

// Function to analyze directory recursively
async function analyzeDirectory(dirPath, relativePath = '') {
  const items = await fs.readdir(dirPath);
  const analysis = {
    files: [],
    directories: [],
    totalFiles: 0,
    totalLines: 0,
    fileTypes: {},
    imports: new Set(),
    largestFiles: [],
    recentFiles: []
  };

  for (const item of items) {
    const fullPath = path.join(dirPath, item);
    const itemRelativePath = path.join(relativePath, item);
    const stats = await getFileStats(fullPath);

    if (!stats) continue;

    if (stats.isDirectory) {
      analysis.directories.push({
        name: item,
        path: itemRelativePath,
        stats
      });
      
      // Recursively analyze subdirectory
      const subAnalysis = await analyzeDirectory(fullPath, itemRelativePath);
      analysis.files.push(...subAnalysis.files);
      analysis.totalFiles += subAnalysis.totalFiles;
      analysis.totalLines += subAnalysis.totalLines;
      analysis.imports = new Set([...analysis.imports, ...subAnalysis.imports]);
      
      // Merge file types
      for (const [type, count] of Object.entries(subAnalysis.fileTypes)) {
        analysis.fileTypes[type] = (analysis.fileTypes[type] || 0) + count;
      }
    } else {
      const fileType = getFileType(item);
      const lines = await countLines(fullPath);
      const imports = fileType === 'typescript' || fileType === 'javascript' 
        ? await extractImports(fullPath) 
        : [];
      const exports = fileType === 'typescript' || fileType === 'javascript'
        ? await extractExports(fullPath)
        : [];
      
      // Get file content if enabled
      const fileContent = INCLUDE_CONTENT ? await getFileContent(fullPath) : null;
      const metadata = fileContent ? extractMetadata(fileContent.content, fileType, fullPath) : null;

      const fileInfo = {
        name: item,
        path: itemRelativePath,
        type: fileType,
        size: stats.size,
        lines,
        created: stats.created,
        modified: stats.modified,
        imports,
        exports,
        content: fileContent,
        metadata
      };

      analysis.files.push(fileInfo);
      analysis.totalFiles++;
      analysis.totalLines += lines;
      analysis.fileTypes[fileType] = (analysis.fileTypes[fileType] || 0) + 1;
      
      // Add imports to global set
      imports.forEach(imp => analysis.imports.add(imp));
    }
  }

  return analysis;
}

// Function to format file size
function formatFileSize(bytes) {
  const sizes = ['B', 'KB', 'MB', 'GB'];
  if (bytes === 0) return '0 B';
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
}

// Function to format date
function formatDate(date) {
  return date.toISOString().split('T')[0];
}

// Main function to analyze a specific directory
async function generateSummaryForDirectory(dirPath, outputFile, dirName) {
  console.log(`🔍 Analyzing ${dirName} folder...`);
  
  try {
    // Check if directory exists
    const dirExists = await fs.access(dirPath).then(() => true).catch(() => false);
    if (!dirExists) {
      throw new Error(`Directory not found: ${dirPath}`);
    }

    // Analyze the entire directory
    const analysis = await analyzeDirectory(dirPath);
    
    // Sort files by size and modification date
    analysis.largestFiles = analysis.files
      .sort((a, b) => b.size - a.size)
      .slice(0, 10);
    
    analysis.recentFiles = analysis.files
      .sort((a, b) => b.modified - a.modified)
      .slice(0, 10);

    // Generate summary content
    const summary = generateSummaryContent(analysis, dirName);
    
    // Write to file
    await fs.writeFile(outputFile, summary, 'utf-8');
    
    console.log(`✅ Summary written to: ${outputFile}`);
    console.log(`📊 Total files: ${analysis.totalFiles}`);
    console.log(`📝 Total lines: ${analysis.totalLines.toLocaleString()}`);
    console.log(`📁 Directories: ${analysis.directories.length}`);
    
    return analysis;
    
  } catch (error) {
    console.error(`❌ Error generating ${dirName} summary:`, error.message);
    throw error;
  }
}

// Main function
async function generateSummary() {
  try {
    // Analyze src directory
    await generateSummaryForDirectory(SRC_DIR, SRC_OUTPUT_FILE, 'src');
    console.log('');
    
    // Analyze test directory
    await generateSummaryForDirectory(TEST_DIR, TEST_OUTPUT_FILE, 'test');
    
    console.log('\n🎉 Both summaries generated successfully!');
    
  } catch (error) {
    console.error('❌ Error generating summaries:', error.message);
    process.exit(1);
  }
}

// Function to generate summary content
function generateSummaryContent(analysis, dirName = 'src') {
  const now = new Date();
  const isTestDir = dirName === 'test';
  
  let content = `# ${isTestDir ? 'Test Code' : 'Source Code'} Analysis Summary
Generated: ${now.toISOString()}
Project: SAP Docs MCP
${isTestDir ? 'Test' : 'Source'} Directory: ${dirName}/

## 📊 Overview
- Total Files: ${analysis.totalFiles.toLocaleString()}
- Total Lines of Code: ${analysis.totalLines.toLocaleString()}
- Directories: ${analysis.directories.length}
- Unique Imports: ${analysis.imports.size}

## 📁 Directory Structure
${analysis.directories.map(dir => `- ${dir.path}/`).join('\n')}

## 📄 File Types Distribution
${Object.entries(analysis.fileTypes)
  .sort(([,a], [,b]) => b - a)
  .map(([type, count]) => `- ${type}: ${count} files`)
  .join('\n')}

## 🔝 Largest Files (by size)
${analysis.largestFiles.map((file, index) => 
  `${index + 1}. ${file.path} (${formatFileSize(file.size)}, ${file.lines} lines)`
).join('\n')}

## ⏰ Recently Modified Files
${analysis.recentFiles.map((file, index) => 
  `${index + 1}. ${file.path} (${formatDate(file.modified)})`
).join('\n')}

## 📋 Detailed File Analysis
${analysis.files
  .sort((a, b) => a.path.localeCompare(b.path))
  .map(file => {
    let fileSection = `### 📄 ${file.path}
**Type:** ${file.type}
**Size:** ${formatFileSize(file.size)}
**Lines:** ${file.lines}
**Modified:** ${formatDate(file.modified)}`;

    if (file.imports.length > 0) {
      fileSection += `\n**Imports:** ${file.imports.join(', ')}`;
    }
    
    if (file.exports.length > 0) {
      fileSection += `\n**Exports:** ${file.exports.join(', ')}`;
    }
    
    if (file.metadata) {
      const meta = file.metadata;
      if (meta.functions.length > 0) {
        fileSection += `\n**Functions:** ${meta.functions.join(', ')}`;
      }
      if (meta.classes.length > 0) {
        fileSection += `\n**Classes:** ${meta.classes.join(', ')}`;
      }
      if (meta.interfaces.length > 0) {
        fileSection += `\n**Interfaces:** ${meta.interfaces.join(', ')}`;
      }
      if (meta.types.length > 0) {
        fileSection += `\n**Types:** ${meta.types.join(', ')}`;
      }
      if (meta.constants.length > 0) {
        fileSection += `\n**Constants:** ${meta.constants.join(', ')}`;
      }
    }
    
    if (file.content && INCLUDE_CONTENT) {
      fileSection += `\n\n**Content:**`;
      if (file.content.truncated) {
        fileSection += ` (${formatFileSize(file.content.originalSize)} - truncated)`;
      }
      fileSection += `\n\`\`\`${file.type === 'typescript' ? 'typescript' : file.type === 'javascript' ? 'javascript' : ''}\n${file.content.content}\n\`\`\``;
    }
    
    return fileSection;
  })
  .join('\n\n')}

## 🔗 Most Common Imports
${Array.from(analysis.imports)
  .sort()
  .slice(0, 20)
  .map(imp => `- ${imp}`)
  .join('\n')}

## 🔍 Code Analysis Summary
${(() => {
  const allFunctions = analysis.files.flatMap(f => f.metadata?.functions || []);
  const allClasses = analysis.files.flatMap(f => f.metadata?.classes || []);
  const allInterfaces = analysis.files.flatMap(f => f.metadata?.interfaces || []);
  const allTypes = analysis.files.flatMap(f => f.metadata?.types || []);
  const allExports = analysis.files.flatMap(f => f.exports || []);
  
  return `- Total Functions: ${allFunctions.length}
- Total Classes: ${allClasses.length}
- Total Interfaces: ${allInterfaces.length}
- Total Types: ${allTypes.length}
- Total Exports: ${allExports.length}
- Files with Content: ${analysis.files.filter(f => f.content).length}`;
})()}

## 📈 Statistics
- Average file size: ${formatFileSize(analysis.files.reduce((sum, f) => sum + f.size, 0) / analysis.totalFiles)}
- Average lines per file: ${Math.round(analysis.totalLines / analysis.totalFiles)}
- Most common file type: ${Object.entries(analysis.fileTypes).sort(([,a], [,b]) => b - a)[0]?.[0] || 'N/A'}
- Oldest file: ${analysis.files.length > 0 ? formatDate(analysis.files.reduce((oldest, f) => f.created < oldest.created ? f : oldest).created) : 'N/A'}
- Newest file: ${analysis.files.length > 0 ? formatDate(analysis.files.reduce((newest, f) => f.modified > newest.modified ? f : newest).modified) : 'N/A'}
- Content included: ${INCLUDE_CONTENT ? 'Yes' : 'No'}
- Max content size per file: ${formatFileSize(MAX_CONTENT_SIZE)}

---
Generated by summarize-src.js for ${dirName}/ directory
`;

  return content;
}

// Run the script
generateSummary();

```

--------------------------------------------------------------------------------
/.github/workflows/update-submodules.yml:
--------------------------------------------------------------------------------

```yaml
name: Documentation Update & Database Health Monitor

on:
  # Run daily at 4 AM UTC for submodule updates
  schedule:
    - cron: '0 4 * * *'
    # Run every 2 hours for database health monitoring
    - cron: '0 */2 * * *'
  
  # Allow manual triggering with options
  workflow_dispatch:
    inputs:
      health_check_only:
        description: 'Perform health check only (no submodule update)'
        required: false
        default: 'false'
        type: boolean
      force_rebuild:
        description: 'Force database rebuild even if healthy'
        required: false
        default: 'false'
        type: boolean

jobs:
  update-submodules:
    name: Update Submodules on Server
    runs-on: ubuntu-22.04
    environment:
      name: remove server

    steps:
      - name: Update submodules on server with database safety checks
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            set -e
            cd /opt/mcp-sap/mcp-sap-docs
            
            DB_PATH="/opt/mcp-sap/mcp-sap-docs/dist/data/docs.sqlite"
            HEALTH_CHECK_ONLY="${{ inputs.health_check_only }}"
            FORCE_REBUILD="${{ inputs.force_rebuild }}"
            
            # Determine if this is a health check or full update
            IS_HEALTH_CHECK_SCHEDULE=false
            if [ "$(date +%H)" != "04" ] && [ "$HEALTH_CHECK_ONLY" != "true" ]; then
              IS_HEALTH_CHECK_SCHEDULE=true
              echo "=== Database Health Check (Scheduled) Started ==="
            elif [ "$HEALTH_CHECK_ONLY" = "true" ]; then
              echo "=== Database Health Check (Manual) Started ==="
            else
              echo "=== Documentation Update with Database Safety Started ==="
            fi
            echo "📅 $(date)"
            echo "🔍 Health check only: $HEALTH_CHECK_ONLY"
            echo "🔧 Force rebuild: $FORCE_REBUILD"
            
            # Function to check SQLite database integrity
            check_db_integrity() {
              local db_path="$1"
              if [ -f "$db_path" ]; then
                echo "🔍 Checking database integrity: $db_path"
                if sqlite3 "$db_path" "PRAGMA integrity_check;" 2>/dev/null | grep -q "ok"; then
                  echo "✅ Database integrity OK"
                  return 0
                else
                  echo "❌ Database corruption detected"
                  return 1
                fi
              else
                echo "ℹ️  Database file does not exist: $db_path"
                return 1
              fi
            }
            
            echo "==> Database health check"
            DB_WAS_CORRUPT=0
            DB_NEEDS_REPAIR=0
            
            if ! check_db_integrity "$DB_PATH"; then
              DB_WAS_CORRUPT=1
              DB_NEEDS_REPAIR=1
              echo "⚠️  Database corruption detected"
            fi
            
            # Force rebuild if requested
            if [ "$FORCE_REBUILD" = "true" ]; then
              DB_NEEDS_REPAIR=1
              echo "🔄 Force rebuild requested"
            fi
            
            # For health checks, test search functionality 
            if [ "$IS_HEALTH_CHECK_SCHEDULE" = "true" ] || [ "$HEALTH_CHECK_ONLY" = "true" ]; then
              echo "==> Testing search functionality"
              SEARCH_TEST=$(curl -s -X POST http://127.0.0.1:3001/mcp \
                -H "Content-Type: application/json" \
                -d '{"role": "user", "content": "health check search"}' || echo "curl_failed")
              
              if echo "$SEARCH_TEST" | grep -q "SqliteError\|SQLITE_CORRUPT\|Tool execution failed\|curl_failed"; then
                DB_NEEDS_REPAIR=1
                echo "❌ Search functionality failed - repair needed"
                echo "Response: $SEARCH_TEST"
              else
                echo "✅ Search functionality OK"
              fi
            fi
            
            # Only proceed with backup and rebuilds if needed
            REPAIR_PERFORMED=0
            SUBMODULES_UPDATED=0
            
            if [ "$DB_NEEDS_REPAIR" -eq 1 ]; then
              echo ""
              echo "🔧 DATABASE REPAIR REQUIRED"
              echo "=========================="
              
              echo "==> Creating backup before repair"
              BACKUP_DIR="/opt/mcp-sap/backups"
              mkdir -p "$BACKUP_DIR"
              
              if [ -f "$DB_PATH" ] && [ "$DB_WAS_CORRUPT" -eq 0 ]; then
                BACKUP_PATH="$BACKUP_DIR/pre-repair-$(date +%Y%m%d-%H%M%S).sqlite"
                cp "$DB_PATH" "$BACKUP_PATH"
                echo "✅ Database backed up to $BACKUP_PATH"
              fi
              
              echo "==> Checking system resources"
              # Check disk space
              AVAILABLE_MB=$(df /opt/mcp-sap --output=avail -m | tail -n1)
              if [ "$AVAILABLE_MB" -lt 1000 ]; then
                echo "❌ ERROR: Insufficient disk space. Available: ${AVAILABLE_MB}MB, Required: 1000MB"
                exit 1
              fi
              echo "✅ Disk space OK: ${AVAILABLE_MB}MB available"
              
              # Check memory
              AVAILABLE_KB=$(awk '/MemAvailable/ { print $2 }' /proc/meminfo)
              AVAILABLE_MB_MEM=$((AVAILABLE_KB / 1024))
              if [ "$AVAILABLE_MB_MEM" -lt 512 ]; then
                echo "❌ ERROR: Insufficient memory. Available: ${AVAILABLE_MB_MEM}MB, Required: 512MB"
                exit 1
              fi
              echo "✅ Memory OK: ${AVAILABLE_MB_MEM}MB available"
              
              echo "==> Gracefully stopping services for repair"
              pm2 stop all || true
              sleep 3
              
              # Remove corrupted database
              echo "🗑️  Removing corrupted database"
              rm -f "$DB_PATH"
              
              # Rebuild database
              echo "🔨 Rebuilding database..."
              npm run build:fts
              
              REPAIR_PERFORMED=1
              
            elif [ "$IS_HEALTH_CHECK_SCHEDULE" != "true" ] && [ "$HEALTH_CHECK_ONLY" != "true" ]; then
              echo ""
              echo "📦 SUBMODULE UPDATE PROCESS"
              echo "=========================="
              
              echo "==> Creating backup before update"
              BACKUP_DIR="/opt/mcp-sap/backups"
              mkdir -p "$BACKUP_DIR"
              
              if [ -f "$DB_PATH" ]; then
                BACKUP_PATH="$BACKUP_DIR/pre-update-$(date +%Y%m%d-%H%M%S).sqlite"
                cp "$DB_PATH" "$BACKUP_PATH"
                echo "✅ Database backed up to $BACKUP_PATH"
              fi
              
              echo "==> Checking system resources"
              # Check disk space
              AVAILABLE_MB=$(df /opt/mcp-sap --output=avail -m | tail -n1)
              if [ "$AVAILABLE_MB" -lt 500 ]; then
                echo "❌ ERROR: Insufficient disk space. Available: ${AVAILABLE_MB}MB, Required: 500MB"
                exit 1
              fi
              echo "✅ Disk space OK: ${AVAILABLE_MB}MB available"
              
              echo "==> Gracefully stopping services for update"
              pm2 stop all || true
              sleep 3
              
              # Configure git for HTTPS
              git config --global url."https://github.com/".insteadOf [email protected]:
              
              echo "==> Checking for submodule updates"
              # Get current submodule commits
              git submodule status > /tmp/before-update.txt || true
              
              # Reuse setup.sh to ensure shallow, single-branch submodules and build
              echo "==> Running setup script (includes submodule update and rebuild)"
              SKIP_NESTED_SUBMODULES=1 bash setup.sh
              
              # Check what changed
              git submodule status > /tmp/after-update.txt || true
              
              if ! diff -q /tmp/before-update.txt /tmp/after-update.txt >/dev/null 2>&1; then
                echo "✅ Submodules were updated - changes detected"
                SUBMODULES_UPDATED=1
              else
                echo "ℹ️  No submodule changes detected"
                SUBMODULES_UPDATED=0
              fi
            else
              echo "ℹ️  Health check only - no database repair or submodule update needed"
            fi
            
            # Post-operation database integrity check (only if changes were made)
            if [ "$REPAIR_PERFORMED" -eq 1 ] || [ "$SUBMODULES_UPDATED" -eq 1 ]; then
              echo "==> Post-operation database integrity check"
              if ! check_db_integrity "$DB_PATH"; then
                echo "❌ ERROR: Database corruption after operation"
                
                # Try to restore from backup if available
                if [ -f "$BACKUP_PATH" ] && [ "$DB_WAS_CORRUPT" -eq 0 ]; then
                  echo "🔄 Attempting to restore from backup..."
                  cp "$BACKUP_PATH" "$DB_PATH"
                  
                  if check_db_integrity "$DB_PATH"; then
                    echo "✅ Successfully restored from backup"
                  else
                    echo "❌ Backup is also corrupted - forcing fresh rebuild"
                    rm -f "$DB_PATH"
                    npm run build:fts
                  fi
                else
                  echo "🔄 No clean backup available - forcing fresh rebuild"
                  rm -f "$DB_PATH"
                  npm run build:fts
                fi
                
                # Final check
                if ! check_db_integrity "$DB_PATH"; then
                  echo "❌ CRITICAL: Could not create working database"
                  exit 1
                fi
              fi
            fi
            
            # Restart services if they were stopped
            if [ "$REPAIR_PERFORMED" -eq 1 ] || [ "$SUBMODULES_UPDATED" -eq 1 ]; then
              echo "==> Creating logs directory"
              mkdir -p /opt/mcp-sap/logs
              chown -R "$USER":"$USER" /opt/mcp-sap/logs
              
              echo "==> Restarting services"
              pm2 start all
              pm2 save
              sleep 10
            fi
            
            echo "==> Health verification"
            # Test search functionality
            SEARCH_TEST=$(curl -s -X POST http://127.0.0.1:3001/mcp \
              -H "Content-Type: application/json" \
              -d '{"role": "user", "content": "test search"}' || echo "curl_failed")
            
            if echo "$SEARCH_TEST" | grep -q "SqliteError\|SQLITE_CORRUPT\|Tool execution failed\|curl_failed"; then
              echo "❌ ERROR: Search functionality test failed after update"
              echo "Response: $SEARCH_TEST"
              exit 1
            else
              echo "✅ Search functionality verified"
            fi
            
            echo "==> Cleanup old backups (keep last 7)"
            ls -t "$BACKUP_DIR"/*.sqlite 2>/dev/null | tail -n +8 | xargs -r rm --
            
            echo ""
            if [ "$IS_HEALTH_CHECK_SCHEDULE" = "true" ] || [ "$HEALTH_CHECK_ONLY" = "true" ]; then
              echo "=== Health Check Completed Successfully ==="
              echo "🔍 Database health: $([ "$DB_NEEDS_REPAIR" -eq 0 ] && echo "Healthy" || echo "Repaired")"
              echo "🔧 Repair performed: $([ "$REPAIR_PERFORMED" -eq 1 ] && echo "Yes" || echo "No")"
            else
              echo "=== Documentation Update Completed Successfully ==="
              echo "📊 Submodules updated: $([ "$SUBMODULES_UPDATED" -eq 1 ] && echo "Yes" || echo "No")"
              echo "🔧 Database repair: $([ "$REPAIR_PERFORMED" -eq 1 ] && echo "Yes" || echo "No")"
            fi
            echo "📅 Completion time: $(date)"

      - name: Create success summary
        if: success()
        run: |
          echo "## ✅ Documentation Update Complete (Enhanced with DB Safety)" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "### Update Results:" >> $GITHUB_STEP_SUMMARY
          echo "✅ **Submodules**: Updated to latest remote commits" >> $GITHUB_STEP_SUMMARY
          echo "✅ **Search Index**: Rebuilt with enhanced safety checks" >> $GITHUB_STEP_SUMMARY  
          echo "✅ **Database Health**: Verified before and after update" >> $GITHUB_STEP_SUMMARY
          echo "✅ **Services**: Restarted and verified working" >> $GITHUB_STEP_SUMMARY
          echo "✅ **Backup**: Pre-update backup created (if needed)" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "### Safety Features Added:" >> $GITHUB_STEP_SUMMARY
          echo "- 🔍 **Database integrity checks** before and after update" >> $GITHUB_STEP_SUMMARY
          echo "- 📦 **Automatic backup** of healthy database before changes" >> $GITHUB_STEP_SUMMARY
          echo "- 🔧 **Automatic corruption recovery** with backup restoration" >> $GITHUB_STEP_SUMMARY
          echo "- 🧪 **Search functionality verification** after update" >> $GITHUB_STEP_SUMMARY
          echo "- 💾 **System resource validation** before rebuild" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "🕐 **Update time**: $(date -u)" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "The MCP server now has the latest SAP documentation with verified database integrity."

      - name: Create failure summary
        if: failure()
        run: |
          echo "## ❌ Documentation Update Failed" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "🚨 **Status**: Enhanced update process encountered errors" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "### Enhanced Troubleshooting:" >> $GITHUB_STEP_SUMMARY
          echo "**Check these areas on the server:**" >> $GITHUB_STEP_SUMMARY
          echo "1. **Database corruption**: Check for SQLite errors in logs" >> $GITHUB_STEP_SUMMARY
          echo "2. **System resources**: Disk space and memory availability" >> $GITHUB_STEP_SUMMARY
          echo "3. **Service status**: PM2 process health and logs" >> $GITHUB_STEP_SUMMARY
          echo "4. **Backup availability**: Check /opt/mcp-sap/backups/" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "### Quick Recovery Commands:" >> $GITHUB_STEP_SUMMARY
          echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
          echo "# Check PM2 status and logs" >> $GITHUB_STEP_SUMMARY
          echo "pm2 status && pm2 logs --lines 50" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "# Manual database rebuild if corruption detected" >> $GITHUB_STEP_SUMMARY
          echo "cd /opt/mcp-sap/mcp-sap-docs" >> $GITHUB_STEP_SUMMARY
          echo "pm2 stop all" >> $GITHUB_STEP_SUMMARY
          echo "rm -f dist/data/docs.sqlite" >> $GITHUB_STEP_SUMMARY
          echo "npm run build:fts" >> $GITHUB_STEP_SUMMARY
          echo "pm2 start all" >> $GITHUB_STEP_SUMMARY
          echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "🕐 **Failure time**: $(date -u)" >> $GITHUB_STEP_SUMMARY
```

--------------------------------------------------------------------------------
/src/metadata.json:
--------------------------------------------------------------------------------

```json
{
  "version": 1,
  "updated_at": "2025-01-14",
  "description": "Centralized configuration for SAP Docs MCP search system",
  
  "sources": [
    {
      "id": "sapui5",
      "type": "documentation",
      "lang": "en",
      "boost": 0.1,
      "tags": ["ui5", "frontend", "javascript"],
      "description": "SAPUI5 framework documentation",
      "libraryId": "/sapui5",
      "sourcePath": "sapui5-docs/docs",
      "baseUrl": "https://ui5.sap.com",
      "pathPattern": "/#/topic/{file}",
      "anchorStyle": "custom"
    },
    {
      "id": "cap",
      "type": "documentation", 
      "lang": "en",
      "boost": 0.1,
      "tags": ["backend", "nodejs", "java", "cds"],
      "description": "SAP Cloud Application Programming model",
      "libraryId": "/cap",
      "sourcePath": "cap-docs",
      "baseUrl": "https://cap.cloud.sap",
      "pathPattern": "/docs/{file}",
      "anchorStyle": "docsify"
    },
    {
      "id": "openui5-api",
      "type": "api",
      "lang": "en", 
      "boost": 0.1,
      "tags": ["api", "controls", "ui5"],
      "description": "OpenUI5 API documentation",
      "libraryId": "/openui5-api",
      "sourcePath": "openui5/src",
      "baseUrl": "https://sdk.openui5.org",
      "pathPattern": "/#/api/{file}",
      "anchorStyle": "custom"
    },
    {
      "id": "openui5-samples",
      "type": "samples",
      "lang": "en",
      "boost": 0.05,
      "tags": ["samples", "examples", "ui5"],
      "description": "OpenUI5 code samples",
      "libraryId": "/openui5-samples",
      "sourcePath": "openui5/src",
      "baseUrl": "https://sdk.openui5.org",
      "pathPattern": "/entity/{file}",
      "anchorStyle": "custom"
    },
    {
      "id": "wdi5",
      "type": "documentation",
      "lang": "en",
      "boost": 0.05,
      "tags": ["testing", "e2e", "webdriver"],
      "description": "wdi5 testing framework",
      "libraryId": "/wdi5",
      "sourcePath": "wdi5/docs",
      "baseUrl": "https://ui5-community.github.io/wdi5",
      "pathPattern": "#{file}",
      "anchorStyle": "docsify"
    },
    {
      "id": "ui5-tooling",
      "type": "documentation",
      "lang": "en",
      "boost": 0.0,
      "tags": ["tooling", "build", "cli"],
      "description": "UI5 Tooling documentation",
      "libraryId": "/ui5-tooling",
      "sourcePath": "ui5-tooling/docs",
      "baseUrl": "https://sap.github.io/ui5-tooling/v4",
      "pathPattern": "/pages/{file}",
      "anchorStyle": "github"
    },
    {
      "id": "cloud-mta-build-tool", 
      "type": "documentation",
      "lang": "en",
      "boost": 0.0,
      "tags": ["mta", "build", "deployment"],
      "description": "Cloud MTA Build Tool",
      "libraryId": "/cloud-mta-build-tool",
      "sourcePath": "cloud-mta-build-tool/docs/docs",
      "baseUrl": "https://sap.github.io/cloud-mta-build-tool",
      "pathPattern": "/{file}",
      "anchorStyle": "github"
    },
    {
      "id": "ui5-webcomponents",
      "type": "documentation", 
      "lang": "en",
      "boost": 0.0,
      "tags": ["webcomponents", "ui5"],
      "description": "UI5 Web Components",
      "libraryId": "/ui5-webcomponents",
      "sourcePath": "ui5-webcomponents/docs",
      "baseUrl": "https://sap.github.io/ui5-webcomponents/docs",
      "pathPattern": "/{file}",
      "anchorStyle": "github"
    },
    {
      "id": "cloud-sdk-js",
      "type": "documentation",
      "lang": "en", 
      "boost": 0.05,
      "tags": ["sdk", "javascript", "cloud"],
      "description": "SAP Cloud SDK for JavaScript",
      "libraryId": "/cloud-sdk-js",
      "sourcePath": "cloud-sdk/docs-js",
      "baseUrl": "https://sap.github.io/cloud-sdk/docs/js",
      "pathPattern": "/{file}",
      "anchorStyle": "github"
    },
    {
      "id": "cloud-sdk-java",
      "type": "documentation",
      "lang": "en",
      "boost": 0.05,
      "tags": ["sdk", "java", "cloud"],
      "description": "SAP Cloud SDK for Java",
      "libraryId": "/cloud-sdk-java",
      "sourcePath": "cloud-sdk/docs-java",
      "baseUrl": "https://sap.github.io/cloud-sdk/docs/java",
      "pathPattern": "/{file}",
      "anchorStyle": "github"
    },
    {
      "id": "cloud-sdk-ai-js",
      "type": "documentation",
      "lang": "en",
      "boost": 0.05, 
      "tags": ["ai", "sdk", "javascript"],
      "description": "SAP Cloud SDK AI for JavaScript",
      "libraryId": "/cloud-sdk-ai-js",
      "sourcePath": "cloud-sdk-ai/docs-js",
      "baseUrl": "https://sap.github.io/ai-sdk/docs/js",
      "pathPattern": "/{file}",
      "anchorStyle": "github"
    },
    {
      "id": "cloud-sdk-ai-java",
      "type": "documentation",
      "lang": "en",
      "boost": 0.05,
      "tags": ["ai", "sdk", "java"],
      "description": "SAP Cloud SDK AI for Java",
      "libraryId": "/cloud-sdk-ai-java",
      "sourcePath": "cloud-sdk-ai/docs-java",
      "baseUrl": "https://sap.github.io/ai-sdk/docs/java",
      "pathPattern": "/{file}",
      "anchorStyle": "github"
    },
    {
      "id": "ui5-typescript",
      "type": "documentation",
      "lang": "en",
      "boost": 0.1,
      "tags": ["ui5", "typescript", "types", "frontend"],
      "description": "UI5 TypeScript",
      "libraryId": "/ui5-typescript",
      "sourcePath": "ui5-typescript",
      "baseUrl": "https://github.com/UI5/typescript/blob/gh-pages",
      "pathPattern": "/{file}",
      "anchorStyle": "github"
    },
    {
      "id": "ui5-cc-spreadsheetimporter",
      "type": "documentation", 
      "lang": "en",
      "boost": 0.05,
      "tags": ["ui5", "spreadsheet", "importer", "custom-control"],
      "description": "UI5 CC Spreadsheet Importer",
      "libraryId": "/ui5-cc-spreadsheetimporter",
      "sourcePath": "ui5-cc-spreadsheetimporter/docs",
      "baseUrl": "https://docs.spreadsheet-importer.com",
      "pathPattern": "/pages/{file}/",
      "anchorStyle": "github"
    },
    {
      "id": "abap-cheat-sheets",
      "type": "documentation",
      "lang": "en", 
      "boost": 0.05,
      "tags": ["abap", "syntax", "cheat-sheets", "examples", "backend"],
      "description": "ABAP Cheat Sheets",
      "libraryId": "/abap-cheat-sheets",
      "sourcePath": "abap-cheat-sheets",
      "baseUrl": "https://github.com/SAP-samples/abap-cheat-sheets/blob/main",
      "pathPattern": "/{file}",
      "anchorStyle": "github"
    },
    {
      "id": "sap-styleguides",
      "type": "documentation",
      "lang": "en",
      "boost": 0.06,
      "tags": ["abap", "clean-code", "style-guide", "best-practices", "code-review"],
      "description": "SAP Style Guides",
      "libraryId": "/sap-styleguides", 
      "sourcePath": "sap-styleguides",
      "baseUrl": "https://github.com/SAP/styleguides/blob/main",
      "pathPattern": "/{file}",
      "anchorStyle": "github"
    },
    {
      "id": "dsag-abap-leitfaden",
      "type": "documentation",
      "lang": "de",
      "boost": 0.05,
      "tags": ["abap", "leitfaden", "best-practices", "german", "dsag", "clean-core"],
      "description": "DSAG ABAP Leitfaden",
      "libraryId": "/dsag-abap-leitfaden",
      "sourcePath": "dsag-abap-leitfaden/docs",
      "baseUrl": "https://1dsag.github.io/ABAP-Leitfaden",
      "pathPattern": "/{file}/",
      "anchorStyle": "github"
    },
    {
      "id": "abap-fiori-showcase",
      "type": "documentation",
      "lang": "en",
      "boost": 0.08,
      "tags": ["fiori-elements", "abap", "rap", "annotations", "odata-v4", "showcase"],
      "description": "ABAP Platform Fiori Feature Showcase",
      "libraryId": "/abap-fiori-showcase",
      "sourcePath": "abap-fiori-showcase",
      "baseUrl": "https://github.com/SAP-samples/abap-platform-fiori-feature-showcase/blob/main",
      "pathPattern": "/{file}",
      "anchorStyle": "github"
    },
    {
      "id": "cap-fiori-showcase",
      "type": "documentation",
      "lang": "en",
      "boost": 0.08,
      "tags": ["fiori-elements", "cap", "annotations", "odata-v4", "showcase", "nodejs"],
      "description": "CAP Fiori Elements Feature Showcase",
      "libraryId": "/cap-fiori-showcase",
      "sourcePath": "cap-fiori-showcase",
      "baseUrl": "https://github.com/SAP-samples/fiori-elements-feature-showcase/blob/main",
      "pathPattern": "/{file}",
      "anchorStyle": "github"
    },
    {
      "id": "abap-docs-758",
      "type": "documentation",
      "lang": "en",
      "boost": 0.05,
      "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.58"],
      "description": "Official ABAP Keyword Documentation (7.58)",
      "libraryId": "/abap-docs-758",
      "sourcePath": "abap-docs/docs/7.58/md",
      "baseUrl": "https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US",
      "pathPattern": "/{file}",
      "anchorStyle": "sap-help"
    },
    {
      "id": "abap-docs-757",
      "type": "documentation",
      "lang": "en",
      "boost": 0.02,
      "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.57"],
      "description": "Official ABAP Keyword Documentation (7.57)",
      "libraryId": "/abap-docs-757",
      "sourcePath": "abap-docs/docs/7.57/md",
      "baseUrl": "https://help.sap.com/doc/abapdocu_757_index_htm/7.57/en-US",
      "pathPattern": "/{file}",
      "anchorStyle": "sap-help"
    },
    {
      "id": "abap-docs-756",
      "type": "documentation",
      "lang": "en",
      "boost": 0.01,
      "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.56"],
      "description": "Official ABAP Keyword Documentation (7.56)",
      "libraryId": "/abap-docs-756",
      "sourcePath": "abap-docs/docs/7.56/md",
      "baseUrl": "https://help.sap.com/doc/abapdocu_756_index_htm/7.56/en-US",
      "pathPattern": "/{file}",
      "anchorStyle": "sap-help"
    },
    {
      "id": "abap-docs-755",
      "type": "documentation",
      "lang": "en",
      "boost": 0.01,
      "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.55"],
      "description": "Official ABAP Keyword Documentation (7.55)",
      "libraryId": "/abap-docs-755",
      "sourcePath": "abap-docs/docs/7.55/md",
      "baseUrl": "https://help.sap.com/doc/abapdocu_755_index_htm/7.55/en-US",
      "pathPattern": "/{file}",
      "anchorStyle": "sap-help"
    },
    {
      "id": "abap-docs-754",
      "type": "documentation",
      "lang": "en",
      "boost": 0.01,
      "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.54"],
      "description": "Official ABAP Keyword Documentation (7.54)",
      "libraryId": "/abap-docs-754",
      "sourcePath": "abap-docs/docs/7.54/md",
      "baseUrl": "https://help.sap.com/doc/abapdocu_754_index_htm/7.54/en-US",
      "pathPattern": "/{file}",
      "anchorStyle": "sap-help"
    },
    {
      "id": "abap-docs-753",
      "type": "documentation",
      "lang": "en",
      "boost": 0.01,
      "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.53"],
      "description": "Official ABAP Keyword Documentation (7.53)",
      "libraryId": "/abap-docs-753",
      "sourcePath": "abap-docs/docs/7.53/md",
      "baseUrl": "https://help.sap.com/doc/abapdocu_753_index_htm/7.53/en-US",
      "pathPattern": "/{file}",
      "anchorStyle": "sap-help"
    },
    {
      "id": "abap-docs-752",
      "type": "documentation",
      "lang": "en",
      "boost": 0.01,
      "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.52"],
      "description": "Official ABAP Keyword Documentation (7.52)",
      "libraryId": "/abap-docs-752",
      "sourcePath": "abap-docs/docs/7.52/md",
      "baseUrl": "https://help.sap.com/doc/abapdocu_752_index_htm/7.52/en-US",
      "pathPattern": "/{file}",
      "anchorStyle": "sap-help"
    },
    {
      "id": "abap-docs-latest",
      "type": "documentation",
      "lang": "en",
      "boost": 1.0,
      "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "latest"],
      "description": "Official ABAP Keyword Documentation (Latest)",
      "libraryId": "/abap-docs-latest",
      "sourcePath": "abap-docs/docs/latest/md",
      "baseUrl": "https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US",
      "pathPattern": "/{file}",
      "anchorStyle": "sap-help"
    }
  ],
  
  "synonyms": [
    { "from": "ui5", "to": ["sapui5", "openui5"] },
    { "from": "button", "to": ["btn", "sap.m.Button"] },
    { "from": "table", "to": ["sap.m.Table", "sap.ui.table.Table"] },
    { "from": "wizard", "to": ["sap.m.Wizard"] },
    { "from": "testing", "to": ["wdi5", "e2e", "automation"] },
    { "from": "cds", "to": ["cap", "cloud application programming"] },
    { "from": "odata", "to": ["rest", "api", "service"] },
    { "from": "typescript", "to": ["ts", "types", "type definitions"] },
    { "from": "spreadsheet", "to": ["excel", "csv", "import", "upload"] },
    { "from": "abap", "to": ["advanced business application programming", "sap programming"] },
    { "from": "clean-code", "to": ["clean code", "best practices", "style guide"] },
    { "from": "style-guide", "to": ["styleguide", "coding standards", "best practices"] },
    { "from": "leitfaden", "to": ["guidelines", "guide", "best practices"] },
    { "from": "clean-core", "to": ["clean core", "extensibility", "cloud development"] },
    { "from": "fiori-elements", "to": ["fiori elements", "annotations", "odata"] },
    { "from": "rap", "to": ["restful application programming", "abap restful"] },
    { "from": "annotations", "to": ["ui5 annotations", "odata annotations", "fiori annotations"] }
  ],
  
  "acronyms": {
    "CAP": ["Cloud Application Programming", "cds"],
    "CDS": ["Core Data Services", "cap"],
    "UI5": ["sapui5", "openui5"],
    "BTP": ["Business Technology Platform", "cloud"],
    "MTA": ["Multi-Target Application"],
    "CQL": ["CDS Query Language", "query"],
    "OData": ["Open Data Protocol", "rest", "api"],
    "TS": ["TypeScript", "types"],
    "ABAP": ["Advanced Business Application Programming", "sap programming"],
    "DSAG": ["Deutschsprachige SAP-Anwendergruppe", "german sap user group"],
    "RAP": ["RESTful Application Programming", "abap restful", "business object"]
  },

  "contextBoosts": {
    "SAP Cloud SDK": {
      "/cloud-sdk-ai-js": 1.0,
      "/cloud-sdk-ai-java": 1.0,
      "/cloud-sdk-js": 0.8,
      "/cloud-sdk-java": 0.8,
      "/cap": 0.2
    },
    "UI5": {
      "/sapui5": 0.9,
      "/openui5-api": 0.9,
      "/openui5-samples": 0.9,
      "/ui5-typescript": 0.8
    },
    "wdi5": {
      "/wdi5": 1.0,
      "/openui5-api": 0.4,
      "/openui5-samples": 0.4,
      "/sapui5": 0.4
    },
    "UI5 Web Components": {
      "/ui5-webcomponents": 1.0
    },
    "UI5 Tooling": {
      "/ui5-tooling": 1.0
    },
    "Cloud MTA Build Tool": {
      "/cloud-mta-build-tool": 1.0
    },
    "CAP": {
      "/cap": 1.0,
      "/cap-fiori-showcase": 0.9,
      "/sapui5": 0.2
    },
    "TypeScript": {
      "/ui5-typescript": 1.0,
      "/sapui5": 0.4,
      "/openui5-api": 0.4
    },
    "UI5 CC Spreadsheet Importer": {
      "/ui5-cc-spreadsheetimporter": 1.0,
      "/sapui5": 0.3,
      "/openui5-api": 0.3
    },
    "ABAP": {
      "/abap-docs-latest": 1.0,
      "/abap-cheat-sheets": 0.8,
      "/sap-styleguides": 0.7,
      "/dsag-abap-leitfaden": 0.6,
      "/abap-docs-758": 0.05,
      "/abap-docs-757": 0.02,
      "/abap-docs-756": 0.01,
      "/abap-docs-755": 0.01,
      "/abap-docs-754": 0.01,
      "/abap-docs-753": 0.01,
      "/abap-docs-752": 0.01,
      "/cap": 0.2
    },
    "Clean Code": {
      "/sap-styleguides": 1.0,
      "/dsag-abap-leitfaden": 0.8,
      "/abap-cheat-sheets": 0.4
    },
    "DSAG": {
      "/dsag-abap-leitfaden": 1.0,
      "/abap-cheat-sheets": 0.5,
      "/sap-styleguides": 0.3
    },
    "Fiori Elements": {
      "/abap-fiori-showcase": 1.0,
      "/cap-fiori-showcase": 1.0,
      "/sapui5": 0.6,
      "/cap": 0.5
    },
    "RAP": {
      "/abap-fiori-showcase": 1.0,
      "/abap-cheat-sheets": 0.6,
      "/cap": 0.3
    },
    "7.58": {
      "/abap-docs-758": 2.0,
      "/abap-docs-latest": 0.3
    },
    "7.57": {
      "/abap-docs-757": 2.0,
      "/abap-docs-latest": 0.3
    },
    "7.56": {
      "/abap-docs-756": 2.0,
      "/abap-docs-latest": 0.3
    },
    "7.55": {
      "/abap-docs-755": 2.0,
      "/abap-docs-latest": 0.3
    },
    "7.54": {
      "/abap-docs-754": 2.0,
      "/abap-docs-latest": 0.3
    },
    "7.53": {
      "/abap-docs-753": 2.0,
      "/abap-docs-latest": 0.3
    },
    "7.52": {
      "/abap-docs-752": 2.0,
      "/abap-docs-latest": 0.3
    },
    "latest": {
      "/abap-docs-latest": 1.5,
      "/abap-docs-758": 0.1
    }
  },

  "libraryMappings": {
    "sapui5": "sapui5",
    "cap": "cap",
    "cloud-sdk-js": "cloud-sdk-js",
    "cloud-sdk-ai-js": "cloud-sdk-ai-js",
    "openui5-api": "sapui5",
    "openui5-samples": "sapui5",
    "wdi5": "wdi5",
    "ui5-tooling": "ui5-tooling",
    "cloud-mta-build-tool": "cloud-mta-build-tool",
    "ui5-webcomponents": "ui5-webcomponents",
    "cloud-sdk-java": "cloud-sdk-java",
    "cloud-sdk-ai-java": "cloud-sdk-ai-java",
    "ui5-typescript": "ui5-typescript",
    "ui5-cc-spreadsheetimporter": "ui5-cc-spreadsheetimporter",
    "abap-cheat-sheets": "abap-cheat-sheets",
    "sap-styleguides": "sap-styleguides",
    "dsag-abap-leitfaden": "dsag-abap-leitfaden",
    "abap-fiori-showcase": "abap-fiori-showcase",
    "cap-fiori-showcase": "cap-fiori-showcase",
    "abap-docs-758": "abap-docs",
    "abap-docs-757": "abap-docs",
    "abap-docs-756": "abap-docs",
    "abap-docs-755": "abap-docs",
    "abap-docs-754": "abap-docs",
    "abap-docs-753": "abap-docs",
    "abap-docs-752": "abap-docs",
    "abap-docs-latest": "abap-docs"
  },

  "contextEmojis": {
    "CAP": "🏗️",
    "wdi5": "🧪", 
    "UI5": "🎨",
    "UI5 Web Components": "🕹️",
    "SAP Cloud SDK": "🌐",
    "UI5 Tooling": "🔧",
    "Cloud MTA Build Tool": "🚢",
    "TypeScript": "📝",
    "UI5 CC Spreadsheet Importer": "📊",
    "ABAP": "💻",
    "Clean Code": "✨",
    "DSAG": "🇩🇪",
    "Fiori Elements": "📱",
    "RAP": "⚡",
    "MIXED": "🔀"
  }
}

```

--------------------------------------------------------------------------------
/test/comprehensive-url-generation.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Comprehensive URL Generation Test Suite
 * 
 * This test suite validates the URL generation system for SAP documentation sources.
 * It tests both the main generateDocumentationUrl function and individual generator classes
 * for 10+ different documentation sources including CAP, Cloud SDK, UI5, wdi5, etc.
 * 
 * Key Features:
 * - Reads from real source files when available (automatic path mapping)
 * - Falls back to test data when source files don't exist
 * - Uses real configuration from metadata.json (no hardcoded configs)
 * - Comprehensive coverage of all URL generation patterns
 * - Debug mode available with DEBUG_TESTS=true environment variable
 * 
 * Running Tests:
 * - npm run test:url-generation           # Run URL generation tests
 * - npm run test:url-generation:debug     # Run with debug output
 * - DEBUG_TESTS=true npx vitest run test/comprehensive-url-generation.test.ts
 * 
 * Architecture:
 * The system uses an abstract BaseUrlGenerator class with source-specific implementations
 * for different documentation platforms. Each generator handles its own URL patterns,
 * frontmatter parsing, and path transformations.
 */

import { describe, it, expect } from 'vitest';
import { 
  generateDocumentationUrl,
  CloudSdkUrlGenerator,
  SapUi5UrlGenerator,
  CapUrlGenerator,
  Wdi5UrlGenerator,
  DsagUrlGenerator,
  GenericUrlGenerator
} from '../src/lib/url-generation/index.js';
import { AbapUrlGenerator, generateAbapUrl } from '../src/lib/url-generation/abap.js';
import { DocUrlConfig, getDocUrlConfig } from '../src/lib/metadata.js';

describe('Comprehensive URL Generation System', () => {
  
  /**
   * Retrieves URL configuration from metadata.json for a given library
   * @param libraryId - The library identifier (e.g., '/cloud-sdk-js')
   * @returns Configuration object with baseUrl, pathPattern, and anchorStyle
   * @throws Error if no configuration is found
   */
  function getConfigForLibrary(libraryId: string): DocUrlConfig {
    const config = getDocUrlConfig(libraryId);
    if (!config) {
      throw new Error(`No configuration found for library: ${libraryId}`);
    }
    return config;
  }

  /**
   * Maps libraryId + relFile to actual source file path in the filesystem
   * Handles different repository structures and path transformations
   * @param libraryId - The library identifier 
   * @param relFile - The relative file path within the library
   * @returns Full path to the actual source file
   * @throws Error if no path mapping exists for the library
   */
  function getSourceFilePath(libraryId: string, relFile: string): string {
    const pathMappings: Record<string, { basePath: string; transform?: (relFile: string) => string }> = {
      '/cap': { basePath: 'sources/cap-docs' },
      '/cloud-mta-build-tool': { basePath: 'sources/cloud-mta-build-tool' },
      '/cloud-sdk-js': { basePath: 'sources/cloud-sdk/docs-js' },
      '/cloud-sdk-ai-js': { basePath: 'sources/cloud-sdk-ai/docs-js' },
      '/openui5-api': { 
        basePath: 'sources/openui5',
        transform: (relFile) => {
          // Transform src/sap/m/Button.js → src/sap.m/src/sap/m/Button.js
          const match = relFile.match(/^src\/sap\/([^\/]+)\/(.+)$/);
          if (match) {
            const [, module, file] = match;
            return `src/sap.${module}/src/sap/${module}/${file}`;
          }
          return relFile;
        }
      },
      '/openui5-samples': { basePath: 'sources/openui5' },
      '/sapui5': { basePath: 'sources/sapui5-docs/docs' },
      '/ui5-tooling': { basePath: 'sources/ui5-tooling/docs' },
      '/ui5-webcomponents': { basePath: 'sources/ui5-webcomponents/docs' },
      '/wdi5': { basePath: 'sources/wdi5/docs' },
      '/ui5-typescript': { basePath: 'sources/ui5-typescript' },
      '/ui5-cc-spreadsheetimporter': { basePath: 'sources/ui5-cc-spreadsheetimporter/docs' },
      '/abap-cheat-sheets': { basePath: 'sources/abap-cheat-sheets' },
      '/sap-styleguides': { basePath: 'sources/sap-styleguides' },
      '/dsag-abap-leitfaden': { basePath: 'sources/dsag-abap-leitfaden/docs' },
      '/abap-fiori-showcase': { basePath: 'sources/abap-fiori-showcase' },
      '/cap-fiori-showcase': { basePath: 'sources/cap-fiori-showcase' }
    };

    const mapping = pathMappings[libraryId];
    if (!mapping) {
      throw new Error(`No source path mapping found for library: ${libraryId}`);
    }

    const transformedRelFile = mapping.transform ? mapping.transform(relFile) : relFile;
    return `${mapping.basePath}/${transformedRelFile}`;
  }

  /**
   * Reads file content from actual source files with graceful fallback
   * @param libraryId - The library identifier (e.g., '/cloud-sdk-js')
   * @param relFile - The relative file path within the library
   * @returns File content as string, or null if file doesn't exist
   */
  function readFileContent(libraryId: string, relFile: string): string | null {
    const fs = require('fs');
    const path = require('path');
    
    try {
      const sourceFilePath = getSourceFilePath(libraryId, relFile);
      const fullPath = path.resolve(sourceFilePath);
      return fs.readFileSync(fullPath, 'utf8');
    } catch (error: any) {
      console.warn(`Could not read file for ${libraryId}/${relFile}:`, error.message);
      // Return null to trigger fallback to test data
      return null;
    }
  }
  
  /**
   * Test cases for comprehensive URL generation testing
   * 
   * Each test case defines:
   * - name: Human-readable test description
   * - libraryId: Library identifier from metadata.json
   * - relFile: Relative file path within the library (used for path mapping)
   * - expectedUrl: Expected generated URL for validation
   * - frontmatter: Fallback YAML frontmatter (used when real file not found)
   * - content: Fallback content (used when real file not found)
   * 
   * The system will attempt to read real source files first, falling back to
   * the provided frontmatter/content if the file doesn't exist.
   */
  const testCases = [
    {
      name: 'CAP - CDS Log Documentation',
      libraryId: '/cap',
      relFile: 'node.js/cds-log.md',
      expectedUrl: 'https://cap.cloud.sap/docs/#/node.js/cds-log',
      frontmatter: '---\nid: cds-log\ntitle: Logging\n---\n',
      content: '# Logging\n\nCAP provides structured logging capabilities...'
    },
    {
      name: 'Cloud MTA Build Tool - Download Page',
      libraryId: '/cloud-mta-build-tool',
      relFile: 'docs/download.md',
      expectedUrl: 'https://sap.github.io/cloud-mta-build-tool/download',
      frontmatter: '',
      content: '\nYou can install the Cloud MTA Build Tool...'
    },
    {
      name: 'Cloud SDK JS - Kubernetes Migration',
      libraryId: '/cloud-sdk-js',
      relFile: 'environments/migrate-sdk-application-from-btp-cf-to-kubernetes.mdx',
      expectedUrl: 'https://sap.github.io/cloud-sdk/docs/js/environments/kubernetes',
      frontmatter: '---\nid: kubernetes\ntitle: Migrate your App from SAP BTP CF to Kubernetes\n---\n',
      content: '# Migrate a Cloud Foundry Application to a Kubernetes Cluster\n\nThis guide details...'
    },
    {
      name: 'Cloud SDK AI JS - Orchestration',
      libraryId: '/cloud-sdk-ai-js',
      relFile: 'langchain/orchestration.mdx',
      expectedUrl: 'https://sap.github.io/ai-sdk/docs/js/langchain/orchestration',
      frontmatter: '---\nid: orchestration\ntitle: Orchestration Integration\n---\n',
      content: '# Orchestration Integration\n\nThe @sap-ai-sdk/langchain packages provides...'
    },
    {
      name: 'OpenUI5 API - Button Control',
      libraryId: '/openui5-api',
      relFile: 'src/sap/m/Button.js',
      expectedUrl: 'https://sdk.openui5.org/#/api/sap.m.Button',
      frontmatter: '',
      content: 'sap.ui.define([\n  "./library",\n  "sap/ui/core/Control",\n  // Button control implementation'
    },
    {
      name: 'OpenUI5 Samples - ButtonWithBadge',
      libraryId: '/openui5-samples',
      relFile: 'src/sap.m/test/sap/m/demokit/sample/ButtonWithBadge/Component.js',
      expectedUrl: 'https://sdk.openui5.org/entity/sap.m.Button/sample/sap.m.sample.ButtonWithBadge',
      frontmatter: '',
      content: 'sap.ui.define([\n  "sap/ui/core/UIComponent"\n], function (UIComponent) {\n  // Sample implementation'
    },
         {
       name: 'SAPUI5 - Multi-Selection Navigation',
       libraryId: '/sapui5',
       relFile: '06_SAP_Fiori_Elements/multi-selection-for-intent-based-navigation-640cabf.md',
       expectedUrl: 'https://ui5.sap.com/#/topic/640cabfd35c3469aacf31be28924d50d',
       frontmatter: '---\nid: 640cabfd35c3469aacf31be28924d50d\ntopic: 640cabfd35c3469aacf31be28924d50d\ntitle: Multi-Selection for Intent-Based Navigation\n---\n',
       content: '# Multi-Selection for Intent-Based Navigation\n\nThis feature allows...'
     },
    {
      name: 'UI5 Tooling - Builder Documentation',
      libraryId: '/ui5-tooling',
      relFile: 'pages/Builder.md',
      expectedUrl: 'https://sap.github.io/ui5-tooling/v4/pages/Builder#ui5-builder',
      frontmatter: '',
      content: '# UI5 Builder\n\nThe UI5 Builder module takes care of building your project...'
    },
    {
      name: 'UI5 Web Components - Configuration',
      libraryId: '/ui5-webcomponents',
      relFile: '2-advanced/01-configuration.md',
      expectedUrl: 'https://sap.github.io/ui5-webcomponents/docs/01-configuration#configuration',
      frontmatter: '',
      content: '# Configuration\n\nThis section explains how you can configure UI5 Web Components...'
    },
    {
      name: 'wdi5 - Locators Documentation',
      libraryId: '/wdi5',
      relFile: 'locators.md',
      expectedUrl: 'https://ui5-community.github.io/wdi5/#/locators',
      frontmatter: '---\nid: locators\ntitle: Locators\n---\n',
      content: '# Locators\n\nwdi5 provides various locators for UI5 controls...'
    },
    {
      name: 'UI5 TypeScript - FAQ Documentation',
      libraryId: '/ui5-typescript',
      relFile: 'faq.md',
      expectedUrl: 'https://github.com/UI5/typescript/blob/gh-pages/faq#faq---frequently-asked-questions-for-the-ui5-type-definitions',
      frontmatter: '',
      content: '# FAQ - Frequently Asked Questions for the UI5 Type Definitions\n\nWhile the [main page](README.md) answers the high-level questions...'
    },
    {
      name: 'UI5 CC Spreadsheet Importer - Checks Documentation',
      libraryId: '/ui5-cc-spreadsheetimporter',
      relFile: 'pages/Checks.md',
      expectedUrl: 'https://docs.spreadsheet-importer.com/pages/Checks/#error-types',
      frontmatter: '',
      content: '## Error Types\n\nThe following types of errors are handled by the UI5 Spreadsheet Upload Control...'
    },
    {
      name: 'ABAP Cheat Sheets - Internal Tables',
      libraryId: '/abap-cheat-sheets',
      relFile: '01_Internal_Tables.md',
      expectedUrl: 'https://github.com/SAP-samples/abap-cheat-sheets/blob/main/01_Internal_Tables#internal-tables',
      frontmatter: '',
      content: '# Internal Tables\n\nThis cheat sheet contains a selection of syntax examples and notes on internal tables...'
    },
    {
      name: 'SAP Style Guides - Clean ABAP',
      libraryId: '/sap-styleguides',
      relFile: 'clean-abap/CleanABAP.md',
      expectedUrl: 'https://github.com/SAP/styleguides/blob/main/CleanABAP#clean-abap',
      frontmatter: '',
      content: '# Clean ABAP\n\n> [**中文**](CleanABAP_zh.md)\n\nThis style guide presents the essentials of clean ABAP...'
    },
    {
      name: 'DSAG ABAP Leitfaden - Clean Core',
      libraryId: '/dsag-abap-leitfaden',
      relFile: 'clean-core/what-is-clean-core.md',
      expectedUrl: 'https://1dsag.github.io/ABAP-Leitfaden/clean-core/what-is-clean-core/#was-ist-clean-core',
      frontmatter: '',
      content: '# Was ist Clean Core?\n\nClean Core ist ein Konzept von SAP, das darauf abzielt...'
    },
    {
      name: 'ABAP Platform Fiori Feature Showcase - General Features',
      libraryId: '/abap-fiori-showcase',
      relFile: '01_general_features.md',
      expectedUrl: 'https://github.com/SAP-samples/abap-platform-fiori-feature-showcase/blob/main/01_general_features#general-features',
      frontmatter: '',
      content: '# General Features\n\nThis section describes the features that are generally used throughout...'
    },
    {
      name: 'CAP Fiori Elements Feature Showcase - README',
      libraryId: '/cap-fiori-showcase',
      relFile: 'README.md',
      expectedUrl: 'https://github.com/SAP-samples/fiori-elements-feature-showcase/blob/main/README#sap-fiori-elements-for-odata-v4-feature-showcase',
      frontmatter: '',
      content: '# SAP Fiori Elements for OData V4 Feature Showcase\n\nThis app showcases different features of SAP Fiori elements...'
    }
    // Note: Some sources like CAP, Cloud SDK AI, wdi5, etc. may need different file mappings
    // or fallback to mock content if actual files don't exist in expected locations
  ];

  describe('Main URL Generation Function', () => {
    testCases.forEach(({ name, libraryId, relFile, expectedUrl, frontmatter, content }) => {
      it(`should generate correct URL for ${name}`, () => {
        // Step 1: Get configuration from metadata.json
        const config = getConfigForLibrary(libraryId);
        
        // Step 2: Try to read from actual source file first, fallback to test data
        let fileContent = readFileContent(libraryId, relFile);
        let contentSource = 'real file';
        
        if (!fileContent) {
          // Fallback to hardcoded test data when real file is not available
          fileContent = frontmatter ? `${frontmatter}\n${content}` : content;
          contentSource = 'test data';
        }
        
        // For debugging: log which content source was used
        if (process.env.DEBUG_TESTS === 'true') {
          console.log(`\n[${name}] Using ${contentSource}`);
          console.log(`File path: ${libraryId}/${relFile}`);
          console.log(`Content preview: ${fileContent.slice(0, 100)}...`);
        }
        
        // Step 3: Generate URL using the URL generation system
        const result = generateDocumentationUrl(libraryId, relFile, fileContent, config);
        
        // Step 4: Validate the result
        expect(result).toBe(expectedUrl);
      });
    });
  });

  describe('Individual Generator Classes', () => {
    
    describe('CloudSdkUrlGenerator', () => {
      it('should generate URLs using frontmatter ID', () => {
        const config = getConfigForLibrary('/cloud-sdk-js');
        const generator = new CloudSdkUrlGenerator('/cloud-sdk-js', config);
        const content = '---\nid: kubernetes\n---\n# Migration Guide';
        
        const result = generator.generateUrl({
          libraryId: '/cloud-sdk-js',
          relFile: 'environments/migrate.mdx',
          content,
          config
        });
        
        expect(result).toBe('https://sap.github.io/cloud-sdk/docs/js/environments/kubernetes');
      });

      it('should handle AI SDK variants differently', () => {
        const config = getConfigForLibrary('/cloud-sdk-ai-js');
        const generator = new CloudSdkUrlGenerator('/cloud-sdk-ai-js', config);
        const content = '---\nid: orchestration\n---\n# Orchestration';
        
        const result = generator.generateUrl({
          libraryId: '/cloud-sdk-ai-js',
          relFile: 'langchain/orchestration.mdx',
          content,
          config
        });
        
        expect(result).toBe('https://sap.github.io/ai-sdk/docs/js/langchain/orchestration');
      });
    });

    describe('SapUi5UrlGenerator', () => {
      it('should generate topic-based URLs for SAPUI5', () => {
        const config = getConfigForLibrary('/sapui5');
        const generator = new SapUi5UrlGenerator('/sapui5', config);
        const content = '---\nid: 123e4567-e89b-12d3-a456-426614174000\n---\n# Topic Content';
        
        const result = generator.generateUrl({
          libraryId: '/sapui5',
          relFile: 'docs/topic.md',
          content,
          config
        });
        
        expect(result).toBe('https://ui5.sap.com/#/topic/123e4567-e89b-12d3-a456-426614174000');
      });

      it('should generate API URLs for OpenUI5 controls', () => {
        const config = getConfigForLibrary('/openui5-api');
        const generator = new SapUi5UrlGenerator('/openui5-api', config);
        const content = 'sap.ui.define([\n  "sap/m/Button"\n], function(Button) {';
        
        const result = generator.generateUrl({
          libraryId: '/openui5-api',
          relFile: 'src/sap/m/Button.js',
          content,
          config
        });
        
        expect(result).toBe('https://sdk.openui5.org/#/api/sap.m.Button');
      });
    });

    describe('CapUrlGenerator', () => {
      it('should generate docsify-style URLs', () => {
        const config = getConfigForLibrary('/cap');
        const generator = new CapUrlGenerator('/cap', config);
        const content = '---\nid: getting-started\n---\n# Getting Started';
        
        const result = generator.generateUrl({
          libraryId: '/cap',
          relFile: 'guides/getting-started.md',
          content,
          config
        });
        
        expect(result).toBe('https://cap.cloud.sap/docs/#/guides/getting-started');
      });

      it('should handle CDS-specific sections', () => {
        const config = getConfigForLibrary('/cap');
        const generator = new CapUrlGenerator('/cap', config);
        const content = '---\nslug: cds-types\n---\n# CDS Types';
        
        const result = generator.generateUrl({
          libraryId: '/cap',
          relFile: 'cds/types.md',
          content,
          config
        });
        
        expect(result).toBe('https://cap.cloud.sap/docs/#/cds/cds-types');
      });
    });

    describe('Wdi5UrlGenerator', () => {
      it('should generate docsify-style URLs for wdi5', () => {
        const config = getConfigForLibrary('/wdi5');
        const generator = new Wdi5UrlGenerator('/wdi5', config);
        const content = '---\nid: locators\n---\n# Locators';
        
        const result = generator.generateUrl({
          libraryId: '/wdi5',
          relFile: 'locators.md',
          content,
          config
        });
        
        expect(result).toBe('https://ui5-community.github.io/wdi5/#/locators');
      });

      it('should handle configuration-specific sections', () => {
        const config = getConfigForLibrary('/wdi5');
        const generator = new Wdi5UrlGenerator('/wdi5', config);
        const content = '---\nid: basic-config\n---\n# Basic Configuration';
        
        const result = generator.generateUrl({
          libraryId: '/wdi5',
          relFile: 'configuration/basic.md',
          content,
          config
        });
        
        expect(result).toBe('https://ui5-community.github.io/wdi5/#/configuration/basic-config');
      });
    });

    describe('DsagUrlGenerator', () => {
      it('should generate GitHub Pages URLs with path transformation', () => {
        const config = getConfigForLibrary('/dsag-abap-leitfaden');
        const generator = new DsagUrlGenerator('/dsag-abap-leitfaden', config);
        const content = '# Was ist Clean Core?\n\nClean Core ist ein Konzept von SAP...';
        
        const result = generator.generateUrl({
          libraryId: '/dsag-abap-leitfaden',
          relFile: 'clean-core/what-is-clean-core.md',
          content,
          config
        });
        
        expect(result).toBe('https://1dsag.github.io/ABAP-Leitfaden/clean-core/what-is-clean-core/#was-ist-clean-core');
      });

      it('should handle root-level documentation', () => {
        const config = getConfigForLibrary('/dsag-abap-leitfaden');
        const generator = new DsagUrlGenerator('/dsag-abap-leitfaden', config);
        const content = '# ABAP Leitfaden\n\nDer DSAG ABAP Leitfaden...';
        
        const result = generator.generateUrl({
          libraryId: '/dsag-abap-leitfaden',
          relFile: 'README.md',
          content,
          config
        });
        
        expect(result).toBe('https://1dsag.github.io/ABAP-Leitfaden/README/#abap-leitfaden');
      });
    });

    describe('GenericUrlGenerator', () => {
      it('should handle generic sources with frontmatter', () => {
        const config = getConfigForLibrary('/ui5-tooling'); // Use a real generic source
        const generator = new GenericUrlGenerator('/ui5-tooling', config);
        const content = '---\nid: test-doc\n---\n# Test Document';
        
        const result = generator.generateUrl({
          libraryId: '/ui5-tooling',
          relFile: 'pages/test.md',
          content,
          config
        });
        
        expect(result).toBe('https://sap.github.io/ui5-tooling/v4/pages/test-doc#test-document');
      });

      it('should fallback to filename when no frontmatter', () => {
        const config = getConfigForLibrary('/ui5-tooling'); // Use a real generic source
        const generator = new GenericUrlGenerator('/ui5-tooling', config);
        const content = '# Test Document\n\nSome content...';
        
        const result = generator.generateUrl({
          libraryId: '/ui5-tooling',
          relFile: 'pages/test.md',
          content,
          config
        });
        
        expect(result).toBe('https://sap.github.io/ui5-tooling/v4/pages/test#test-document');
      });
    });

    describe('AbapUrlGenerator', () => {
      it('should generate correct cloud URLs for latest version', () => {
        // Mock config for ABAP documentation
        const config: DocUrlConfig = {
          baseUrl: 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US',
          pathPattern: '/latest/en-US/{filename}',
          anchorStyle: 'lowercase-with-dashes'
        };
        
        const generator = new AbapUrlGenerator('/abap-docs-latest', config);
        
        const result = generator.generateSourceSpecificUrl({
          libraryId: '/abap-docs-latest',
          relFile: 'abeninline_declarations.md',
          content: '# Inline Declarations',
          config
        });
        
        expect(result).toBe('https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/abeninline_declarations.html');
      });

      it('should generate correct cloud URLs for version 9.16', () => {
        const config: DocUrlConfig = {
          baseUrl: 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US',
          pathPattern: '/9.16/en-US/{filename}',
          anchorStyle: 'lowercase-with-dashes'
        };
        
        const generator = new AbapUrlGenerator('/abap-docs-916', config);
        
        const result = generator.generateSourceSpecificUrl({
          libraryId: '/abap-docs-916',
          relFile: 'md/abapselect.md',
          content: '# SELECT Statement',
          config
        });
        
        expect(result).toBe('https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/abapselect.html');
      });

      it('should generate correct cloud URLs for S/4HANA 2025 version 8.10', () => {
        const config: DocUrlConfig = {
          baseUrl: 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US',
          pathPattern: '/8.10/en-US/{filename}',
          anchorStyle: 'lowercase-with-dashes'
        };
        
        const generator = new AbapUrlGenerator('/abap-docs-810', config);
        
        const result = generator.generateSourceSpecificUrl({
          libraryId: '/abap-docs-810',
          relFile: 'abaploop.md',
          content: '# LOOP Statement',
          config
        });
        
        expect(result).toBe('https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/abaploop.html');
      });

      it('should generate correct legacy URLs for version 7.58', () => {
        const config: DocUrlConfig = {
          baseUrl: 'https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US',
          pathPattern: '/7.58/en-US/{filename}',
          anchorStyle: 'lowercase-with-dashes'
        };
        
        const generator = new AbapUrlGenerator('/abap-docs-758', config);
        
        const result = generator.generateSourceSpecificUrl({
          libraryId: '/abap-docs-758',
          relFile: 'md/abapdata.md',
          content: '# DATA Statement',
          config
        });
        
        expect(result).toBe('https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/abapdata.html');
      });

      it('should handle anchors correctly', () => {
        const config: DocUrlConfig = {
          baseUrl: 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US',
          pathPattern: '/latest/en-US/{filename}',
          anchorStyle: 'lowercase-with-dashes'
        };
        
        const generator = new AbapUrlGenerator('/abap-docs-latest', config);
        
        const result = generator.generateSourceSpecificUrl({
          libraryId: '/abap-docs-latest',
          relFile: 'abeninline_declarations.md',
          content: '# Inline Declarations',
          config,
          anchor: 'syntax'
        });
        
        expect(result).toBe('https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/abeninline_declarations.html#syntax');
      });

      it('should correctly extract version from library ID', () => {
        const testCases = [
          { libraryId: '/abap-docs-758', expected: '7.58' },
          { libraryId: '/abap-docs-latest', expected: 'latest' },
          { libraryId: '/abap-docs-916', expected: '9.16' },
          { libraryId: '/abap-docs-810', expected: '8.10' }
        ];

        testCases.forEach(({ libraryId, expected }) => {
          const config: DocUrlConfig = {
            baseUrl: 'https://example.com',
            pathPattern: `/${expected}/en-US/{filename}`,
            anchorStyle: 'lowercase-with-dashes'
          };
          
          const generator = new AbapUrlGenerator(libraryId, config);
          
          // Test the version extraction by checking the generated URL
          const result = generator.generateSourceSpecificUrl({
            libraryId,
            relFile: 'test.md',
            content: '# Test',
            config
          });
          
          if (expected === 'latest' || parseFloat(expected) >= 9.1 || parseFloat(expected) >= 8.1) {
            expect(result).toContain('abapdocu_cp_index_htm/CLOUD');
          } else {
            const versionCode = expected.replace('.', '');
            expect(result).toContain(`abapdocu_${versionCode}_index_htm/${expected}`);
          }
        });
      });

      it('should use .html extension instead of .htm for file extension', () => {
        const config: DocUrlConfig = {
          baseUrl: 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US',
          pathPattern: '/latest/en-US/{filename}',
          anchorStyle: 'lowercase-with-dashes'
        };
        
        const generator = new AbapUrlGenerator('/abap-docs-latest', config);
        
        const result = generator.generateSourceSpecificUrl({
          libraryId: '/abap-docs-latest',
          relFile: 'abeninline_declarations.md',
          content: '# Inline Declarations',
          config
        });
        
        // Should use .html file extension (not .htm file extension)
        expect(result).toContain('abeninline_declarations.html');
        expect(result).toMatch(/\.html$/);
        expect(result).not.toMatch(/\.htm$/);
      });

      it('should point to latest cloud version instead of legacy 7.58 version', () => {
        const config: DocUrlConfig = {
          baseUrl: 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US',
          pathPattern: '/latest/en-US/{filename}',
          anchorStyle: 'lowercase-with-dashes'
        };
        
        const generator = new AbapUrlGenerator('/abap-docs-latest', config);
        
        const result = generator.generateSourceSpecificUrl({
          libraryId: '/abap-docs-latest',
          relFile: 'abeninline_declarations.md',
          content: '# Inline Declarations',
          config
        });
        
        // Should use the new cloud URL pattern instead of the old 7.58 pattern
        expect(result).toContain('abapdocu_cp_index_htm/CLOUD');
        expect(result).not.toContain('abapdocu_758_index_htm/7.58');
        expect(result).not.toContain('abapdocu_latest_index_htm/latest');
        
        // The full URL should match the expected cloud pattern
        expect(result).toBe('https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/abeninline_declarations.html');
      });
    });
  });

  describe('Error Handling', () => {
    it('should return null for missing config', () => {
      const result = generateDocumentationUrl('/unknown', 'file.md', 'content', null as any);
      expect(result).toBeNull();
    });

    it('should handle malformed frontmatter gracefully', () => {
      // Test with a non-existent library ID that will use the generic generator
      const config = getConfigForLibrary('/ui5-tooling'); // Use a real config for fallback testing
      
      const content = '---\ninvalid: yaml: content:\n---\n# Content';
      const result = generateDocumentationUrl('/ui5-tooling', 'test.md', content, config);
      
      expect(result).not.toBeNull();
    });
  });

  describe('URL Pattern Validation', () => {
    testCases.forEach(({ name, expectedUrl }) => {
      it(`should generate valid URL format for ${name}`, () => {
        expect(expectedUrl).toMatch(/^https?:\/\//);
        expect(() => new URL(expectedUrl)).not.toThrow();
      });
    });
  });
});

```

--------------------------------------------------------------------------------
/src/lib/BaseServerHandler.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Base Server Handler - Shared functionality for MCP servers
 * Eliminates code duplication between stdio and HTTP server implementations
 * 
 * IMPORTANT FOR LLMs/AI ASSISTANTS:
 * =================================
 * The function names in this MCP server may appear with different prefixes depending on your MCP client:
 * - Simple names: search, fetch, sap_community_search, sap_help_search, sap_help_get
 * - Prefixed names: mcp_sap-docs-remote_search, mcp_sap-docs-remote_fetch, etc.
 * 
 * Try the simple names first, then the prefixed versions if they don't work.
 * 
 * Note: sap_docs_search and sap_docs_get are legacy aliases for backward compatibility.
 */

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListPromptsRequestSchema,
  GetPromptRequestSchema
} from "@modelcontextprotocol/sdk/types.js";
import {
  searchLibraries,
  fetchLibraryDocumentation,
  listDocumentationResources,
  readDocumentationResource,
  searchCommunity
} from "./localDocs.js";
import { searchSapHelp, getSapHelpContent } from "./sapHelp.js";

import { SearchResponse } from "./types.js";
import { logger } from "./logger.js";
import { search } from "./search.js";
import { CONFIG } from "./config.js";
import { loadMetadata, getDocUrlConfig } from "./metadata.js";
import { generateDocumentationUrl, formatSearchResult } from "./url-generation/index.js";

/**
 * Helper functions for creating structured JSON responses compatible with ChatGPT and all MCP clients
 */

interface SearchResult {
  id: string;
  title: string;
  url: string;
  snippet?: string;
  score?: number;
  metadata?: Record<string, any>;
}

interface DocumentResult {
  id: string;
  title: string;
  text: string;
  url: string;
  metadata?: Record<string, any>;
}

/**
 * Create structured JSON response for search results (ChatGPT-compatible)
 */
function createSearchResponse(results: SearchResult[]): any {
  // Clean the results to avoid JSON serialization issues in MCP protocol
  const cleanedResults = results.map(result => ({
    // ChatGPT requires: id, title, url (other fields optional)
    id: result.id,
    title: result.title ? result.title.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n') : result.title,
    url: result.url,
    // Additional fields for enhanced functionality
    snippet: result.snippet ? result.snippet.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n') : result.snippet,
    score: result.score,
    metadata: result.metadata
  }));
  
  // ChatGPT expects: { "results": [...] } in JSON-encoded text content
  return {
    content: [
      {
        type: "text",
        text: JSON.stringify({ results: cleanedResults })
      }
    ]
  };
}

/**
 * Create structured JSON response for document fetch (ChatGPT-compatible)
 */
function createDocumentResponse(document: DocumentResult): any {
  // Clean the text content to avoid JSON serialization issues in MCP protocol
  const cleanedDocument = {
    // ChatGPT requires: id, title, text, url, metadata
    id: document.id,
    title: document.title,
    text: document.text
      .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '') // Remove control chars except \n, \r, \t
      .replace(/\r\n/g, '\n') // Normalize line endings
      .replace(/\r/g, '\n'), // Convert remaining \r to \n
    url: document.url,
    metadata: document.metadata
  };
  
  // ChatGPT expects document object as JSON-encoded text content
  return {
    content: [
      {
        type: "text", 
        text: JSON.stringify(cleanedDocument)
      }
    ]
  };
}

/**
 * Create error response in structured JSON format
 */
function createErrorResponse(error: string, requestId?: string): any {
  return {
    content: [
      {
        type: "text",
        text: JSON.stringify({ 
          error,
          requestId: requestId || 'unknown'
        })
      }
    ]
  };
}

export interface ServerConfig {
  name: string;
  description: string;
  version: string;
}

/**
 * Helper function to extract client metadata from request
 */
function extractClientMetadata(request: any): Record<string, any> {
  const metadata: Record<string, any> = {};
  
  // Try to extract available metadata from the request
  if (request.meta) {
    metadata.meta = request.meta;
  }
  
  // Extract any client identification from headers or other sources
  if (request.headers) {
    metadata.headers = request.headers;
  }
  
  // Extract transport information if available
  if (request.transport) {
    metadata.transport = request.transport;
  }
  
  // Extract session or connection info
  if (request.id) {
    metadata.requestId = request.id;
  }
  
  return metadata;
}

/**
 * Base Server Handler Class
 * Provides shared functionality for all MCP server implementations
 */
export class BaseServerHandler {
  
  /**
   * Configure server with shared resource and tool handlers
   */
  static configureServer(srv: Server): void {
    // Only setup resource handlers if resources capability is enabled
    // DISABLED: Resources capability causes 60,000+ resources which breaks Cursor
    // this.setupResourceHandlers(srv);
    this.setupToolHandlers(srv);

    const capabilities = (srv as unknown as { _capabilities?: { prompts?: object } })._capabilities;
    if (capabilities?.prompts) {
      this.setupPromptHandlers(srv);
    }
  }

  /**
   * Setup resource handlers (shared between all server types)
   */
  private static setupResourceHandlers(srv: Server): void {
    // List available resources
    srv.setRequestHandler(ListResourcesRequestSchema, async () => {
      const resources = await listDocumentationResources();
      return { resources };
    });

    // Read resource contents
    srv.setRequestHandler(ReadResourceRequestSchema, async (request) => {
      const { uri } = request.params;
      try {
        return await readDocumentationResource(uri);
      } catch (error: any) {
        return {
          contents: [{
            uri,
            mimeType: "text/plain",
            text: `Error reading resource: ${error.message}`
          }]
        };
      }
    });
  }

  /**
   * Setup tool handlers (shared between all server types)
   */
  private static setupToolHandlers(srv: Server): void {
    // List available tools
    srv.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          {
            name: "sap_community_search", 
            description: `SEARCH SAP COMMUNITY: sap_community_search(query="search terms")

FUNCTION NAME: sap_community_search (or mcp_sap-docs-remote_sap_community_search)

FINDS: Blog posts, discussions, solutions from SAP Community
INCLUDES: Engagement data (kudos), ranked by "Best Match"

TYPICAL WORKFLOW:
1. sap_community_search(query="your problem + error code")
2. fetch(id="community-12345") for full posts

BEST FOR TROUBLESHOOTING:
• Include error codes: "415 error", "500 error"
• Be specific: "CAP action binary upload 415"
• Use real scenarios: "wizard implementation issues"`,
            inputSchema: {
              type: "object",
              properties: {
                query: {
                  type: "string",
                  description: "Search terms for SAP Community. Include error codes and specific technical details.",
                  examples: [
                    "CAP action parameter binary file upload 415 error",
                    "wizard implementation best practices",
                    "fiori elements authentication",
                    "UI5 deployment issues",
                    "wdi5 test automation problems"
                  ]
                }
              },
              required: ["query"]
            }
          },
          {
            name: "sap_help_search",
            description: `SEARCH SAP HELP PORTAL: sap_help_search(query="product + topic")

FUNCTION NAME: sap_help_search (or mcp_sap-docs-remote_sap_help_search)

SEARCHES: Official SAP Help Portal (help.sap.com)
COVERS: Product guides, implementation guides, technical documentation

TYPICAL WORKFLOW:
1. sap_help_search(query="product name + configuration topic")
2. sap_help_get(result_id="sap-help-12345abc")

BEST PRACTICES:
• Include product names: "S/4HANA", "BTP", "Fiori"
• Add specific tasks: "configuration", "setup", "deployment"
• Use official SAP terminology`,
            inputSchema: {
              type: "object",
              properties: {
                query: {
                  type: "string",
                  description: "Search terms for SAP Help Portal. Include product names and specific topics.",
                  examples: [
                    "S/4HANA configuration",
                    "Fiori Launchpad setup", 
                    "BTP integration",
                    "ABAP development guide",
                    "SAP Analytics Cloud setup"
                  ]
                }
              },
              required: ["query"]
            }
          },
          {
            name: "sap_help_get", 
            description: `GET SAP HELP PAGE: sap_help_get(result_id="sap-help-12345abc")

FUNCTION NAME: sap_help_get (or mcp_sap-docs-remote_sap_help_get)

RETRIEVES: Complete SAP Help Portal page content
REQUIRES: Exact result_id from sap_help_search

USAGE PATTERN:
1. Get ID from sap_help_search results  
2. Use exact ID (don't modify the format)
3. Receive full page content + metadata`,
            inputSchema: {
              type: "object",
              properties: {
                result_id: {
                  type: "string",
                  description: "Exact ID from sap_help_search results. Copy the ID exactly as returned.",
                  examples: [
                    "sap-help-12345abc",
                    "sap-help-98765def"
                  ]
                }
              },
              required: ["result_id"]
            }
          },
          {
            name: "search",
            description: `SEARCH SAP DOCS: search(query="search terms")

FUNCTION NAME: search

COVERS: ABAP (all versions), UI5, CAP, wdi5, OpenUI5 APIs, Cloud SDK
AUTO-DETECTS: ABAP versions from query (e.g. "LOOP 7.57", defaults to 7.58)

TYPICAL WORKFLOW:
1. search(query="your search terms") 
2. fetch(id="result_id_from_step_1")

QUERY TIPS:
• Be specific: "CAP action binary parameter" not just "CAP"
• Include error codes: "415 error CAP action"
• Use technical terms: "LargeBinary MediaType XMLHttpRequest"
• For ABAP: Include version like "7.58" or "latest"`,
            inputSchema: {
              type: "object",
              properties: {
                query: {
                  type: "string",
                  description: "Search terms using natural language. Be specific and include technical terms.",
                  examples: [
                    "CAP binary data LargeBinary MediaType",
                    "UI5 button properties",
                    "wdi5 testing locators", 
                    "ABAP SELECT statements 7.58",
                    "415 error CAP action parameter"
                  ]
                }
              },
              required: ["query"]
            }
          },
          {
            name: "fetch",
            description: `GET SPECIFIC DOCS: fetch(id="result_id")

FUNCTION NAME: fetch

RETRIEVES: Full content from search results
WORKS WITH: Document IDs returned by search

ChatGPT COMPATIBLE:
• Uses "id" parameter (required by ChatGPT)
• Returns structured JSON content
• Includes full document text and metadata`,
            inputSchema: {
              type: "object",
              properties: {
                id: {
                  type: "string",
                  description: "Unique document ID from search results. Use exact IDs returned by search.",
                  examples: [
                    "/cap/guides/domain-modeling",
                    "/sapui5/controls/button-properties", 
                    "/openui5-api/sap/m/Button",
                    "/abap-docs-758/inline-declarations",
                    "community-12345"
                  ]
                }
              },
              required: ["id"]
            }
          },

        ]
      };
    });

    // Handle tool execution
    srv.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;
      const clientMetadata = extractClientMetadata(request);

      if (name === "sap_docs_search" || name === "search") {
        const { query } = args as { query: string };
        
        // Enhanced logging with timing
        const timing = logger.logToolStart(name, query, clientMetadata);
        
        try {
          // Use hybrid search with reranking
          const results = await search(query, { 
            k: CONFIG.RETURN_K 
          });
          
          const topResults = results;
          
          if (topResults.length === 0) {
            logger.logToolSuccess(name, timing.requestId, timing.startTime, 0, { fallback: false });
            return createErrorResponse(
              `No results for "${query}". Try UI5 controls ("button", "table"), CAP topics ("actions", "binary"), testing ("wdi5", "e2e"), ABAP with versions ("SELECT 7.58"), or include error codes ("415 error").`,
              timing.requestId
            );
          }
          
          // Transform results to ChatGPT-compatible format with id, title, url
          const searchResults: SearchResult[] = topResults.map((r, index) => {
            // Extract library_id and topic from document ID
            const libraryIdMatch = r.id.match(/^(\/[^\/]+)/);
            const libraryId = libraryIdMatch ? libraryIdMatch[1] : (r.sourceId ? `/${r.sourceId}` : r.id);
            const topic = r.id.startsWith(libraryId) ? r.id.slice(libraryId.length + 1) : '';
            
            const config = getDocUrlConfig(libraryId);
            const docUrl = config ? generateDocumentationUrl(libraryId, '', r.text, config) : null;
            
            return {
              // ChatGPT-required format: id, title, url
              id: r.id,  // Use full document ID as required by ChatGPT
              title: r.text.split('\n')[0] || r.id,
              url: docUrl || `#${r.id}`,
              // Additional fields for backward compatibility
              library_id: libraryId,
              topic: topic,
              snippet: r.text ? r.text.substring(0, CONFIG.EXCERPT_LENGTH_MAIN) + '...' : '',
              score: r.finalScore,
              metadata: {
                source: r.sourceId || 'sap-docs',
                library: libraryId,
                bm25Score: r.bm25,
                rank: index + 1
              }
            };
          });
          
          logger.logToolSuccess(name, timing.requestId, timing.startTime, topResults.length, { fallback: false });
          
          return createSearchResponse(searchResults);
        } catch (error) {
          logger.logToolError(name, timing.requestId, timing.startTime, error, false);
          logger.info('Attempting fallback to original search after hybrid search failure');
          
          // Fallback to original search
          try {
            const res: SearchResponse = await searchLibraries(query);
            
            if (!res.results.length) {
              logger.logToolSuccess(name, timing.requestId, timing.startTime, 0, { fallback: true });
              return createErrorResponse(
                res.error || `No fallback results for "${query}". Try UI5 controls ("button", "table"), CAP topics ("actions", "binary"), testing ("wdi5", "e2e"), ABAP with versions ("SELECT 7.58"), or include error codes.`,
                timing.requestId
              );
            }
            
            // Transform fallback results to structured format
            const fallbackResults: SearchResult[] = res.results.map((r, index) => ({
              id: r.id || `fallback-${index}`,
              title: r.title || 'SAP Documentation',
              url: r.url || `#${r.id}`,
              snippet: r.description ? r.description.substring(0, 200) + '...' : '',
              metadata: {
                source: 'fallback-search',
                rank: index + 1
              }
            }));
            
            logger.logToolSuccess(name, timing.requestId, timing.startTime, res.results.length, { fallback: true });
            
            return createSearchResponse(fallbackResults);
          } catch (fallbackError) {
            logger.logToolError(name, timing.requestId, timing.startTime, fallbackError, true);
            return createErrorResponse(
              `Search temporarily unavailable. Wait 30 seconds and retry, try sap_community_search instead, or use more specific search terms.`,
              timing.requestId
            );
          }
        }
      }

      if (name === "sap_community_search") {
        const { query } = args as { query: string };
        
        // Enhanced logging with timing
        const timing = logger.logToolStart(name, query, clientMetadata);
        
        try {
          const res: SearchResponse = await searchCommunity(query);
          
          if (!res.results.length) {
            logger.logToolSuccess(name, timing.requestId, timing.startTime, 0);
            return createErrorResponse(
              res.error || `No SAP Community posts found for "${query}". Try different keywords or check your connection.`,
              timing.requestId
            );
          }
          
          // Transform community search results to ChatGPT-compatible format
          const communityResults: SearchResult[] = res.results.map((r: any, index) => ({
            // ChatGPT-required format: id, title, url
            id: r.id || `community-${index}`,
            title: r.title || 'SAP Community Post',
            url: r.url || `#${r.id}`,
            // Additional fields for enhanced functionality
            library_id: r.library_id || `community-${index}`,
            topic: r.topic || '',
            snippet: r.snippet || (r.description ? r.description.substring(0, 200) + '...' : ''),
            score: r.score || 0,
            metadata: r.metadata || {
              source: 'sap-community',
              likes: r.likes,
              author: r.author,
              postTime: r.postTime,
              rank: index + 1
            }
          }));
          
          logger.logToolSuccess(name, timing.requestId, timing.startTime, res.results.length);
          
          return createSearchResponse(communityResults);
        } catch (error) {
          logger.logToolError(name, timing.requestId, timing.startTime, error);
          return createErrorResponse(
            `SAP Community search service temporarily unavailable. Please try again later.`,
            timing.requestId
          );
        }
      }

      if (name === "sap_docs_get" || name === "fetch") {
        // Handle both old format (library_id) and new ChatGPT format (id)
        const library_id = (args as any).library_id || (args as any).id;
        const topic = (args as any).topic || "";
        
        if (!library_id) {
          const timing = logger.logToolStart(name, 'missing_id', clientMetadata);
          logger.logToolError(name, timing.requestId, timing.startTime, new Error('Missing id parameter'));
          return createErrorResponse(
            `Missing required parameter: id. Please provide a document ID from search results.`,
            timing.requestId
          );
        }
        
        // Enhanced logging with timing
        const searchKey = library_id + (topic ? `/${topic}` : '');
        const timing = logger.logToolStart(name, searchKey, clientMetadata);
        
        try {
          const text = await fetchLibraryDocumentation(library_id, topic);
          
          if (!text) {
            logger.logToolSuccess(name, timing.requestId, timing.startTime, 0);
            return createErrorResponse(
              `Nothing found for ${library_id}`,
              timing.requestId
            );
          }
          
          // Transform document content to ChatGPT-compatible format
          const config = getDocUrlConfig(library_id);
          const docUrl = config ? generateDocumentationUrl(library_id, '', text, config) : null;
          const document: DocumentResult = {
            id: library_id,
            title: library_id.replace(/^\//, '').replace(/\//g, ' > ') + (topic ? ` (${topic})` : ''),
            text: text,
            url: docUrl || `#${library_id}`,
            metadata: {
              source: 'sap-docs',
              library: library_id,
              topic: topic || undefined,
              contentLength: text.length
            }
          };
          
          logger.logToolSuccess(name, timing.requestId, timing.startTime, 1, { 
            contentLength: text.length,
            libraryId: library_id,
            topic: topic || undefined
          });
          
          return createDocumentResponse(document);
        } catch (error) {
          logger.logToolError(name, timing.requestId, timing.startTime, error);
          return createErrorResponse(
            `Error retrieving documentation for ${library_id}. Please try again later.`,
            timing.requestId
          );
        }
      }

      if (name === "sap_help_search") {
        const { query } = args as { query: string };
        
        // Enhanced logging with timing
        const timing = logger.logToolStart(name, query, clientMetadata);
        
        try {
          const res: SearchResponse = await searchSapHelp(query);
          
          if (!res.results.length) {
            logger.logToolSuccess(name, timing.requestId, timing.startTime, 0);
            return createErrorResponse(
              res.error || `No SAP Help results found for "${query}". Try different keywords or check your connection.`,
              timing.requestId
            );
          }
          
          // Transform SAP Help search results to ChatGPT-compatible format
          const helpResults: SearchResult[] = res.results.map((r, index) => ({
            // ChatGPT-required format: id, title, url
            id: r.id || `sap-help-${index}`,
            title: r.title || 'SAP Help Document',
            url: r.url || `#${r.id}`,
            // Additional fields for enhanced functionality
            snippet: r.description ? r.description.substring(0, 200) + '...' : '',
            metadata: {
              source: 'sap-help',
              totalSnippets: r.totalSnippets,
              rank: index + 1
            }
          }));
          
          logger.logToolSuccess(name, timing.requestId, timing.startTime, res.results.length);
          
          return createSearchResponse(helpResults);
        } catch (error) {
          logger.logToolError(name, timing.requestId, timing.startTime, error);
          return createErrorResponse(
            `SAP Help search service temporarily unavailable. Please try again later.`,
            timing.requestId
          );
        }
      }

      if (name === "sap_help_get") {
        const { result_id } = args as { result_id: string };
        
        // Enhanced logging with timing
        const timing = logger.logToolStart(name, result_id, clientMetadata);
        
        try {
          const content = await getSapHelpContent(result_id);
          
          // Transform SAP Help content to structured format
          const document: DocumentResult = {
            id: result_id,
            title: `SAP Help Document (${result_id})`,
            text: content,
            url: `https://help.sap.com/#${result_id}`,
            metadata: {
              source: 'sap-help',
              resultId: result_id,
              contentLength: content.length
            }
          };
          
          logger.logToolSuccess(name, timing.requestId, timing.startTime, 1, { 
            contentLength: content.length,
            resultId: result_id
          });
          
          return createDocumentResponse(document);
        } catch (error) {
          logger.logToolError(name, timing.requestId, timing.startTime, error);
          return createErrorResponse(
            `Error retrieving SAP Help content. Please try again later.`,
            timing.requestId
          );
        }
      }



      throw new Error(`Unknown tool: ${name}`);
    });
  }

  /**
   * Setup prompt handlers (shared between all server types)
   */
  private static setupPromptHandlers(srv: Server): void {
    // List available prompts
    srv.setRequestHandler(ListPromptsRequestSchema, async () => {
      return {
        prompts: [
          {
            name: "sap_search_help",
            title: "SAP Documentation Search Helper",
            description: "Helps users construct effective search queries for SAP documentation",
            arguments: [
              {
                name: "domain",
                description: "SAP domain (UI5, CAP, ABAP, etc.)",
                required: false
              },
              {
                name: "context",
                description: "Specific context or technology area",
                required: false
              }
            ]
          },
          {
            name: "sap_troubleshoot",
            title: "SAP Issue Troubleshooting Guide",
            description: "Guides users through troubleshooting common SAP development issues",
            arguments: [
              {
                name: "error_message",
                description: "Error message or symptom description",
                required: false
              },
              {
                name: "technology",
                description: "SAP technology stack (UI5, CAP, ABAP, etc.)",
                required: false
              }
            ]
          }
        ]
      };
    });

    // Get specific prompt
    srv.setRequestHandler(GetPromptRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;
      
      switch (name) {
        case "sap_search_help":
          const domain = args?.domain || "general SAP";
          const context = args?.context || "development";
          
          return {
            description: `Search helper for ${domain} documentation`,
            messages: [
              {
                role: "user",
                content: {
                  type: "text",
                  text: `I need help searching ${domain} documentation for ${context}. What search terms should I use to find the most relevant results?

Here are some tips for effective SAP documentation searches:

**For UI5/Frontend:**
- Include specific control names (e.g., "Table", "Button", "ObjectPage")
- Mention UI5 version if relevant
- Use terms like "properties", "events", "aggregations"

**For CAP/Backend:**
- Include CDS concepts (e.g., "entity", "service", "annotation")
- Mention specific features (e.g., "authentication", "authorization", "events")
- Use terms like "deployment", "configuration"

**For ABAP:**
- Include version number (e.g., "7.58", "latest")
- Use specific statement types (e.g., "SELECT", "LOOP", "MODIFY")
- Include object types (e.g., "class", "method", "interface")

**General Tips:**
- Be specific rather than broad
- Include error codes if troubleshooting
- Use technical terms rather than business descriptions
- Combine multiple related terms

What specific topic are you looking for help with?`
                }
              }
            ]
          };

        case "sap_troubleshoot":
          const errorMessage = args?.error_message || "an issue";
          const technology = args?.technology || "SAP";
          
          return {
            description: `Troubleshooting guide for ${technology}`,
            messages: [
              {
                role: "user", 
                content: {
                  type: "text",
                  text: `I'm experiencing ${errorMessage} with ${technology}. Let me help you troubleshoot this systematically.

**Step 1: Information Gathering**
- What is the exact error message or symptom?
- When does this occur (during development, runtime, deployment)?
- What were you trying to accomplish?
- What technology stack are you using?

**Step 2: Initial Search Strategy**
Let me search the SAP documentation for similar issues:

**For UI5 Issues:**
- Search for the exact error message
- Include control or component names
- Look for browser console errors

**For CAP Issues:**
- Check service definitions and annotations
- Look for deployment configuration
- Verify database connections

**For ABAP Issues:**
- Include ABAP version in search
- Look for syntax or runtime errors
- Check object dependencies

**Step 3: Common Solutions**
Based on the issue type, I'll search for:
- Official SAP documentation
- Community discussions
- Code examples and samples

Please provide more details about your specific issue, and I'll search for relevant solutions.`
                }
              }
            ]
          };

        default:
          throw new Error(`Unknown prompt: ${name}`);
      }
    });
  }

  /**
   * Initialize metadata system (shared initialization logic)
   */
  static initializeMetadata(): void {
    logger.info('Initializing BM25 search system...');
    try {
      loadMetadata();
      logger.info('Search system ready with metadata');
    } catch (error) {
      logger.warn('Metadata loading failed, using defaults', { error: String(error) });
      logger.info('Search system ready');
    }
  }
}

```

--------------------------------------------------------------------------------
/scripts/build-index.ts:
--------------------------------------------------------------------------------

```typescript
// Build pipeline step 1: Creates dist/data/index.json (bundle of all docs from submodules)
import fg from "fast-glob";
import fs from "fs/promises";
import path, { join } from "path";
import matter from "gray-matter";

interface DocEntry {
  id: string;              // "/sapui5/<rel-path>", "/cap/<rel-path>", "/openui5-api/<rel-path>", or "/openui5-samples/<rel-path>"
  title: string;
  description: string;
  snippetCount: number;
  relFile: string;         // path relative to sources/…
  type?: "markdown" | "jsdoc" | "sample" | "markdown-section";  // type of documentation
  controlName?: string;    // extracted UI5 control name (e.g., "Wizard", "Button")
  namespace?: string;      // UI5 namespace (e.g., "sap.m", "sap.f")
  keywords?: string[];     // searchable keywords and tags
  properties?: string[];   // control properties for API docs
  events?: string[];       // control events for API docs
  aggregations?: string[]; // control aggregations for API docs
  parentDocument?: string; // for sections, the ID of the parent document
  sectionStartLine?: number; // for sections, the line number where the section starts
  headingLevel?: number;   // for sections, the heading level (2=##, 3=###, 4=####)
}

interface LibraryBundle {
  id: string;              // "/sapui5" | "/cap" | "/openui5-api" | "/openui5-samples"
  name: string;            // "SAPUI5", "CAP", "OpenUI5 API", "OpenUI5 Samples"
  description: string;
  docs: DocEntry[];
}

interface SourceConfig {
  repoName: string;
  absDir: string;
  id: string;
  name: string;
  description: string;
  filePattern: string;
  exclude?: string;
  type: "markdown" | "jsdoc" | "sample";
}

const SOURCES: SourceConfig[] = [
  {
    repoName: "sapui5-docs",
    absDir: join("sources", "sapui5-docs", "docs"),
    id: "/sapui5",
    name: "SAPUI5",
    description: "Official SAPUI5 Markdown documentation",
    filePattern: "**/*.md",
    type: "markdown" as const
  },
  {
    repoName: "cap-docs",
    absDir: join("sources", "cap-docs"),
    id: "/cap",
    name: "SAP Cloud Application Programming Model (CAP)",
    description: "CAP (Capire) reference & guides",
    filePattern: "**/*.md",
    type: "markdown" as const
  },
  {
    repoName: "openui5",
    absDir: join("sources", "openui5", "src"),
    id: "/openui5-api",
    name: "OpenUI5 API",
    description: "OpenUI5 Control API documentation and JSDoc",
    filePattern: "**/src/**/*.js",
    exclude: "**/test/**/*",
    type: "jsdoc" as const
  },
  {
    repoName: "openui5",
    absDir: join("sources", "openui5", "src"),
    id: "/openui5-samples",
    name: "OpenUI5 Samples", 
    description: "OpenUI5 demokit sample applications and code examples",
    filePattern: "**/demokit/sample/**/*.{js,xml,json,html}",
    type: "sample" as const
  },
  {
    repoName: "wdi5",
    absDir: join("sources", "wdi5", "docs"),
    id: "/wdi5",
    name: "wdi5",
    description: "wdi5 end-to-end test framework documentation",
    filePattern: "**/*.md",
    type: "markdown" as const
  },
  {
    repoName: "ui5-tooling",
    absDir: join("sources", "ui5-tooling", "docs"),
    id: "/ui5-tooling",
    name: "UI5 Tooling ",
    description: "UI5 Tooling documentation",
    filePattern: "**/*.md",
    type: "markdown" as const
  },
  {
    repoName: "cloud-mta-build-tool",
    absDir: join("sources", "cloud-mta-build-tool", "docs", "docs"),
    id: "/cloud-mta-build-tool",
    name: "Cloud MTA Build Tool",
    description: "Cloud MTA Build Tool documentation",
    filePattern: "**/*.md",
    type: "markdown" as const
  },
  {
    repoName: "ui5-webcomponents",
    absDir: join("sources", "ui5-webcomponents", "docs"),
    id: "/ui5-webcomponents",
    name: "UI5 Web Components",
    description: "UI5 Web Components documentation",
    filePattern: "**/*.md",
    type: "markdown" as const
  },
  {
    repoName: "cloud-sdk",
    absDir: join("sources", "cloud-sdk", "docs-js"),
    id: "/cloud-sdk-js",
    name: "Cloud SDK (JavaScript)",
    description: "Cloud SDK (JavaScript) documentation",
    filePattern: "**/*.mdx",
    type: "markdown" as const
  },
  {
    repoName: "cloud-sdk",
    absDir: join("sources", "cloud-sdk", "docs-java"),
    id: "/cloud-sdk-java",
    name: "Cloud SDK (Java)",
    description: "Cloud SDK (Java) documentation",
    filePattern: "**/*.mdx",
    type: "markdown" as const
  },
  {
    repoName: "cloud-sdk-ai",
    absDir: join("sources", "cloud-sdk-ai", "docs-js"),
    id: "/cloud-sdk-ai-js",
    name: "Cloud SDK AI (JavaScript)",
    description: "Cloud SDK AI (JavaScript) documentation",
    filePattern: "**/*.mdx",
    type: "markdown" as const
  },
  {
    repoName: "cloud-sdk-ai",
    absDir: join("sources", "cloud-sdk-ai", "docs-java"),
    id: "/cloud-sdk-ai-java",
    name: "Cloud SDK AI (Java)",
    description: "Cloud SDK AI (Java) documentation",
    filePattern: "**/*.mdx",
    type: "markdown" as const
  },
  {
    repoName: "ui5-typescript",
    absDir: join("sources", "ui5-typescript"),
    id: "/ui5-typescript",
    name: "UI5 TypeScript",
    description: "Official entry point to anything TypeScript related for UI5",
    filePattern: "*.md",
    type: "markdown" as const
  },
  {
    repoName: "ui5-cc-spreadsheetimporter",
    absDir: join("sources", "ui5-cc-spreadsheetimporter", "docs"),
    id: "/ui5-cc-spreadsheetimporter",
    name: "UI5 CC Spreadsheet Importer",
    description: "UI5 Custom Control for importing spreadsheet data",
    filePattern: "**/*.md",
    type: "markdown" as const
  },
  {
    repoName: "abap-cheat-sheets",
    absDir: join("sources", "abap-cheat-sheets"),
    id: "/abap-cheat-sheets",
    name: "ABAP Cheat Sheets",
    description: "Comprehensive ABAP syntax examples and cheat sheets",
    filePattern: "*.md",
    type: "markdown" as const
  },
  {
    repoName: "sap-styleguides",
    absDir: join("sources", "sap-styleguides"),
    id: "/sap-styleguides",
    name: "SAP Style Guides",
    description: "SAP coding style guides and best practices including Clean ABAP",
    filePattern: "**/*.md",
    type: "markdown" as const
  },
  {
    repoName: "dsag-abap-leitfaden",
    absDir: join("sources", "dsag-abap-leitfaden", "docs"),
    id: "/dsag-abap-leitfaden",
    name: "DSAG ABAP Leitfaden",
    description: "German ABAP guidelines and best practices by DSAG",
    filePattern: "**/*.md",
    type: "markdown" as const
  },
  {
    repoName: "abap-fiori-showcase",
    absDir: join("sources", "abap-fiori-showcase"),
    id: "/abap-fiori-showcase",
    name: "ABAP Platform Fiori Feature Showcase",
    description: "Annotation-driven SAP Fiori Elements features for OData V4 using ABAP RAP",
    filePattern: "*.md",
    type: "markdown" as const
  },
  {
    repoName: "cap-fiori-showcase", 
    absDir: join("sources", "cap-fiori-showcase"),
    id: "/cap-fiori-showcase",
    name: "CAP Fiori Elements Feature Showcase",
    description: "SAP Fiori Elements features and annotations showcase using CAP",
    filePattern: "*.md",
    type: "markdown" as const
  },
  {
    repoName: "abap-docs",
    absDir: join("sources", "abap-docs", "docs", "7.58", "md"),
    id: "/abap-docs-758",
    name: "ABAP Keyword Documentation (7.58)",
    description: "Official ABAP language reference and syntax documentation (version 7.58) - individual files optimized for LLM consumption",
    filePattern: "*.md",
    type: "markdown" as const
  },
  {
    repoName: "abap-docs",
    absDir: join("sources", "abap-docs", "docs", "7.57", "md"),
    id: "/abap-docs-757",
    name: "ABAP Keyword Documentation (7.57)",
    description: "Official ABAP language reference and syntax documentation (version 7.57) - individual files optimized for LLM consumption",
    filePattern: "*.md",
    type: "markdown" as const
  },
  {
    repoName: "abap-docs",
    absDir: join("sources", "abap-docs", "docs", "7.56", "md"),
    id: "/abap-docs-756",
    name: "ABAP Keyword Documentation (7.56)",
    description: "Official ABAP language reference and syntax documentation (version 7.56) - individual files optimized for LLM consumption",
    filePattern: "*.md",
    type: "markdown" as const
  },
  {
    repoName: "abap-docs",
    absDir: join("sources", "abap-docs", "docs", "7.55", "md"),
    id: "/abap-docs-755",
    name: "ABAP Keyword Documentation (7.55)",
    description: "Official ABAP language reference and syntax documentation (version 7.55) - individual files optimized for LLM consumption",
    filePattern: "*.md",
    type: "markdown" as const
  },
  {
    repoName: "abap-docs",
    absDir: join("sources", "abap-docs", "docs", "7.54", "md"),
    id: "/abap-docs-754",
    name: "ABAP Keyword Documentation (7.54)",
    description: "Official ABAP language reference and syntax documentation (version 7.54) - individual files optimized for LLM consumption",
    filePattern: "*.md",
    type: "markdown" as const
  },
  {
    repoName: "abap-docs",
    absDir: join("sources", "abap-docs", "docs", "7.53", "md"),
    id: "/abap-docs-753",
    name: "ABAP Keyword Documentation (7.53)",
    description: "Official ABAP language reference and syntax documentation (version 7.53) - individual files optimized for LLM consumption",
    filePattern: "*.md",
    type: "markdown" as const
  },
  {
    repoName: "abap-docs",
    absDir: join("sources", "abap-docs", "docs", "7.52", "md"),
    id: "/abap-docs-752",
    name: "ABAP Keyword Documentation (7.52)",
    description: "Official ABAP language reference and syntax documentation (version 7.52) - individual files optimized for LLM consumption",
    filePattern: "*.md",
    type: "markdown" as const
  },
  {
    repoName: "abap-docs",
    absDir: join("sources", "abap-docs", "docs", "latest", "md"),
    id: "/abap-docs-latest",
    name: "ABAP Keyword Documentation (Latest)",
    description: "Official ABAP language reference and syntax documentation (latest version) - individual files optimized for LLM consumption",
    filePattern: "*.md",
    type: "markdown" as const
  }
];

// Extract meaningful content from ABAP documentation files
function extractAbapContent(content: string, filename: string): { title: string; description: string; snippetCount: number } {
  const lines = content.split(/\r?\n/);
  
  // Skip attribution header (first few lines with "📖 Official SAP Documentation")
  let contentStart = 0;
  for (let i = 0; i < lines.length; i++) {
    if (lines[i].includes('📖 Official SAP Documentation') || lines[i].startsWith('> **📖')) {
      // Skip until we find the actual content (after attribution and separators)
      for (let j = i; j < lines.length; j++) {
        if (lines[j].trim() === '' || lines[j].includes('* * *') || lines[j].includes('---')) {
          continue;
        }
        if (!lines[j].startsWith('>')) {
          contentStart = j;
          break;
        }
      }
      break;
    }
  }
  
  // Find the actual title (first non-metadata heading)
  let title = filename.replace('.md', '').replace('aben', '');
  for (let i = contentStart; i < lines.length; i++) {
    const line = lines[i].trim();
    if (line && !line.startsWith('AS ABAP Release') && !line.startsWith('[ABAP -') && !line.startsWith('[![') && !line.includes('Mail Feedback')) {
      if (line.match(/^[A-Z][a-zA-Z\s]+$/)) {
        // Found a proper title (like "Inline Declarations")
        title = line;
        contentStart = i + 1;
        break;
      }
    }
  }
  
  // Extract meaningful description from content
  const contentLines = lines.slice(contentStart);
  const meaningfulLines = [];
  
  for (const line of contentLines) {
    const trimmed = line.trim();
    
    // Skip empty lines, separators, and navigation
    if (!trimmed || trimmed === '---' || trimmed === '* * *' || trimmed.startsWith('[ABAP -') || trimmed.includes('Mail Feedback')) {
      continue;
    }
    
    // Skip metadata lines
    if (trimmed.startsWith('AS ABAP Release') || trimmed.includes('©Copyright')) {
      continue;
    }
    
    // Stop at "Continue" or "Programming Guideline" sections
    if (trimmed.startsWith('Continue') || trimmed.startsWith('Programming Guideline')) {
      break;
    }
    
    meaningfulLines.push(trimmed);
    
    // Stop when we have enough content for a good description
    if (meaningfulLines.join(' ').length > 300) {
      break;
    }
  }
  
  // Build description from meaningful content
  let description = meaningfulLines.join(' ').trim();
  
  // If description is too short, add version info
  if (description.length < 50) {
    const versionMatch = filename.match(/abap-docs-(\d+)/);
    const version = versionMatch ? versionMatch[1] : '7.58';
    description = `${title} - ABAP ${version} language reference`;
  }
  
  // Extract ABAP-specific terms for better searchability
  const abapTerms: string[] = [];
  const descriptionLower = description.toLowerCase();
  
  // Common ABAP statement keywords
  const statements = ['data', 'final', 'field-symbol', 'select', 'loop', 'if', 'try', 'catch', 'class', 'method'];
  statements.forEach(stmt => {
    if (descriptionLower.includes(stmt)) {
      abapTerms.push(stmt);
    }
  });
  
  // Add statement context if found
  if (abapTerms.length > 0) {
    description += ` | Statements: ${abapTerms.join(', ')}`;
  }
  
  // Count code snippets (ABAP typically has fewer but more meaningful ones)
  const snippetCount = (content.match(/```/g)?.length || 0) / 2;
  
  return {
    title,
    description: description.substring(0, 400), // Allow longer descriptions for ABAP
    snippetCount
  };
}

// Extract information from sample files (JS, XML, JSON, HTML)
function extractSampleInfo(content: string, filePath: string) {
  const fileName = path.basename(filePath);
  const fileExt = path.extname(filePath);
  const sampleDir = path.dirname(filePath);
  
  // Extract control name from the path (e.g., "Button", "Wizard", "Table")
  const pathParts = sampleDir.split('/');
  const sampleIndex = pathParts.findIndex(part => part === 'sample');
  const controlName = sampleIndex >= 0 && sampleIndex < pathParts.length - 1 
    ? pathParts[sampleIndex + 1] 
    : path.basename(sampleDir);
  
  let title = `${controlName} Sample - ${fileName}`;
  let description = `Sample implementation of ${controlName} control`;
  let snippetCount = 0;
  
  // Extract specific information based on file type
  if (fileExt === '.js') {
    // JavaScript sample files
    const jsContent = content.toLowerCase();
    
    // Look for common UI5 patterns
    if (jsContent.includes('controller')) {
      title = `${controlName} Sample Controller`;
      description = `Controller implementation for ${controlName} sample`;
    } else if (jsContent.includes('component')) {
      title = `${controlName} Sample Component`;
      description = `Component definition for ${controlName} sample`;
    }
    
    // Count meaningful code patterns
    const codePatterns = [
      /function\s*\(/g,
      /onPress\s*:/g,
      /on[A-Z][a-zA-Z]*\s*:/g,
      /\.attach[A-Z][a-zA-Z]*/g,
      /new\s+sap\./g
    ];
    
    snippetCount = codePatterns.reduce((count, pattern) => {
      return count + (content.match(pattern)?.length || 0);
    }, 0);
    
  } else if (fileExt === '.xml') {
    // XML view files
    title = `${controlName} Sample View`;
    description = `XML view implementation for ${controlName} sample`;
    
    // Count XML controls and bindings
    const xmlPatterns = [
      /<[a-zA-Z][^>]*>/g,
      /\{[^}]+\}/g,  // bindings
      /press=/g,
      /text=/g
    ];
    
    snippetCount = xmlPatterns.reduce((count, pattern) => {
      return count + (content.match(pattern)?.length || 0);
    }, 0);
    
  } else if (fileExt === '.json') {
    // Manifest or model files
    if (fileName.includes('manifest')) {
      title = `${controlName} Sample Manifest`;
      description = `Application manifest for ${controlName} sample`;
    } else {
      title = `${controlName} Sample Data`;
      description = `Sample data model for ${controlName} control`;
    }
    
    try {
      const jsonObj = JSON.parse(content);
      snippetCount = Object.keys(jsonObj).length;
    } catch {
      snippetCount = 1;
    }
    
  } else if (fileExt === '.html') {
    // HTML files
    title = `${controlName} Sample HTML`;
    description = `HTML page for ${controlName} sample`;
    
    const htmlPatterns = [
      /<script[^>]*>/g,
      /<div[^>]*>/g,
      /data-sap-ui-/g
    ];
    
    snippetCount = htmlPatterns.reduce((count, pattern) => {
      return count + (content.match(pattern)?.length || 0);
    }, 0);
  }
  
  // Add library information from path
  const libraryMatch = filePath.match(/src\/([^\/]+)\/test/);
  if (libraryMatch) {
    const library = libraryMatch[1];
    description += ` (${library} library)`;
  }
  
  return {
    title,
    description,
    snippetCount: Math.max(1, snippetCount) // Ensure at least 1
  };
}

// Extract JSDoc information from JavaScript files with enhanced metadata
function extractJSDocInfo(content: string, fileName: string) {
  const lines = content.split(/\r?\n/);
  
  // Try to find the main class/control definition
  const classMatch = content.match(/\.extend\s*\(\s*["']([^"']+)["']/);
  const fullControlName = classMatch ? classMatch[1] : path.basename(fileName, ".js");
  
  // Extract namespace and control name
  const namespaceMatch = fullControlName.match(/^(sap\.[^.]+)\.(.*)/);
  const namespace = namespaceMatch ? namespaceMatch[1] : '';
  const controlName = namespaceMatch ? namespaceMatch[2] : fullControlName;
  
  // Extract main class JSDoc comment
  const jsdocMatch = content.match(/\/\*\*\s*([\s\S]*?)\*\//);
  let description = "";
  
  if (jsdocMatch) {
    // Clean up JSDoc comment and extract description
    const jsdocContent = jsdocMatch[1]
      .split('\n')
      .map(line => line.replace(/^\s*\*\s?/, ''))
      .join('\n')
      .trim();
    
    // Extract the main description (everything before @tags)
    const firstAtIndex = jsdocContent.indexOf('@');
    description = firstAtIndex > -1 
      ? jsdocContent.substring(0, firstAtIndex).trim()
      : jsdocContent;
    
    // Clean up common JSDoc patterns
    description = description
      .replace(/^\s*Constructor for a new.*$/m, '')
      .replace(/^\s*@param.*$/gm, '')
      .replace(/^\s*@.*$/gm, '')
      .replace(/\n\s*\n/g, '\n')
      .trim();
  }
  
  // Extract properties, events, aggregations with better parsing
  const properties: string[] = [];
  const events: string[] = [];
  const aggregations: string[] = [];
  const keywords: string[] = [];
  
  // Extract properties
  const propertiesSection = content.match(/properties\s*:\s*\{([\s\S]*?)\n\s*\}/);
  if (propertiesSection) {
    const propMatches = propertiesSection[1].matchAll(/(\w+)\s*:\s*\{/g);
    for (const match of propMatches) {
      properties.push(match[1]);
    }
  }
  
  // Extract events  
  const eventsSection = content.match(/events\s*:\s*\{([\s\S]*?)\n\s*\}/);
  if (eventsSection) {
    const eventMatches = eventsSection[1].matchAll(/(\w+)\s*:\s*\{/g);
    for (const match of eventMatches) {
      events.push(match[1]);
    }
  }
  
  // Extract aggregations
  const aggregationsSection = content.match(/aggregations\s*:\s*\{([\s\S]*?)\n\s*\}/);
  if (aggregationsSection) {
    const aggMatches = aggregationsSection[1].matchAll(/(\w+)\s*:\s*\{/g);
    for (const match of aggMatches) {
      aggregations.push(match[1]);
    }
  }
  
  // Generate keywords based on control name and content
  keywords.push(controlName.toLowerCase());
  if (namespace) keywords.push(namespace);
  if (fullControlName !== controlName) keywords.push(fullControlName);
  
  // Add common UI5 control keywords based on control name
  const controlLower = controlName.toLowerCase();
  if (controlLower.includes('wizard')) keywords.push('wizard', 'step', 'multi-step', 'process');
  if (controlLower.includes('button')) keywords.push('button', 'click', 'press', 'action');
  if (controlLower.includes('table')) keywords.push('table', 'grid', 'data', 'row', 'column');
  if (controlLower.includes('dialog')) keywords.push('dialog', 'popup', 'modal', 'overlay');
  if (controlLower.includes('input')) keywords.push('input', 'field', 'text', 'form');
  if (controlLower.includes('list')) keywords.push('list', 'item', 'collection');
  if (controlLower.includes('panel')) keywords.push('panel', 'container', 'layout');
  if (controlLower.includes('page')) keywords.push('page', 'navigation', 'view');
  
  // Add property/event-based keywords
  if (properties.includes('text')) keywords.push('text');
  if (properties.includes('value')) keywords.push('value');
  if (events.includes('press')) keywords.push('press', 'click');
  if (events.includes('change')) keywords.push('change', 'update');
  
  // Count code blocks and property definitions
  const codeBlockCount = (content.match(/```/g)?.length || 0) / 2;
  const propertyCount = properties.length + events.length + aggregations.length;
  
  return {
    title: fullControlName,
    description: description || `OpenUI5 control: ${fullControlName}`,
    snippetCount: Math.max(1, codeBlockCount + Math.floor(propertyCount / 3)),
    controlName,
    namespace,
    keywords: [...new Set(keywords)],
    properties,
    events,
    aggregations
  };
}

function extractMarkdownSections(content: string, lines: string[], src: any, relFile: string, docs: DocEntry[]) {
  const sections: { title: string; content: string; startLine: number; level: number }[] = [];
  let currentSection: { title: string; content: string; startLine: number; level: number } | null = null;
  
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    
    // Check for headings (##, ###, ####)
    let headingLevel = 0;
    let headingText = '';
    
    if (line.startsWith('#### ')) {
      headingLevel = 4;
      headingText = line.slice(5).trim();
    } else if (line.startsWith('### ')) {
      headingLevel = 3;
      headingText = line.slice(4).trim();
    } else if (line.startsWith('## ')) {
      headingLevel = 2;
      headingText = line.slice(3).trim();
    }
    
    if (headingLevel > 0) {
      // Save previous section if it exists
      if (currentSection) {
        sections.push(currentSection);
      }
      
      // Start new section
      currentSection = {
        title: headingText,
        content: '',
        startLine: i,
        level: headingLevel
      };
    } else if (currentSection) {
      // Add content to current section
      currentSection.content += line + '\n';
    }
  }
  
  // Add the last section
  if (currentSection) {
    sections.push(currentSection);
  }
  
  // Create separate docs entries for meaningful sections
  for (const section of sections) {
    // Skip very short sections or those with placeholder titles
    if (section.content.trim().length < 100 || section.title.length < 3) {
      continue;
    }
    
    // Generate description from section content, including code blocks for better searchability
    const contentLines = section.content.split('\n').filter(l => l.trim() && !l.startsWith('#'));
    
    // Extract code blocks content for technical terms
    const codeBlocks = section.content.match(/```[\s\S]*?```/g) || [];
    const codeContent = codeBlocks
      .map(block => block.replace(/```[\w]*\n?/g, '').replace(/```/g, ''))
      .join(' ')
      .replace(/\s+/g, ' ')
      .trim();
    
    // Combine description with code content for better indexing
    let description = contentLines.slice(0, 3).join(' ').trim() || section.title;
    
    // Include important technical terms from code blocks (like annotation qualifiers)
    if (codeContent) {
      // Extract meaningful technical terms (identifiers, annotation qualifiers, etc.)
      const technicalTerms = (codeContent.match(/[@#]?\w+(?:\.\w+)*(?:#\w+)?/g) || [])
        .filter((term: string) => term.length > 3 && !['true', 'false', 'null', 'undefined', 'function', 'return'].includes(term.toLowerCase()))
        .slice(0, 10); // Limit to prevent bloating
      
      if (technicalTerms.length > 0) {
        description += ' ' + technicalTerms.join(' ');
      }
    }
    
    // Count code snippets in this section
    const snippetCount = (section.content.match(/```/g)?.length || 0) / 2;
    
    // Create section entry
    const sectionId = `${src.id}/${relFile.replace(/\.md$/, "")}#${section.title.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`;
    
    docs.push({
      id: sectionId,
      title: section.title,
      description: description.substring(0, 300) + (description.length > 300 ? '...' : ''),
      snippetCount,
      relFile,
      type: 'markdown-section' as any,
      parentDocument: `${src.id}/${relFile.replace(/\.md$/, "")}`,
      sectionStartLine: section.startLine,
      headingLevel: section.level
    });
  }
}

async function main() {
  await fs.mkdir("dist/data", { recursive: true });
  const all: Record<string, LibraryBundle> = {};

  for (const src of SOURCES) {
    const patterns = [src.filePattern];
    if (src.exclude) {
      patterns.push(`!${src.exclude}`);
    }
    const files = await fg(patterns, { cwd: src.absDir, absolute: true });

    const docs: DocEntry[] = [];

    for (const absPath of files) {
      const rel = path.relative(src.absDir, absPath).replace(/\\/g, "/");
      const raw = await fs.readFile(absPath, "utf8");


      let title: string;
      let description: string;
      let snippetCount: number;
      let id: string;

      if (src.type === "markdown") {
        // Handle markdown files with error handling for malformed frontmatter
        let frontmatter, content;
        try {
          const parsed = matter(raw);
          frontmatter = parsed.data;
          content = parsed.content;
        } catch (yamlError: any) {
          console.warn(`YAML parsing failed for ${rel}, using fallback:`, yamlError?.message || yamlError);
          // Fallback: extract content without frontmatter
          const lines = raw.split('\n');
          const contentStartIndex = lines.findIndex((line, index) => line.trim() === '---' && index > 0) + 1;
          frontmatter = {};
          content = contentStartIndex > 0 ? lines.slice(contentStartIndex).join('\n') : raw;
        }
        const lines = content.split(/\r?\n/);

        // Use frontmatter for title and description (works for ABAP and other sources)
        title = frontmatter?.title || 
                lines.find((l) => l.startsWith("# "))?.slice(2).trim() ||
                path.basename(rel, ".md");
        
        // Enhanced description from frontmatter or content
        if (frontmatter?.description) {
          description = frontmatter.description;
        } else if (frontmatter?.synopsis && content.includes("{{ $frontmatter.synopsis }}")) {
          description = frontmatter.synopsis;
        } else {
          // Fallback to content extraction
          const rawDescription = lines.find((l) => l.trim() && !l.startsWith("#"))?.trim() || "";
          description = rawDescription;
        }
        
        snippetCount = (content.match(/```/g)?.length || 0) / 2;
        
        id = `${src.id}/${rel.replace(/\.md$/, "")}`;
        
        // Extract individual sections as separate entries for all markdown docs
        if (content.includes('##')) {
          extractMarkdownSections(content, lines, src, rel, docs);
        }
      } else if (src.type === "jsdoc") {
        // Handle JavaScript files with JSDoc
        const jsDocInfo = extractJSDocInfo(raw, path.basename(absPath));
        title = jsDocInfo.title;
        description = jsDocInfo.description;
        snippetCount = jsDocInfo.snippetCount;
        id = `${src.id}/${rel.replace(/\.js$/, "")}`;
        
        // Skip files that don't look like UI5 controls
        if (!raw.includes('.extend') || !raw.includes('metadata')) {
          continue;
        }
        
        docs.push({ 
          id, 
          title, 
          description, 
          snippetCount, 
          relFile: rel,
          type: src.type,
          controlName: jsDocInfo.controlName,
          namespace: jsDocInfo.namespace,
          keywords: jsDocInfo.keywords,
          properties: jsDocInfo.properties,
          events: jsDocInfo.events,
          aggregations: jsDocInfo.aggregations
        });
        
      } else if (src.type === "sample") {
        // Handle sample files (JS, XML, JSON, HTML)
        const sampleInfo = extractSampleInfo(raw, rel);
        title = sampleInfo.title;
        description = sampleInfo.description;
        snippetCount = sampleInfo.snippetCount;
        id = `${src.id}/${rel.replace(/\.(js|xml|json|html)$/, "")}`;
        
        // Skip empty files or non-meaningful samples
        if (raw.trim().length < 50) {
          continue;
        }
        
        // Extract control name from sample path for better searchability
        const pathParts = rel.split('/');
        const sampleIndex = pathParts.findIndex(part => part === 'sample');
        const controlName = sampleIndex >= 0 && sampleIndex < pathParts.length - 1 
          ? pathParts[sampleIndex + 1] 
          : path.basename(path.dirname(rel));
        
        // Generate sample keywords
        const keywords = [controlName.toLowerCase(), 'sample', 'example'];
        if (rel.includes('.xml')) keywords.push('view', 'xml');
        if (rel.includes('.js')) keywords.push('controller', 'javascript');
        if (rel.includes('.json')) keywords.push('model', 'data', 'configuration');
        if (rel.includes('manifest')) keywords.push('manifest', 'app');
        
        docs.push({ 
          id, 
          title, 
          description, 
          snippetCount, 
          relFile: rel,
          type: src.type,
          controlName,
          keywords: [...new Set(keywords)]
        });
        
      } else {
        continue; // Skip unknown file types
      }

      // For markdown files, still use the basic structure
      if (src.type === "markdown") {

        docs.push({ 
          id, 
          title, 
          description, 
          snippetCount, 
          relFile: rel,
          type: src.type
        });
      }
    }

    const bundle: LibraryBundle = {
      id: src.id,
      name: src.name,
      description: src.description,
      docs
    };

    all[src.id] = bundle;
    await fs.writeFile(
      path.join("dist", "data", `data${src.id}.json`.replace(/\//g, "_")),
      JSON.stringify(bundle, null, 2)
    );
  }

  await fs.writeFile("dist/data/index.json", JSON.stringify(all, null, 2));
  console.log("✅  Index built with", Object.keys(all).length, "libraries.");
}

main(); 
```
Page 3/4FirstPrevNextLast