OverviewFilesCommitsIssuesDocsReleasesPeers

holepunchto-kb / designer-agent.js

274 lines · javascript ref:
#!/usr/bin/env node

/**
 * Designer Sub-Agent
 *
 * Takes user requirements and recommends appropriate holepunchto repositories.
 *
 * Usage:
 *   node designer-agent.js "I want to build a distributed database"
 *   node designer-agent.js --use-case distributed-database
 *   node designer-agent.js --interactive
 */

const fs = require('fs');

// Load semantic mappings
const semanticCategories = JSON.parse(fs.readFileSync('semantic-categories.json', 'utf8'));
const useCaseMap = JSON.parse(fs.readFileSync('use-case-map.json', 'utf8'));
const stabilityIndex = JSON.parse(fs.readFileSync('stability-index.json', 'utf8'));
const platformSupport = JSON.parse(fs.readFileSync('platform-support.json', 'utf8'));
const repoMapData = JSON.parse(fs.readFileSync('repo_map.json', 'utf8'));
// Support both old array format and new object format with _meta
const repoMapArray = repoMapData.repos || repoMapData;

// Convert repo map to object
const repoMap = {};
for (const repo of repoMapArray) {
  repoMap[repo.name] = repo;
}

/**
 * Match user query to use cases
 */
function matchUseCases(query) {
  const q = query.toLowerCase();
  const matches = [];

  for (const [useCaseName, useCase] of Object.entries(useCaseMap.useCases)) {
    let score = 0;

    // Check if query mentions use case name
    if (q.includes(useCaseName.replace(/-/g, ' '))) {
      score += 10;
    }

    // Check description keywords
    const desc = useCase.description.toLowerCase();
    const queryWords = q.split(/\s+/);
    for (const word of queryWords) {
      if (word.length > 3 && desc.includes(word)) {
        score += 2;
      }
    }

    // Check pattern keywords
    for (const pattern of useCase.patterns) {
      if (q.includes(pattern.replace(/-/g, ' '))) {
        score += 3;
      }
    }

    if (score > 0) {
      matches.push({ useCaseName, useCase, score });
    }
  }

  return matches.sort((a, b) => b.score - a.score);
}

/**
 * Recommend repos for a use case
 */
function recommendRepos(useCaseName) {
  const useCase = useCaseMap.useCases[useCaseName];
  if (!useCase) {
    return null;
  }

  const recommendations = {
    useCase: useCaseName,
    description: useCase.description,
    required: [],
    optional: [],
    patterns: useCase.patterns
  };

  // Analyze required repos
  for (const repoName of useCase.requiredRepos) {
    const repo = analyzeRepo(repoName);
    if (repo) {
      recommendations.required.push(repo);
    }
  }

  // Analyze optional repos
  for (const repoName of useCase.optionalRepos) {
    const repo = analyzeRepo(repoName);
    if (repo) {
      recommendations.optional.push(repo);
    }
  }

  return recommendations;
}

/**
 * Analyze a single repo
 */
function analyzeRepo(repoName) {
  const info = repoMap[repoName];
  if (!info) return null;

  const category = semanticCategories[repoName];
  const stability = stabilityIndex[repoName];
  const platform = platformSupport[repoName];

  return {
    name: repoName,
    description: info.description,
    url: info.url,
    category: category ? category.category : 'Unknown',
    stability: stability ? stability.stability : 'unknown',
    maturity: stability ? stability.maturity : 'unknown',
    recommendedFor: stability ? stability.recommendedFor : 'unknown',
    stars: stability ? stability.stars : 0,
    platforms: platform ? Object.entries(platform.platforms)
      .filter(([_, supported]) => supported)
      .map(([name]) => name) : [],
    confidence: category ? category.confidence : 'low'
  };
}

/**
 * Format recommendations as text
 */
function formatRecommendations(recs) {
  let output = '';

  output += '═══════════════════════════════════════════════════════\n';
  output += 'DESIGNER AGENT RECOMMENDATIONS\n';
  output += '═══════════════════════════════════════════════════════\n\n';

  output += `Use Case: ${recs.useCase}\n`;
  output += `Description: ${recs.description}\n`;
  output += `Patterns: ${recs.patterns.join(', ')}\n\n`;

  output += '───────────────────────────────────────────────────────\n';
  output += 'REQUIRED REPOSITORIES\n';
  output += '───────────────────────────────────────────────────────\n\n';

  for (const repo of recs.required) {
    output += `📦 ${repo.name}\n`;
    output += `   ${repo.description}\n`;
    output += `   Category: ${repo.category}\n`;
    output += `   Stability: ${repo.stability} (${repo.maturity})\n`;
    output += `   Recommended for: ${repo.recommendedFor}\n`;
    output += `   Stars: ${repo.stars}\n`;
    output += `   Platforms: ${repo.platforms.join(', ') || 'unknown'}\n`;
    output += `   URL: ${repo.url}\n\n`;
  }

  if (recs.optional.length > 0) {
    output += '───────────────────────────────────────────────────────\n';
    output += 'OPTIONAL REPOSITORIES (Enhance functionality)\n';
    output += '───────────────────────────────────────────────────────\n\n';

    for (const repo of recs.optional) {
      output += `📦 ${repo.name}\n`;
      output += `   ${repo.description}\n`;
      output += `   Category: ${repo.category}\n`;
      output += `   Stability: ${repo.stability} (${repo.maturity})\n`;
      output += `   Stars: ${repo.stars}\n`;
      output += `   Platforms: ${repo.platforms.join(', ') || 'unknown'}\n\n`;
    }
  }

  output += '───────────────────────────────────────────────────────\n';
  output += 'NEXT STEPS\n';
  output += '───────────────────────────────────────────────────────\n\n';
  output += '1. Install required repositories\n';
  output += '2. Review API documentation in skills/{repo-name}/skills.md\n';
  output += '3. Use Architect Agent for system design:\n';
  output += `   node architect-agent.js ${recs.required.map(r => r.name).join(' ')}\n\n`;

  return output;
}

/**
 * Main function
 */
function main() {
  const args = process.argv.slice(2);

  // Handle different invocation modes
  if (args.length === 0 || args[0] === '--help') {
    console.log('Designer Sub-Agent - Recommend repos for P2P use cases\n');
    console.log('Usage:');
    console.log('  node designer-agent.js "your requirements"');
    console.log('  node designer-agent.js --use-case <use-case-name>');
    console.log('  node designer-agent.js --list-use-cases\n');
    console.log('Examples:');
    console.log('  node designer-agent.js "I want to build a distributed database"');
    console.log('  node designer-agent.js --use-case p2p-file-sharing');
    process.exit(0);
  }

  // List all use cases
  if (args[0] === '--list-use-cases') {
    console.log('Available Use Cases:\n');
    for (const [name, useCase] of Object.entries(useCaseMap.useCases)) {
      console.log(`  ${name}`);
      console.log(`    ${useCase.description}\n`);
    }
    process.exit(0);
  }

  // Direct use case lookup
  if (args[0] === '--use-case') {
    const useCaseName = args[1];
    if (!useCaseName) {
      console.error('Error: Please specify a use case name');
      process.exit(1);
    }

    const recs = recommendRepos(useCaseName);
    if (!recs) {
      console.error(`Error: Use case "${useCaseName}" not found`);
      console.error('Run with --list-use-cases to see available use cases');
      process.exit(1);
    }

    console.log(formatRecommendations(recs));
    process.exit(0);
  }

  // Natural language query
  const query = args.join(' ');
  console.log(`Analyzing query: "${query}"\n`);

  const matches = matchUseCases(query);

  if (matches.length === 0) {
    console.log('No matching use cases found.');
    console.log('Run with --list-use-cases to see available use cases');
    process.exit(0);
  }

  console.log(`Found ${matches.length} matching use case(s):\n`);

  // Show top match
  const topMatch = matches[0];
  console.log(`Best match: ${topMatch.useCaseName} (score: ${topMatch.score})\n`);

  const recs = recommendRepos(topMatch.useCaseName);
  console.log(formatRecommendations(recs));

  // Show other matches if any
  if (matches.length > 1) {
    console.log('Other possible matches:');
    for (let i = 1; i < Math.min(matches.length, 3); i++) {
      console.log(`  • ${matches[i].useCaseName} (score: ${matches[i].score})`);
      console.log(`    ${matches[i].useCase.description}`);
    }
    console.log('\nRun with --use-case <name> to see recommendations for other matches\n');
  }
}

// Run if called directly
if (require.main === module) {
  main();
}

module.exports = { matchUseCases, recommendRepos, analyzeRepo };