Skip to main content
Glama

get_feature_hub

Navigate and manage feature hubs in codebases using Obsidian-style wikilinks. List all hubs, display hub contents with linked file skeletons, or identify orphaned files not connected to any hub for better codebase organization.

Instructions

Obsidian-style feature hub navigator. Hub files are .md files containing [[path/to/file]] wikilinks that act as a Map of Content. Modes: (1) No args = list all hubs, (2) hub_path or feature_name = show hub with bundled skeletons of all linked files, (3) show_orphans = find files not linked to any hub. Prevents orphaned code and enables graph-based codebase navigation.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
hub_pathNoPath to a specific hub .md file (relative to root).
feature_nameNoFeature name to search for. Finds matching hub file automatically.
show_orphansNoIf true, lists all source files not linked to any feature hub.

Implementation Reference

  • The getFeatureHub function is the primary handler that retrieves, resolves, and displays documentation/skeleton code for a feature hub.
    export async function getFeatureHub(options: FeatureHubOptions): Promise<string> {
      const { rootDir, showOrphans } = options;
      const out: string[] = [];
    
      if (!options.hubPath && !options.featureName && !showOrphans) {
        const hubs = await discoverHubs(rootDir);
        if (hubs.length === 0) {
          return "No hub files found. Create a .md file with [[path/to/file]] links to establish a feature hub.";
        }
        out.push(`Feature Hubs (${hubs.length}):`);
        out.push("");
        for (const h of hubs) {
          const info = await parseHubFile(resolve(rootDir, h));
          out.push(`  ${h} | ${info.title} | ${info.links.length} links`);
        }
        return out.join("\n");
      }
    
      if (showOrphans) {
        const entries = await walkDirectory({ rootDir, depthLimit: 10 });
        const filePaths = entries.filter((e) => !e.isDirectory).map((e) => e.relativePath);
        const orphans = await findOrphanedFiles(rootDir, filePaths);
        if (orphans.length === 0) return "No orphaned files. All source files are linked to a hub.";
    
        out.push(`Orphaned Files (${orphans.length}):`);
        out.push("These files are not linked to any feature hub:");
        out.push("");
        for (const o of orphans) out.push(`  ⚠ ${o}`);
        out.push("");
        out.push("Fix: Add [[" + orphans[0] + "]] to the appropriate hub .md file.");
        return out.join("\n");
      }
    
      let hubRelPath = options.hubPath;
      if (!hubRelPath && options.featureName) {
        hubRelPath = (await findHubByName(rootDir, options.featureName)) ?? undefined;
        if (!hubRelPath) {
          return `No hub found for feature "${options.featureName}". Available hubs:\n` +
            (await discoverHubs(rootDir)).map((h) => `  - ${h}`).join("\n") || "  (none)";
        }
      }
    
      if (!hubRelPath) return "Provide hub_path, feature_name, or set show_orphans=true.";
    
      const hubFull = resolve(rootDir, hubRelPath);
      if (!(await fileExists(hubFull))) {
        return `Hub file not found: ${hubRelPath}`;
      }
    
      const hub = await parseHubFile(hubFull);
    
      out.push(`Hub: ${hub.title}`);
      out.push(`Path: ${hubRelPath}`);
      out.push(`Links: ${hub.links.length}`);
      if (hub.crossLinks.length > 0) {
        out.push(`Cross-links: ${hub.crossLinks.map((c) => c.hubName).join(", ")}`);
      }
      out.push("");
      out.push("---");
      out.push("");
    
      const resolved: string[] = [];
      const missing: string[] = [];
    
      for (const link of hub.links) {
        const linkFull = resolve(rootDir, link.target);
        if (await fileExists(linkFull)) {
          resolved.push(link.target);
        } else {
          missing.push(link.target);
        }
      }
    
      for (const filePath of resolved) {
        const ext = extname(filePath);
        const desc = hub.links.find((l) => l.target === filePath)?.description;
    
        if (desc) out.push(`## ${filePath} - ${desc}`);
        else out.push(`## ${filePath}`);
    
        try {
          const skeleton = await getFileSkeleton({ rootDir, filePath });
          out.push(skeleton);
        } catch {
          const content = await readFile(resolve(rootDir, filePath), "utf-8");
          out.push(content.split("\n").slice(0, 20).join("\n"));
        }
        out.push("");
      }
    
      if (missing.length > 0) {
        out.push("---");
        out.push(`Missing Links (${missing.length}):`);
        for (const m of missing) out.push(`  ✗ ${m}`);
      }
    
      return out.join("\n");
    }
  • Input parameters for the getFeatureHub tool.
    export interface FeatureHubOptions {
      rootDir: string;
      hubPath?: string;
      featureName?: string;
      showOrphans?: boolean;
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ForLoopCodes/contextplus'

If you have feedback or need assistance with the MCP directory API, please join our Discord server