codemap-ai 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  import {
4
4
  FlowStorage
5
- } from "./chunk-Q4IB4JZ6.js";
5
+ } from "./chunk-LXZ73T7X.js";
6
6
 
7
7
  // src/cli-flow.ts
8
8
  import { Command } from "commander";
9
- import { resolve as resolve3, join as join3 } from "path";
10
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
9
+ import { resolve as resolve3, join as join4 } from "path";
10
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
11
11
  import { homedir } from "os";
12
12
  import chalk from "chalk";
13
13
  import ora from "ora";
@@ -1279,7 +1279,7 @@ var DEFAULT_CONFIG = {
1279
1279
  };
1280
1280
 
1281
1281
  // src/commands/impact.ts
1282
- import { resolve as resolve2, join as join2 } from "path";
1282
+ import { resolve as resolve2, join as join3, relative as relative2 } from "path";
1283
1283
 
1284
1284
  // src/utils/git.ts
1285
1285
  import { execSync } from "child_process";
@@ -1381,10 +1381,279 @@ function getGitStatus(rootPath) {
1381
1381
  }
1382
1382
  }
1383
1383
 
1384
+ // src/analysis/imports.ts
1385
+ import { readFileSync as readFileSync2 } from "fs";
1386
+ function analyzeJavaScriptImports(filePath) {
1387
+ const content = readFileSync2(filePath, "utf-8");
1388
+ const lines = content.split("\n");
1389
+ const imports = /* @__PURE__ */ new Map();
1390
+ for (let i = 0; i < lines.length; i++) {
1391
+ const line = lines[i];
1392
+ const defaultMatch = line.match(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/);
1393
+ if (defaultMatch) {
1394
+ imports.set(defaultMatch[1], i + 1);
1395
+ }
1396
+ const namedMatch = line.match(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/);
1397
+ if (namedMatch) {
1398
+ const names = namedMatch[1].split(",").map((n) => n.trim().split(" as ")[0].trim());
1399
+ names.forEach((name) => imports.set(name, i + 1));
1400
+ }
1401
+ }
1402
+ const usedIdentifiers = /* @__PURE__ */ new Map();
1403
+ for (let i = 0; i < lines.length; i++) {
1404
+ const line = lines[i];
1405
+ if (line.trim().startsWith("import ") || line.trim().startsWith("//") || line.trim().startsWith("*")) {
1406
+ continue;
1407
+ }
1408
+ for (const [identifier] of imports) {
1409
+ const regex = new RegExp(`\\b${identifier}\\b`, "g");
1410
+ if (regex.test(line)) {
1411
+ if (!usedIdentifiers.has(identifier)) {
1412
+ usedIdentifiers.set(identifier, []);
1413
+ }
1414
+ usedIdentifiers.get(identifier).push(i + 1);
1415
+ }
1416
+ }
1417
+ }
1418
+ const unused = [];
1419
+ for (const [identifier, line] of imports) {
1420
+ const usage = usedIdentifiers.get(identifier);
1421
+ if (!usage || usage.length === 0) {
1422
+ unused.push({ identifier, line, type: "unused" });
1423
+ }
1424
+ }
1425
+ const missing = [];
1426
+ const commonLibraries = ["express", "cors", "winston", "Redis", "MongoClient", "mongoose", "axios"];
1427
+ for (const lib of commonLibraries) {
1428
+ if (!imports.has(lib)) {
1429
+ const usageLines = [];
1430
+ for (let i = 0; i < lines.length; i++) {
1431
+ const line = lines[i];
1432
+ if (line.includes(lib) && !line.trim().startsWith("//")) {
1433
+ usageLines.push(i + 1);
1434
+ }
1435
+ }
1436
+ if (usageLines.length > 0) {
1437
+ missing.push({
1438
+ identifier: lib,
1439
+ line: usageLines[0],
1440
+ type: "missing",
1441
+ usedAt: usageLines
1442
+ });
1443
+ }
1444
+ }
1445
+ }
1446
+ return { missing, unused };
1447
+ }
1448
+ function analyzePythonImports(filePath) {
1449
+ const content = readFileSync2(filePath, "utf-8");
1450
+ const lines = content.split("\n");
1451
+ const imports = /* @__PURE__ */ new Map();
1452
+ for (let i = 0; i < lines.length; i++) {
1453
+ const line = lines[i];
1454
+ const importMatch = line.match(/^import\s+(\w+)/);
1455
+ if (importMatch) {
1456
+ imports.set(importMatch[1], i + 1);
1457
+ }
1458
+ const fromMatch = line.match(/^from\s+[\w.]+\s+import\s+(.+)/);
1459
+ if (fromMatch) {
1460
+ const names = fromMatch[1].split(",").map((n) => n.trim().split(" as ")[0].trim());
1461
+ names.forEach((name) => imports.set(name, i + 1));
1462
+ }
1463
+ }
1464
+ const usedIdentifiers = /* @__PURE__ */ new Map();
1465
+ for (let i = 0; i < lines.length; i++) {
1466
+ const line = lines[i];
1467
+ if (line.trim().startsWith("import ") || line.trim().startsWith("from ") || line.trim().startsWith("#")) {
1468
+ continue;
1469
+ }
1470
+ for (const [identifier] of imports) {
1471
+ const regex = new RegExp(`\\b${identifier}\\b`, "g");
1472
+ if (regex.test(line)) {
1473
+ if (!usedIdentifiers.has(identifier)) {
1474
+ usedIdentifiers.set(identifier, []);
1475
+ }
1476
+ usedIdentifiers.get(identifier).push(i + 1);
1477
+ }
1478
+ }
1479
+ }
1480
+ const unused = [];
1481
+ for (const [identifier, line] of imports) {
1482
+ const usage = usedIdentifiers.get(identifier);
1483
+ if (!usage || usage.length === 0) {
1484
+ unused.push({ identifier, line, type: "unused" });
1485
+ }
1486
+ }
1487
+ return { missing: [], unused };
1488
+ }
1489
+ function analyzeImports(filePath) {
1490
+ if (filePath.endsWith(".py")) {
1491
+ return analyzePythonImports(filePath);
1492
+ } else if (filePath.match(/\.(js|jsx|ts|tsx)$/)) {
1493
+ return analyzeJavaScriptImports(filePath);
1494
+ }
1495
+ return { missing: [], unused: [] };
1496
+ }
1497
+
1498
+ // src/analysis/docker.ts
1499
+ import { readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
1500
+ import { load as parseYaml } from "js-yaml";
1501
+ import { join as join2 } from "path";
1502
+ function parseDockerfile(dockerfilePath) {
1503
+ if (!existsSync2(dockerfilePath)) {
1504
+ return null;
1505
+ }
1506
+ const content = readFileSync3(dockerfilePath, "utf-8");
1507
+ const lines = content.split("\n");
1508
+ let inHealthCheck = false;
1509
+ for (const line of lines) {
1510
+ const trimmed = line.trim();
1511
+ if (line.includes("HEALTHCHECK")) {
1512
+ inHealthCheck = true;
1513
+ continue;
1514
+ }
1515
+ if (inHealthCheck && !line.startsWith(" ") && !line.startsWith(" ")) {
1516
+ inHealthCheck = false;
1517
+ }
1518
+ if (inHealthCheck) {
1519
+ continue;
1520
+ }
1521
+ if (trimmed.startsWith("CMD")) {
1522
+ const match = trimmed.match(/CMD\s+\[([^\]]+)\]/);
1523
+ if (match) {
1524
+ const parts = match[1].split(",").map((p) => p.trim().replace(/"/g, ""));
1525
+ const commandStr = parts.join(" ");
1526
+ return extractEntryPointFromCommand(commandStr);
1527
+ }
1528
+ const shellMatch = trimmed.match(/CMD\s+(.+)/);
1529
+ if (shellMatch) {
1530
+ return extractEntryPointFromCommand(shellMatch[1]);
1531
+ }
1532
+ }
1533
+ if (trimmed.startsWith("ENTRYPOINT")) {
1534
+ const match = trimmed.match(/ENTRYPOINT\s+\[([^\]]+)\]/);
1535
+ if (match) {
1536
+ const parts = match[1].split(",").map((p) => p.trim().replace(/"/g, ""));
1537
+ const commandStr = parts.join(" ");
1538
+ return extractEntryPointFromCommand(commandStr);
1539
+ }
1540
+ }
1541
+ }
1542
+ return null;
1543
+ }
1544
+ function extractEntryPointFromCommand(command) {
1545
+ const commandStr = Array.isArray(command) ? command.join(" ") : command;
1546
+ const patterns = [
1547
+ /node\s+([^\s]+\.js)/,
1548
+ /python\s+-m\s+([\w.]+)/,
1549
+ /python\s+([^\s]+\.py)/,
1550
+ /uvicorn\s+([\w:]+)/,
1551
+ /npm\s+start/
1552
+ ];
1553
+ for (const pattern of patterns) {
1554
+ const match = commandStr.match(pattern);
1555
+ if (match) {
1556
+ const entryFile = match[1];
1557
+ if (entryFile.endsWith(".js") || entryFile.endsWith(".py")) {
1558
+ return entryFile;
1559
+ } else if (entryFile.includes(":")) {
1560
+ const file = entryFile.split(":")[0];
1561
+ return `${file}.py`;
1562
+ } else {
1563
+ const file = entryFile.replace(/\./g, "/");
1564
+ return `${file}.py`;
1565
+ }
1566
+ }
1567
+ }
1568
+ return null;
1569
+ }
1570
+ function parseDockerCompose(projectRoot) {
1571
+ const graph = {
1572
+ services: /* @__PURE__ */ new Map(),
1573
+ entryPoints: /* @__PURE__ */ new Map()
1574
+ };
1575
+ const composeFiles = [
1576
+ join2(projectRoot, "docker-compose.yaml"),
1577
+ join2(projectRoot, "docker-compose.yml"),
1578
+ join2(projectRoot, "compose.yaml"),
1579
+ join2(projectRoot, "compose.yml")
1580
+ ];
1581
+ let composeData = null;
1582
+ for (const file of composeFiles) {
1583
+ if (existsSync2(file)) {
1584
+ const content = readFileSync3(file, "utf-8");
1585
+ composeData = parseYaml(content);
1586
+ break;
1587
+ }
1588
+ }
1589
+ if (!composeData || !composeData.services) {
1590
+ return graph;
1591
+ }
1592
+ for (const [serviceName, serviceConfig] of Object.entries(composeData.services)) {
1593
+ const config = serviceConfig;
1594
+ const service = {
1595
+ name: serviceName,
1596
+ dependsOn: [],
1597
+ volumes: [],
1598
+ entryPoint: config.entrypoint,
1599
+ command: config.command
1600
+ };
1601
+ if (config.depends_on) {
1602
+ if (Array.isArray(config.depends_on)) {
1603
+ service.dependsOn = config.depends_on;
1604
+ } else if (typeof config.depends_on === "object") {
1605
+ service.dependsOn = Object.keys(config.depends_on);
1606
+ }
1607
+ }
1608
+ if (config.volumes && Array.isArray(config.volumes)) {
1609
+ service.volumes = config.volumes.filter((v) => typeof v === "string").map((v) => v.split(":")[0]);
1610
+ }
1611
+ if (config.command) {
1612
+ const entryFile = extractEntryPointFromCommand(config.command);
1613
+ if (entryFile) {
1614
+ graph.entryPoints.set(serviceName, entryFile);
1615
+ }
1616
+ }
1617
+ if (!graph.entryPoints.has(serviceName) && config.build) {
1618
+ const buildContext = typeof config.build === "string" ? config.build : config.build.context;
1619
+ if (buildContext) {
1620
+ const dockerfilePath = join2(projectRoot, buildContext, "Dockerfile");
1621
+ const entryFile = parseDockerfile(dockerfilePath);
1622
+ if (entryFile) {
1623
+ graph.entryPoints.set(serviceName, entryFile);
1624
+ }
1625
+ }
1626
+ }
1627
+ graph.services.set(serviceName, service);
1628
+ }
1629
+ return graph;
1630
+ }
1631
+ function findDependentServices(graph, serviceName) {
1632
+ const dependents = [];
1633
+ for (const [name, service] of graph.services) {
1634
+ if (service.dependsOn.includes(serviceName)) {
1635
+ dependents.push(name);
1636
+ }
1637
+ }
1638
+ return dependents;
1639
+ }
1640
+ function calculateServiceBlastRadius(graph, serviceName) {
1641
+ const affected = /* @__PURE__ */ new Set();
1642
+ const queue = [serviceName];
1643
+ while (queue.length > 0) {
1644
+ const current = queue.shift();
1645
+ if (affected.has(current)) continue;
1646
+ affected.add(current);
1647
+ const dependents = findDependentServices(graph, current);
1648
+ queue.push(...dependents);
1649
+ }
1650
+ return Array.from(affected);
1651
+ }
1652
+
1384
1653
  // src/commands/impact.ts
1385
1654
  async function analyzeImpact(options = {}) {
1386
1655
  const rootPath = resolve2(options.path || ".");
1387
- const dbPath = join2(rootPath, ".codemap", "graph.db");
1656
+ const dbPath = join3(rootPath, ".codemap", "graph.db");
1388
1657
  const storage = new FlowStorage(dbPath);
1389
1658
  try {
1390
1659
  if (isGitRepository(rootPath)) {
@@ -1404,6 +1673,7 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
1404
1673
  changedFunctions: [],
1405
1674
  directCallers: /* @__PURE__ */ new Map(),
1406
1675
  affectedEndpoints: [],
1676
+ importIssues: /* @__PURE__ */ new Map(),
1407
1677
  riskLevel: "LOW",
1408
1678
  totalImpact: 0
1409
1679
  };
@@ -1421,7 +1691,7 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
1421
1691
  const directCallers = /* @__PURE__ */ new Map();
1422
1692
  const affectedEndpoints = [];
1423
1693
  for (const func of changedFunctions) {
1424
- const nodes = storage.getNodesByFile(join2(rootPath, func.file)).filter((n) => n.name === func.functionName);
1694
+ const nodes = storage.getNodesByFile(join3(rootPath, func.file)).filter((n) => n.name === func.functionName);
1425
1695
  for (const node of nodes) {
1426
1696
  const callers = storage.getResolvedCallersWithLocation(node.id);
1427
1697
  if (callers.length > 0) {
@@ -1433,11 +1703,44 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
1433
1703
  }
1434
1704
  }
1435
1705
  }
1706
+ const importIssues = /* @__PURE__ */ new Map();
1707
+ for (const filePath of changedFilePaths) {
1708
+ const fullPath = join3(rootPath, filePath);
1709
+ const analysis = analyzeImports(fullPath);
1710
+ if (analysis.missing.length > 0 || analysis.unused.length > 0) {
1711
+ importIssues.set(relative2(rootPath, fullPath), analysis);
1712
+ }
1713
+ }
1714
+ const dockerGraph = parseDockerCompose(rootPath);
1715
+ let dockerImpact;
1716
+ const entryPointFiles = [];
1717
+ const affectedServices = /* @__PURE__ */ new Set();
1718
+ for (const [serviceName, entryPoint] of dockerGraph.entryPoints) {
1719
+ for (const changedFile of changedFilePaths) {
1720
+ if (changedFile.includes(entryPoint) || entryPoint.includes(changedFile)) {
1721
+ entryPointFiles.push(entryPoint);
1722
+ const blastRadius = calculateServiceBlastRadius(dockerGraph, serviceName);
1723
+ blastRadius.forEach((s) => affectedServices.add(s));
1724
+ }
1725
+ }
1726
+ }
1727
+ if (affectedServices.size > 0) {
1728
+ dockerImpact = {
1729
+ affectedServices: Array.from(affectedServices),
1730
+ entryPointFiles
1731
+ };
1732
+ }
1436
1733
  const totalCallers = Array.from(directCallers.values()).reduce((sum, callers) => sum + callers.length, 0);
1734
+ const hasCriticalImportIssues = Array.from(importIssues.values()).some((i) => i.missing.length > 0);
1735
+ const hasDockerImpact = dockerImpact && dockerImpact.affectedServices.length > 0;
1437
1736
  let riskLevel = "LOW";
1438
- if (totalCallers > 20 || affectedEndpoints.length > 5) {
1737
+ if (hasCriticalImportIssues && hasDockerImpact) {
1738
+ riskLevel = "CRITICAL";
1739
+ } else if (hasCriticalImportIssues || hasDockerImpact && dockerImpact.affectedServices.length > 2) {
1740
+ riskLevel = "HIGH";
1741
+ } else if (totalCallers > 20 || affectedEndpoints.length > 5) {
1439
1742
  riskLevel = "HIGH";
1440
- } else if (totalCallers > 10 || affectedEndpoints.length > 2) {
1743
+ } else if (totalCallers > 10 || affectedEndpoints.length > 2 || hasDockerImpact) {
1441
1744
  riskLevel = "MEDIUM";
1442
1745
  }
1443
1746
  return {
@@ -1445,8 +1748,10 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
1445
1748
  changedFunctions,
1446
1749
  directCallers,
1447
1750
  affectedEndpoints,
1751
+ importIssues,
1752
+ dockerImpact,
1448
1753
  riskLevel,
1449
- totalImpact: totalCallers
1754
+ totalImpact: totalCallers + (dockerImpact?.affectedServices.length || 0)
1450
1755
  };
1451
1756
  }
1452
1757
  async function analyzeImpactWithHash(storage, rootPath, options) {
@@ -1467,12 +1772,21 @@ async function analyzeImpactWithHash(storage, rootPath, options) {
1467
1772
  changedFiles.push(filePath);
1468
1773
  }
1469
1774
  }
1775
+ const importIssues = /* @__PURE__ */ new Map();
1776
+ for (const filePath of changedFiles) {
1777
+ const analysis = analyzeImports(filePath);
1778
+ if (analysis.missing.length > 0 || analysis.unused.length > 0) {
1779
+ importIssues.set(relative2(rootPath, filePath), analysis);
1780
+ }
1781
+ }
1782
+ const hasCriticalImportIssues = Array.from(importIssues.values()).some((i) => i.missing.length > 0);
1470
1783
  return {
1471
1784
  changedFiles,
1472
1785
  changedFunctions: [],
1473
1786
  directCallers: /* @__PURE__ */ new Map(),
1474
1787
  affectedEndpoints: [],
1475
- riskLevel: changedFiles.length > 10 ? "HIGH" : changedFiles.length > 5 ? "MEDIUM" : "LOW",
1788
+ importIssues,
1789
+ riskLevel: hasCriticalImportIssues ? "CRITICAL" : changedFiles.length > 10 ? "HIGH" : changedFiles.length > 5 ? "MEDIUM" : "LOW",
1476
1790
  totalImpact: result.indexed
1477
1791
  };
1478
1792
  }
@@ -1481,11 +1795,11 @@ async function analyzeImpactWithHash(storage, rootPath, options) {
1481
1795
  var program = new Command();
1482
1796
  function configureMCPServer(projectPath) {
1483
1797
  try {
1484
- const claudeConfigPath = join3(homedir(), ".claude", "config.json");
1485
- if (!existsSync2(claudeConfigPath)) {
1798
+ const claudeConfigPath = join4(homedir(), ".claude", "config.json");
1799
+ if (!existsSync3(claudeConfigPath)) {
1486
1800
  return false;
1487
1801
  }
1488
- const config = JSON.parse(readFileSync2(claudeConfigPath, "utf-8"));
1802
+ const config = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
1489
1803
  if (!config.mcpServers) {
1490
1804
  config.mcpServers = {};
1491
1805
  }
@@ -1499,17 +1813,17 @@ function configureMCPServer(projectPath) {
1499
1813
  return false;
1500
1814
  }
1501
1815
  }
1502
- program.name("codemap").description("CodeMap Flow - Call graph analyzer for understanding code impact").version("3.1.0");
1816
+ program.name("codemap").description("CodeMap Flow - Call graph analyzer for understanding code impact").version("3.2.0");
1503
1817
  program.command("index").description("Build call graph for your codebase").argument("[path]", "Path to the project root", ".").option("-f, --force", "Force reindex all files (ignore cache)").option("--include <patterns...>", "Glob patterns to include").option("--exclude <patterns...>", "Glob patterns to exclude").action(async (path, options) => {
1504
1818
  const rootPath = resolve3(path);
1505
- const outputDir = join3(rootPath, ".codemap");
1819
+ const outputDir = join4(rootPath, ".codemap");
1506
1820
  console.log(chalk.blue.bold("\n CodeMap Call Graph Indexer\n"));
1507
1821
  console.log(chalk.gray(` Project: ${rootPath}
1508
1822
  `));
1509
- if (!existsSync2(outputDir)) {
1823
+ if (!existsSync3(outputDir)) {
1510
1824
  mkdirSync(outputDir, { recursive: true });
1511
1825
  }
1512
- const dbPath = join3(outputDir, "graph.db");
1826
+ const dbPath = join4(outputDir, "graph.db");
1513
1827
  const storage = new FlowStorage(dbPath);
1514
1828
  const builder = new FlowBuilder(storage, {
1515
1829
  rootPath,
@@ -1580,18 +1894,18 @@ program.command("index").description("Build call graph for your codebase").argum
1580
1894
  });
1581
1895
  program.command("init").description("Initialize CodeMap (index + setup MCP)").argument("[path]", "Path to the project root", ".").option("--include <patterns...>", "Glob patterns to include").option("--exclude <patterns...>", "Glob patterns to exclude").option("--skip-mcp", "Skip MCP server configuration").action(async (path, options) => {
1582
1896
  const rootPath = resolve3(path);
1583
- const outputDir = join3(rootPath, ".codemap");
1897
+ const outputDir = join4(rootPath, ".codemap");
1584
1898
  console.log(chalk.blue.bold("\n CodeMap Initialization\n"));
1585
1899
  console.log(chalk.gray(` Project: ${rootPath}
1586
1900
  `));
1587
- if (existsSync2(join3(outputDir, "graph.db")) && !options.force) {
1901
+ if (existsSync3(join4(outputDir, "graph.db")) && !options.force) {
1588
1902
  console.log(chalk.yellow(" Already initialized. Use 'codemap reindex' to rebuild.\n"));
1589
1903
  return;
1590
1904
  }
1591
- if (!existsSync2(outputDir)) {
1905
+ if (!existsSync3(outputDir)) {
1592
1906
  mkdirSync(outputDir, { recursive: true });
1593
1907
  }
1594
- const dbPath = join3(outputDir, "graph.db");
1908
+ const dbPath = join4(outputDir, "graph.db");
1595
1909
  const storage = new FlowStorage(dbPath);
1596
1910
  const builder = new FlowBuilder(storage, {
1597
1911
  rootPath,
@@ -1653,9 +1967,9 @@ program.command("init").description("Initialize CodeMap (index + setup MCP)").ar
1653
1967
  });
1654
1968
  program.command("impact").description("Analyze impact of code changes").argument("[path]", "Path to the project root", ".").option("-v, --verbose", "Show detailed output").action(async (path, options) => {
1655
1969
  const rootPath = resolve3(path);
1656
- const dbPath = join3(rootPath, ".codemap", "graph.db");
1970
+ const dbPath = join4(rootPath, ".codemap", "graph.db");
1657
1971
  console.log(chalk.blue.bold("\n CodeMap Impact Analysis\n"));
1658
- if (!existsSync2(dbPath)) {
1972
+ if (!existsSync3(dbPath)) {
1659
1973
  console.log(chalk.red(" Error: Not initialized. Run 'codemap init' first.\n"));
1660
1974
  process.exit(1);
1661
1975
  }
@@ -1702,9 +2016,50 @@ program.command("impact").description("Analyze impact of code changes").argument
1702
2016
  console.log(chalk.gray(` \u2022 ${endpoint}`));
1703
2017
  }
1704
2018
  }
2019
+ if (result.importIssues.size > 0) {
2020
+ console.log(chalk.blue("\n \u{1F50D} Import Analysis:\n"));
2021
+ for (const [file, issues] of result.importIssues.entries()) {
2022
+ if (issues.missing.length > 0) {
2023
+ console.log(chalk.red(` \u274C CRITICAL: ${file} - Missing imports still in use:`));
2024
+ for (const missing of issues.missing.slice(0, 5)) {
2025
+ const usageInfo = missing.usedAt ? ` (used at lines: ${missing.usedAt.slice(0, 3).join(", ")}${missing.usedAt.length > 3 ? "..." : ""})` : "";
2026
+ console.log(chalk.gray(` \u2022 ${missing.identifier}${usageInfo}`));
2027
+ }
2028
+ if (issues.missing.length > 5) {
2029
+ console.log(chalk.gray(` ... and ${issues.missing.length - 5} more`));
2030
+ }
2031
+ }
2032
+ if (issues.unused.length > 0 && options.verbose) {
2033
+ console.log(chalk.yellow(` \u26A0\uFE0F ${file} - Unused imports:`));
2034
+ for (const unused of issues.unused.slice(0, 3)) {
2035
+ console.log(chalk.gray(` \u2022 ${unused.identifier} (line ${unused.line})`));
2036
+ }
2037
+ }
2038
+ }
2039
+ }
2040
+ if (result.dockerImpact) {
2041
+ console.log(chalk.blue("\n \u{1F4E6} Docker Service Impact:\n"));
2042
+ console.log(chalk.red(` \u26A0\uFE0F Entry point file(s) modified - service(s) will not start:`));
2043
+ for (const file of result.dockerImpact.entryPointFiles) {
2044
+ console.log(chalk.gray(` \u2022 ${file}`));
2045
+ }
2046
+ console.log(chalk.red(`
2047
+ \u{1F4A5} Affected Services (${result.dockerImpact.affectedServices.length}):`));
2048
+ for (const service of result.dockerImpact.affectedServices) {
2049
+ console.log(chalk.gray(` \u2022 ${service}`));
2050
+ }
2051
+ console.log(chalk.yellow(`
2052
+ \u2192 If entry point crashes, these services WILL FAIL`));
2053
+ }
1705
2054
  console.log(chalk.blue("\n Risk Assessment:\n"));
1706
- const riskColor = result.riskLevel === "HIGH" ? chalk.red : result.riskLevel === "MEDIUM" ? chalk.yellow : chalk.green;
1707
- console.log(` ${riskColor(result.riskLevel)} - ${result.totalImpact} functions affected`);
2055
+ const riskColor = result.riskLevel === "CRITICAL" ? chalk.red.bold : result.riskLevel === "HIGH" ? chalk.red : result.riskLevel === "MEDIUM" ? chalk.yellow : chalk.green;
2056
+ console.log(` ${riskColor(result.riskLevel)} - ${result.totalImpact} components affected`);
2057
+ if (result.riskLevel === "CRITICAL") {
2058
+ console.log(chalk.red.bold("\n \u26A0\uFE0F DO NOT DEPLOY - System-wide failure risk"));
2059
+ console.log(chalk.gray(" Fix critical issues before proceeding"));
2060
+ } else if (result.riskLevel === "HIGH") {
2061
+ console.log(chalk.yellow("\n \u26A0\uFE0F High risk - Extensive testing required"));
2062
+ }
1708
2063
  console.log();
1709
2064
  } catch (error) {
1710
2065
  spinner.fail(chalk.red("Impact analysis failed"));
@@ -1720,8 +2075,8 @@ program.command("reindex").description("Force full re-index of codebase").argume
1720
2075
  });
1721
2076
  program.command("mcp-server").description("Start MCP server for Claude Code integration").option("-p, --path <path>", "Project root path", ".").action(async (options) => {
1722
2077
  process.env.CODEMAP_PROJECT_ROOT = resolve3(options.path);
1723
- process.env.CODEMAP_DB_PATH = join3(resolve3(options.path), ".codemap", "graph.db");
1724
- await import("./flow-server-4LJZBCB4.js");
2078
+ process.env.CODEMAP_DB_PATH = join4(resolve3(options.path), ".codemap", "graph.db");
2079
+ await import("./flow-server-UQEOUP2C.js");
1725
2080
  });
1726
2081
  program.parse();
1727
2082
  //# sourceMappingURL=cli.js.map