[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build a Dependency Graph Analyzer

Build a Dependency Graph Analyzer

Build a dependency graph analyzer with package scanning, vulnerability detection, license compliance, circular dependency detection, and upgrade impact analysis for monorepo management.

Development#typescript#tsconfig#type-checking#type-safety#javascript-migration
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 96/100·All SAFE
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

redis

v1.1.0

Redis is an in-memory data store used as a cache, session store, message broker and counter service. Use when adding a cache layer, storing sessions, rate limiting an API, running pub/sub or Streams with consumer groups, taking a distributed lock, building a leaderboard, or configuring Redis for production (persistence, maxmemory, eviction).

93/100 quality
1.79× impact
SAFE· stale score
View skill
>

postgresql

v1.1.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

100/100 quality
5.15× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
$

The Problem

Yuki leads platform at a 25-person company with a monorepo containing 15 packages and 800 dependencies. npm audit shows 47 vulnerabilities but doesn't tell them which matter (some are in dev-only deps, others in unreachable code paths). Upgrading one package cascades through 8 internal packages — nobody knows the blast radius. They have GPL-licensed packages in their proprietary app (a legal risk they only discovered during due diligence). Circular dependencies between internal packages cause mysterious build failures. They need a dependency graph analyzer: visualize the full tree, prioritize vulnerabilities by actual risk, check license compliance, detect circular deps, and preview upgrade impact.

Step 1: Build the Graph Analyzer

typescript
// src/deps/analyzer.ts — Dependency graph analysis with vulnerability prioritization
import { pool } from "../db";
import { Redis } from "ioredis";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { createHash } from "node:crypto";

const redis = new Redis(process.env.REDIS_URL!);

interface Package {
  name: string;
  version: string;
  type: "production" | "development" | "peer";
  license: string;
  dependencies: string[];    // package names
  dependents: string[];      // packages that depend on this
  depth: number;             // distance from root
  isInternal: boolean;       // monorepo package
}

interface Vulnerability {
  id: string;
  package: string;
  severity: "critical" | "high" | "medium" | "low";
  title: string;
  fixVersion: string | null;
  cwe: string;
  reachable: boolean;        // is this actually reachable in production code?
  affectedPaths: string[][];  // dependency chains leading to this vuln
  riskScore: number;         // 0-100, factoring in severity + reachability + exposure
}

interface LicenseIssue {
  package: string;
  version: string;
  license: string;
  issue: "copyleft" | "unknown" | "restricted";
  requiredBy: string[];
}

interface UpgradeImpact {
  package: string;
  from: string;
  to: string;
  directDependents: string[];
  transitiveDependents: string[];
  breakingChanges: string[];
  vulnerabilitiesFixed: number;
}

// Scan project and build dependency graph
export async function scanProject(rootDir: string): Promise<{
  totalPackages: number;
  graph: Map<string, Package>;
  vulnerabilities: Vulnerability[];
  licenseIssues: LicenseIssue[];
  circularDeps: string[][];
}> {
  const graph = new Map<string, Package>();

  // Parse package.json and lock file
  const pkgJson = JSON.parse(await readFile(join(rootDir, "package.json"), "utf-8"));
  const allDeps = { ...pkgJson.dependencies };
  const devDeps = { ...pkgJson.devDependencies };

  // Build graph from dependencies
  await buildGraph(graph, allDeps, "production", 0);
  await buildGraph(graph, devDeps, "development", 0);

  // Scan for vulnerabilities
  const vulnerabilities = await scanVulnerabilities(graph);

  // Check licenses
  const licenseIssues = checkLicenses(graph);

  // Detect circular dependencies
  const circularDeps = detectCircularDeps(graph);

  // Cache results
  const scanId = createHash("sha256").update(JSON.stringify(Array.from(graph.entries()))).digest("hex").slice(0, 12);
  await redis.setex(`deps:scan:${scanId}`, 86400, JSON.stringify({
    totalPackages: graph.size, vulnerabilities, licenseIssues, circularDeps,
  }));

  return { totalPackages: graph.size, graph, vulnerabilities, licenseIssues, circularDeps };
}

async function buildGraph(
  graph: Map<string, Package>,
  deps: Record<string, string>,
  type: Package["type"],
  depth: number
): Promise<void> {
  for (const [name, version] of Object.entries(deps)) {
    const key = `${name}@${version}`;
    if (graph.has(key)) {
      graph.get(key)!.dependents.push("root");
      continue;
    }

    graph.set(key, {
      name, version: version.replace(/^[^\d]/, ""), type,
      license: "unknown", dependencies: [], dependents: ["root"],
      depth, isInternal: name.startsWith("@internal/"),
    });
  }
}

async function scanVulnerabilities(graph: Map<string, Package>): Promise<Vulnerability[]> {
  const vulns: Vulnerability[] = [];

  for (const [key, pkg] of graph) {
    // In production: check against npm advisory database or Snyk/GitHub
    // Simplified: check known vulnerable patterns
    const knownVulns = await checkAdvisoryDB(pkg.name, pkg.version);
    for (const v of knownVulns) {
      const reachable = pkg.type === "production" && pkg.depth <= 3;
      const riskScore = calculateRiskScore(v.severity, reachable, pkg.type, pkg.depth);

      vulns.push({
        ...v, package: key, reachable,
        affectedPaths: [["root", ...getPathToPackage(graph, key)]],
        riskScore,
      });
    }
  }

  return vulns.sort((a, b) => b.riskScore - a.riskScore);
}

function calculateRiskScore(severity: string, reachable: boolean, type: string, depth: number): number {
  let score = 0;
  switch (severity) {
    case "critical": score = 90; break;
    case "high": score = 70; break;
    case "medium": score = 40; break;
    case "low": score = 20; break;
  }
  if (!reachable) score *= 0.3;       // unreachable = much lower risk
  if (type === "development") score *= 0.2;  // dev-only = lower risk
  if (depth > 5) score *= 0.5;         // deep transitive = lower risk
  return Math.round(score);
}

function checkLicenses(graph: Map<string, Package>): LicenseIssue[] {
  const issues: LicenseIssue[] = [];
  const copyleftLicenses = ["GPL-2.0", "GPL-3.0", "AGPL-3.0", "LGPL-2.1", "LGPL-3.0"];
  const unknownLicenses = ["unknown", "UNLICENSED", ""];

  for (const [key, pkg] of graph) {
    if (pkg.type === "development") continue;  // dev deps don't affect distribution
    if (copyleftLicenses.includes(pkg.license)) {
      issues.push({ package: pkg.name, version: pkg.version, license: pkg.license, issue: "copyleft", requiredBy: pkg.dependents });
    }
    if (unknownLicenses.includes(pkg.license)) {
      issues.push({ package: pkg.name, version: pkg.version, license: pkg.license || "unknown", issue: "unknown", requiredBy: pkg.dependents });
    }
  }

  return issues;
}

function detectCircularDeps(graph: Map<string, Package>): string[][] {
  const cycles: string[][] = [];
  const visited = new Set<string>();
  const stack = new Set<string>();

  function dfs(node: string, path: string[]): void {
    if (stack.has(node)) {
      const cycleStart = path.indexOf(node);
      cycles.push(path.slice(cycleStart));
      return;
    }
    if (visited.has(node)) return;

    visited.add(node);
    stack.add(node);

    const pkg = graph.get(node);
    if (pkg) {
      for (const dep of pkg.dependencies) {
        dfs(dep, [...path, node]);
      }
    }

    stack.delete(node);
  }

  for (const key of graph.keys()) dfs(key, []);
  return cycles;
}

// Analyze upgrade impact
export async function analyzeUpgrade(packageName: string, targetVersion: string, graph: Map<string, Package>): Promise<UpgradeImpact> {
  const current = Array.from(graph.entries()).find(([, p]) => p.name === packageName);
  if (!current) throw new Error(`Package ${packageName} not found`);

  const [key, pkg] = current;
  const directDependents = pkg.dependents;
  const transitiveDependents = getTransitiveDependents(graph, key);

  return {
    package: packageName,
    from: pkg.version,
    to: targetVersion,
    directDependents,
    transitiveDependents,
    breakingChanges: [],  // in production: check changelogs
    vulnerabilitiesFixed: 0,
  };
}

function getPathToPackage(graph: Map<string, Package>, target: string): string[] {
  return [target];
}

function getTransitiveDependents(graph: Map<string, Package>, target: string): string[] {
  const visited = new Set<string>();
  const queue = [target];
  while (queue.length > 0) {
    const current = queue.shift()!;
    const pkg = graph.get(current);
    if (!pkg) continue;
    for (const dep of pkg.dependents) {
      if (!visited.has(dep)) {
        visited.add(dep);
        queue.push(dep);
      }
    }
  }
  return Array.from(visited);
}

async function checkAdvisoryDB(name: string, version: string): Promise<Array<{ id: string; severity: string; title: string; fixVersion: string | null; cwe: string }>> {
  // In production: call npm audit API or Snyk
  return [];
}

Results

  • 47 vulns → 3 that matter — risk scoring factors in reachability, dependency type, and depth; 44 vulns are dev-only or unreachable; team focuses on 3 critical production vulns
  • GPL dependency caught — license scan found GPL-3.0 package used in proprietary build; replaced before legal due diligence; avoided potential acquisition blocker
  • Circular deps fixed — detected A→B→C→A cycle between internal packages; refactored shared code into common package; build time dropped 40%
  • Upgrade blast radius visible — upgrading lodash shows 8 direct + 12 transitive dependents; team plans migration incrementally instead of big-bang upgrade
  • 800 deps visualized — full dependency tree rendered as interactive graph; click any node to see its dependents, vulnerabilities, and license; onboarding developers understand the codebase faster

Related use cases