OverviewFilesCommitsIssuesDocsReleasesPeers

holepunchto-kb / architect-agent.js

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

/**
 * Architect Sub-Agent
 *
 * Takes a list of repositories and designs system architecture,
 * identifying patterns, dependencies, and integration points.
 *
 * Usage:
 *   node architect-agent.js hypercore hyperbee hyperswarm
 *   node architect-agent.js --use-case distributed-database
 */

const fs = require('fs');

// Load semantic mappings
const patternMap = JSON.parse(fs.readFileSync('pattern-map.json', 'utf8'));
const dependencyGraph = JSON.parse(fs.readFileSync('dependency-graph.json', 'utf8'));
const useCaseMap = JSON.parse(fs.readFileSync('use-case-map.json', 'utf8'));
const stabilityIndex = JSON.parse(fs.readFileSync('stability-index.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;
}

// Load API extracts for deeply analyzed repos
function loadAPIExtract(repoName) {
  const path = `skills/${repoName}/api-extract.json`;
  if (fs.existsSync(path)) {
    try {
      return JSON.parse(fs.readFileSync(path, 'utf8'));
    } catch (e) {
      return null;
    }
  }
  return null;
}

/**
 * Analyze architecture for a set of repos
 */
function analyzeArchitecture(repoNames) {
  const architecture = {
    repos: [],
    patterns: [],
    dependencies: {},
    layers: {},
    integrationPoints: [],
    recommendations: []
  };

  // Analyze each repo
  for (const repoName of repoNames) {
    const repoInfo = analyzeRepoForArchitecture(repoName);
    if (repoInfo) {
      architecture.repos.push(repoInfo);
    }
  }

  // Identify patterns
  architecture.patterns = identifyPatterns(repoNames);

  // Analyze dependencies
  architecture.dependencies = analyzeDependencies(repoNames);

  // Organize into layers
  architecture.layers = organizeLayers(repoNames, architecture.dependencies);

  // Identify integration points
  architecture.integrationPoints = identifyIntegrationPoints(repoNames);

  // Generate recommendations
  architecture.recommendations = generateRecommendations(architecture);

  return architecture;
}

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

  const patterns = patternMap.repoPatterns[repoName] || { implements: [], uses: [] };
  const deps = dependencyGraph[repoName] || { dependencies: [], reverseDependencies: [] };
  const api = loadAPIExtract(repoName);

  return {
    name: repoName,
    description: info.description,
    patternsImplemented: patterns.implements || [],
    patternsUsed: patterns.uses || [],
    dependencies: deps.dependencies || [],
    dependents: deps.reverseDependencies || [],
    hasAPIData: !!api,
    apiClasses: api && api.api ? (api.api.classes || []) : [],
    apiFunctions: api && api.api ? (api.api.functions || []) : []
  };
}

/**
 * Identify patterns across selected repos
 */
function identifyPatterns(repoNames) {
  const patternsUsed = new Set();

  for (const repoName of repoNames) {
    const repoPatterns = patternMap.repoPatterns[repoName];
    if (repoPatterns) {
      for (const pattern of repoPatterns.implements || []) {
        patternsUsed.add(pattern);
      }
      for (const pattern of repoPatterns.uses || []) {
        patternsUsed.add(pattern);
      }
    }
  }

  return Array.from(patternsUsed).map(patternName => {
    const pattern = patternMap.patterns[patternName];
    const implementedBy = repoNames.filter(repo =>
      patternMap.repoPatterns[repo] &&
      (patternMap.repoPatterns[repo].implements || []).includes(patternName)
    );
    const usedBy = repoNames.filter(repo =>
      patternMap.repoPatterns[repo] &&
      (patternMap.repoPatterns[repo].uses || []).includes(patternName)
    );

    return {
      name: patternName,
      description: pattern ? pattern.description : 'Unknown pattern',
      implementedBy,
      usedBy
    };
  });
}

/**
 * Analyze dependencies between selected repos
 */
function analyzeDependencies(repoNames) {
  const deps = {};
  const repoSet = new Set(repoNames);

  for (const repoName of repoNames) {
    const repoDeps = dependencyGraph[repoName];
    if (repoDeps) {
      // Only include dependencies that are in our selected repos
      const internalDeps = (repoDeps.dependencies || []).filter(dep => repoSet.has(dep));
      if (internalDeps.length > 0) {
        deps[repoName] = internalDeps;
      }
    }
  }

  return deps;
}

/**
 * Organize repos into architectural layers
 */
function organizeLayers(repoNames, dependencies) {
  const layers = {
    foundation: [],
    core: [],
    integration: [],
    application: []
  };

  for (const repoName of repoNames) {
    const deps = dependencies[repoName] || [];
    const stability = stabilityIndex[repoName];

    // Foundation layer: no dependencies on other selected repos, stable
    if (deps.length === 0 && stability && stability.stability === 'stable') {
      layers.foundation.push(repoName);
    }
    // Core layer: few dependencies, implements key patterns
    else if (deps.length <= 2) {
      const patterns = patternMap.repoPatterns[repoName];
      if (patterns && patterns.implements && patterns.implements.length > 0) {
        layers.core.push(repoName);
      } else {
        layers.integration.push(repoName);
      }
    }
    // Integration/Application layer: many dependencies
    else {
      layers.application.push(repoName);
    }
  }

  // Move any remaining repos to integration
  for (const repoName of repoNames) {
    const inAnyLayer = Object.values(layers).some(layer => layer.includes(repoName));
    if (!inAnyLayer) {
      layers.integration.push(repoName);
    }
  }

  return layers;
}

/**
 * Identify integration points between repos
 */
function identifyIntegrationPoints(repoNames) {
  const points = [];
  const repoSet = new Set(repoNames);

  for (const repoName of repoNames) {
    const deps = dependencyGraph[repoName];
    if (deps && deps.dependencies) {
      for (const dep of deps.dependencies) {
        if (repoSet.has(dep)) {
          points.push({
            from: repoName,
            to: dep,
            type: 'dependency'
          });
        }
      }
    }

    // Check for pattern-based integration
    const patterns = patternMap.repoPatterns[repoName];
    if (patterns && patterns.uses) {
      for (const pattern of patterns.uses) {
        const patternDef = patternMap.patterns[pattern];
        if (patternDef && patternDef.implementedBy) {
          for (const impl of patternDef.implementedBy) {
            if (repoSet.has(impl) && impl !== repoName) {
              points.push({
                from: repoName,
                to: impl,
                type: 'pattern',
                pattern: pattern
              });
            }
          }
        }
      }
    }
  }

  return points;
}

/**
 * Generate architecture recommendations
 */
function generateRecommendations(architecture) {
  const recommendations = [];

  // Check for missing core dependencies
  for (const repo of architecture.repos) {
    for (const dep of repo.dependencies) {
      if (!architecture.repos.find(r => r.name === dep)) {
        recommendations.push({
          type: 'missing-dependency',
          severity: 'warning',
          message: `Consider adding "${dep}" - required by ${repo.name}`
        });
      }
    }
  }

  // Check for pattern coverage
  const hasReplication = architecture.patterns.some(p => p.name === 'replication');
  const hasPeerDiscovery = architecture.patterns.some(p => p.name === 'peer-discovery');

  if (!hasReplication && architecture.repos.length > 1) {
    recommendations.push({
      type: 'pattern-suggestion',
      severity: 'info',
      message: 'Consider adding replication pattern for data sync between peers'
    });
  }

  if (!hasPeerDiscovery) {
    recommendations.push({
      type: 'pattern-suggestion',
      severity: 'info',
      message: 'Consider adding peer-discovery (hyperswarm/hyperdht) for P2P networking'
    });
  }

  // Check layer distribution
  if (architecture.layers.foundation.length === 0) {
    recommendations.push({
      type: 'architecture',
      severity: 'warning',
      message: 'No foundation layer repos - consider adding stable core primitives'
    });
  }

  return recommendations;
}

/**
 * Format architecture as text
 */
function formatArchitecture(arch) {
  let output = '';

  output += '═══════════════════════════════════════════════════════\n';
  output += 'ARCHITECT AGENT - SYSTEM ARCHITECTURE\n';
  output += '═══════════════════════════════════════════════════════\n\n';

  output += `Analyzing ${arch.repos.length} repositories\n\n`;

  // Architectural layers
  output += '───────────────────────────────────────────────────────\n';
  output += 'ARCHITECTURAL LAYERS\n';
  output += '───────────────────────────────────────────────────────\n\n';

  output += '┌─ Foundation Layer (Stable, no internal deps)\n';
  for (const repo of arch.layers.foundation) {
    output += `│  • ${repo}\n`;
  }
  if (arch.layers.foundation.length === 0) output += '│  (none)\n';

  output += '├─ Core Layer (Key patterns, few deps)\n';
  for (const repo of arch.layers.core) {
    output += `│  • ${repo}\n`;
  }
  if (arch.layers.core.length === 0) output += '│  (none)\n';

  output += '├─ Integration Layer\n';
  for (const repo of arch.layers.integration) {
    output += `│  • ${repo}\n`;
  }
  if (arch.layers.integration.length === 0) output += '│  (none)\n';

  output += '└─ Application Layer (High-level, many deps)\n';
  for (const repo of arch.layers.application) {
    output += `   • ${repo}\n`;
  }
  if (arch.layers.application.length === 0) output += '   (none)\n';

  // Patterns
  output += '\n───────────────────────────────────────────────────────\n';
  output += 'P2P PATTERNS IN USE\n';
  output += '───────────────────────────────────────────────────────\n\n';

  for (const pattern of arch.patterns) {
    output += `📐 ${pattern.name}\n`;
    output += `   ${pattern.description}\n`;
    if (pattern.implementedBy.length > 0) {
      output += `   Implemented by: ${pattern.implementedBy.join(', ')}\n`;
    }
    if (pattern.usedBy.length > 0) {
      output += `   Used by: ${pattern.usedBy.join(', ')}\n`;
    }
    output += '\n';
  }

  // Dependencies
  if (Object.keys(arch.dependencies).length > 0) {
    output += '───────────────────────────────────────────────────────\n';
    output += 'DEPENDENCY GRAPH\n';
    output += '───────────────────────────────────────────────────────\n\n';

    for (const [repo, deps] of Object.entries(arch.dependencies)) {
      output += `${repo}\n`;
      for (const dep of deps) {
        output += `  └─> ${dep}\n`;
      }
    }
    output += '\n';
  }

  // Integration points
  if (arch.integrationPoints.length > 0) {
    output += '───────────────────────────────────────────────────────\n';
    output += 'INTEGRATION POINTS\n';
    output += '───────────────────────────────────────────────────────\n\n';

    for (const point of arch.integrationPoints.slice(0, 10)) {
      if (point.type === 'dependency') {
        output += `🔗 ${point.from} → ${point.to} (direct dependency)\n`;
      } else if (point.type === 'pattern') {
        output += `🔗 ${point.from} → ${point.to} (via ${point.pattern} pattern)\n`;
      }
    }

    if (arch.integrationPoints.length > 10) {
      output += `\n... and ${arch.integrationPoints.length - 10} more\n`;
    }
    output += '\n';
  }

  // Recommendations
  if (arch.recommendations.length > 0) {
    output += '───────────────────────────────────────────────────────\n';
    output += 'RECOMMENDATIONS\n';
    output += '───────────────────────────────────────────────────────\n\n';

    for (const rec of arch.recommendations) {
      const icon = rec.severity === 'warning' ? '⚠️ ' : '💡';
      output += `${icon} ${rec.message}\n`;
    }
    output += '\n';
  }

  // Repository details
  output += '───────────────────────────────────────────────────────\n';
  output += 'REPOSITORY DETAILS\n';
  output += '───────────────────────────────────────────────────────\n\n';

  for (const repo of arch.repos) {
    output += `📦 ${repo.name}\n`;
    output += `   ${repo.description}\n`;
    if (repo.patternsImplemented.length > 0) {
      output += `   Implements: ${repo.patternsImplemented.join(', ')}\n`;
    }
    if (repo.patternsUsed.length > 0) {
      output += `   Uses: ${repo.patternsUsed.join(', ')}\n`;
    }
    if (repo.hasAPIData) {
      output += `   API: ${repo.apiClasses.length} classes, ${repo.apiFunctions.length} functions\n`;
      output += `   Documentation: skills/${repo.name}/skills.md\n`;
    }
    output += '\n';
  }

  return output;
}

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

  if (args.length === 0 || args[0] === '--help') {
    console.log('Architect Sub-Agent - Design system architecture\n');
    console.log('Usage:');
    console.log('  node architect-agent.js <repo1> <repo2> ...');
    console.log('  node architect-agent.js --use-case <use-case-name>\n');
    console.log('Examples:');
    console.log('  node architect-agent.js hypercore hyperbee hyperswarm');
    console.log('  node architect-agent.js --use-case distributed-database');
    process.exit(0);
  }

  let repoNames = args;

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

    const useCase = useCaseMap.useCases[useCaseName];
    if (!useCase) {
      console.error(`Error: Use case "${useCaseName}" not found`);
      process.exit(1);
    }

    repoNames = [...useCase.requiredRepos, ...useCase.optionalRepos];
    console.log(`Analyzing architecture for use case: ${useCaseName}\n`);
  }

  const architecture = analyzeArchitecture(repoNames);
  console.log(formatArchitecture(architecture));
}

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

module.exports = { analyzeArchitecture };