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/{chunk-Q4IB4JZ6.js → chunk-LXZ73T7X.js} +3 -3
- package/dist/{chunk-X54UKOJM.js.map → chunk-LXZ73T7X.js.map} +1 -1
- package/dist/{chunk-X54UKOJM.js → chunk-VB74K47A.js} +3 -3
- package/dist/{chunk-Q4IB4JZ6.js.map → chunk-VB74K47A.js.map} +1 -1
- package/dist/cli.js +382 -27
- package/dist/cli.js.map +1 -1
- package/dist/{flow-server-4LJZBCB4.js → flow-server-UQEOUP2C.js} +3 -3
- package/dist/{flow-server-4LJZBCB4.js.map → flow-server-UQEOUP2C.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp-server.js +2 -2
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
FlowStorage
|
|
5
|
-
} from "./chunk-
|
|
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
|
|
10
|
-
import { existsSync as
|
|
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
|
|
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 =
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
1485
|
-
if (!
|
|
1798
|
+
const claudeConfigPath = join4(homedir(), ".claude", "config.json");
|
|
1799
|
+
if (!existsSync3(claudeConfigPath)) {
|
|
1486
1800
|
return false;
|
|
1487
1801
|
}
|
|
1488
|
-
const config = JSON.parse(
|
|
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.
|
|
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 =
|
|
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 (!
|
|
1823
|
+
if (!existsSync3(outputDir)) {
|
|
1510
1824
|
mkdirSync(outputDir, { recursive: true });
|
|
1511
1825
|
}
|
|
1512
|
-
const dbPath =
|
|
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 =
|
|
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 (
|
|
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 (!
|
|
1905
|
+
if (!existsSync3(outputDir)) {
|
|
1592
1906
|
mkdirSync(outputDir, { recursive: true });
|
|
1593
1907
|
}
|
|
1594
|
-
const dbPath =
|
|
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 =
|
|
1970
|
+
const dbPath = join4(rootPath, ".codemap", "graph.db");
|
|
1657
1971
|
console.log(chalk.blue.bold("\n CodeMap Impact Analysis\n"));
|
|
1658
|
-
if (!
|
|
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}
|
|
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 =
|
|
1724
|
-
await import("./flow-server-
|
|
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
|