codemap-ai 3.0.0 → 3.1.1

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,13 @@
2
2
 
3
3
  import {
4
4
  FlowStorage
5
- } from "./chunk-XNA2HNUR.js";
5
+ } from "./chunk-LXZ73T7X.js";
6
6
 
7
7
  // src/cli-flow.ts
8
8
  import { Command } from "commander";
9
- import { resolve as resolve2, join } from "path";
10
- import { existsSync, mkdirSync } from "fs";
9
+ import { resolve as resolve3, join as join3 } from "path";
10
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
11
+ import { homedir } from "os";
11
12
  import chalk from "chalk";
12
13
  import ora from "ora";
13
14
 
@@ -1277,19 +1278,238 @@ var DEFAULT_CONFIG = {
1277
1278
  languages: ["typescript", "javascript", "python"]
1278
1279
  };
1279
1280
 
1281
+ // src/commands/impact.ts
1282
+ import { resolve as resolve2, join as join2 } from "path";
1283
+
1284
+ // src/utils/git.ts
1285
+ import { execSync } from "child_process";
1286
+ import { existsSync } from "fs";
1287
+ import { join } from "path";
1288
+ function isGitRepository(path) {
1289
+ return existsSync(join(path, ".git"));
1290
+ }
1291
+ function getGitChangedFiles(rootPath) {
1292
+ try {
1293
+ const output = execSync("git diff HEAD --name-status", {
1294
+ cwd: rootPath,
1295
+ encoding: "utf-8"
1296
+ }).trim();
1297
+ if (!output) {
1298
+ return [];
1299
+ }
1300
+ const files = [];
1301
+ for (const line of output.split("\n")) {
1302
+ const [status, path] = line.split(" ");
1303
+ if (path) {
1304
+ files.push({
1305
+ path,
1306
+ status: status === "A" ? "added" : status === "D" ? "deleted" : "modified"
1307
+ });
1308
+ }
1309
+ }
1310
+ return files;
1311
+ } catch (error) {
1312
+ return [];
1313
+ }
1314
+ }
1315
+ function parseGitDiffForFunctions(rootPath, files) {
1316
+ const changedFunctions = [];
1317
+ for (const file of files) {
1318
+ if (file.status === "deleted") {
1319
+ continue;
1320
+ }
1321
+ try {
1322
+ const diff = execSync(`git diff HEAD -- "${file.path}"`, {
1323
+ cwd: rootPath,
1324
+ encoding: "utf-8"
1325
+ });
1326
+ const lines = diff.split("\n");
1327
+ let currentFunction = null;
1328
+ for (let i = 0; i < lines.length; i++) {
1329
+ const line = lines[i];
1330
+ if (line.startsWith("@@")) {
1331
+ const contextMatch = line.match(/@@.*@@\s*(.*)/);
1332
+ if (contextMatch) {
1333
+ const context = contextMatch[1];
1334
+ const pythonMatch = context.match(/(?:async\s+)?def\s+(\w+)/);
1335
+ if (pythonMatch) {
1336
+ currentFunction = pythonMatch[1];
1337
+ }
1338
+ const jsMatch = context.match(/(?:function|const|class)\s+(\w+)/);
1339
+ if (jsMatch) {
1340
+ currentFunction = jsMatch[1];
1341
+ }
1342
+ const lineMatch = line.match(/\+(\d+)/);
1343
+ const lineNumber = lineMatch ? parseInt(lineMatch[1]) : 0;
1344
+ if (currentFunction) {
1345
+ changedFunctions.push({
1346
+ file: file.path,
1347
+ functionName: currentFunction,
1348
+ lineNumber,
1349
+ changeType: file.status === "added" ? "added" : "modified"
1350
+ });
1351
+ }
1352
+ }
1353
+ }
1354
+ }
1355
+ } catch (error) {
1356
+ continue;
1357
+ }
1358
+ }
1359
+ return changedFunctions;
1360
+ }
1361
+ function getGitStatus(rootPath) {
1362
+ try {
1363
+ const statusOutput = execSync("git status --porcelain", {
1364
+ cwd: rootPath,
1365
+ encoding: "utf-8"
1366
+ }).trim();
1367
+ const lines = statusOutput.split("\n").filter((l) => l.trim());
1368
+ const staged = lines.filter((l) => l[0] !== " " && l[0] !== "?").length;
1369
+ const modified = lines.length;
1370
+ return {
1371
+ hasChanges: modified > 0,
1372
+ modifiedFiles: modified,
1373
+ stagedFiles: staged
1374
+ };
1375
+ } catch (error) {
1376
+ return {
1377
+ hasChanges: false,
1378
+ modifiedFiles: 0,
1379
+ stagedFiles: 0
1380
+ };
1381
+ }
1382
+ }
1383
+
1384
+ // src/commands/impact.ts
1385
+ async function analyzeImpact(options = {}) {
1386
+ const rootPath = resolve2(options.path || ".");
1387
+ const dbPath = join2(rootPath, ".codemap", "graph.db");
1388
+ const storage = new FlowStorage(dbPath);
1389
+ try {
1390
+ if (isGitRepository(rootPath)) {
1391
+ return await analyzeImpactWithGit(storage, rootPath, options);
1392
+ } else {
1393
+ return await analyzeImpactWithHash(storage, rootPath, options);
1394
+ }
1395
+ } finally {
1396
+ storage.close();
1397
+ }
1398
+ }
1399
+ async function analyzeImpactWithGit(storage, rootPath, options) {
1400
+ const gitStatus = getGitStatus(rootPath);
1401
+ if (!gitStatus.hasChanges) {
1402
+ return {
1403
+ changedFiles: [],
1404
+ changedFunctions: [],
1405
+ directCallers: /* @__PURE__ */ new Map(),
1406
+ affectedEndpoints: [],
1407
+ riskLevel: "LOW",
1408
+ totalImpact: 0
1409
+ };
1410
+ }
1411
+ const changedFiles = getGitChangedFiles(rootPath);
1412
+ const changedFilePaths = changedFiles.map((f) => f.path);
1413
+ const changedFunctions = parseGitDiffForFunctions(rootPath, changedFiles);
1414
+ const builder = new FlowBuilder(storage, {
1415
+ rootPath,
1416
+ include: changedFilePaths.map((f) => f),
1417
+ exclude: [],
1418
+ forceReindex: true
1419
+ });
1420
+ await builder.build();
1421
+ const directCallers = /* @__PURE__ */ new Map();
1422
+ const affectedEndpoints = [];
1423
+ for (const func of changedFunctions) {
1424
+ const nodes = storage.getNodesByFile(join2(rootPath, func.file)).filter((n) => n.name === func.functionName);
1425
+ for (const node of nodes) {
1426
+ const callers = storage.getResolvedCallersWithLocation(node.id);
1427
+ if (callers.length > 0) {
1428
+ directCallers.set(func.functionName, callers);
1429
+ }
1430
+ const endpoints = storage.getHttpEndpointsByHandler(node.id);
1431
+ for (const endpoint of endpoints) {
1432
+ affectedEndpoints.push(`${endpoint.method} ${endpoint.path}`);
1433
+ }
1434
+ }
1435
+ }
1436
+ const totalCallers = Array.from(directCallers.values()).reduce((sum, callers) => sum + callers.length, 0);
1437
+ let riskLevel = "LOW";
1438
+ if (totalCallers > 20 || affectedEndpoints.length > 5) {
1439
+ riskLevel = "HIGH";
1440
+ } else if (totalCallers > 10 || affectedEndpoints.length > 2) {
1441
+ riskLevel = "MEDIUM";
1442
+ }
1443
+ return {
1444
+ changedFiles: changedFilePaths,
1445
+ changedFunctions,
1446
+ directCallers,
1447
+ affectedEndpoints,
1448
+ riskLevel,
1449
+ totalImpact: totalCallers
1450
+ };
1451
+ }
1452
+ async function analyzeImpactWithHash(storage, rootPath, options) {
1453
+ const oldHashes = storage.getAllFileHashes();
1454
+ const builder = new FlowBuilder(storage, {
1455
+ rootPath,
1456
+ include: DEFAULT_CONFIG.include,
1457
+ exclude: DEFAULT_CONFIG.exclude,
1458
+ forceReindex: false
1459
+ // Use hash comparison
1460
+ });
1461
+ const result = await builder.build();
1462
+ const changedFiles = [];
1463
+ const newHashes = storage.getAllFileHashes();
1464
+ for (const [filePath, newHash] of Object.entries(newHashes)) {
1465
+ const oldHash = oldHashes[filePath];
1466
+ if (!oldHash || oldHash !== newHash) {
1467
+ changedFiles.push(filePath);
1468
+ }
1469
+ }
1470
+ return {
1471
+ changedFiles,
1472
+ changedFunctions: [],
1473
+ directCallers: /* @__PURE__ */ new Map(),
1474
+ affectedEndpoints: [],
1475
+ riskLevel: changedFiles.length > 10 ? "HIGH" : changedFiles.length > 5 ? "MEDIUM" : "LOW",
1476
+ totalImpact: result.indexed
1477
+ };
1478
+ }
1479
+
1280
1480
  // src/cli-flow.ts
1281
1481
  var program = new Command();
1282
- program.name("codemap").description("CodeMap Flow - Call graph analyzer for understanding code impact").version("3.0.0");
1482
+ function configureMCPServer(projectPath) {
1483
+ try {
1484
+ const claudeConfigPath = join3(homedir(), ".claude", "config.json");
1485
+ if (!existsSync2(claudeConfigPath)) {
1486
+ return false;
1487
+ }
1488
+ const config = JSON.parse(readFileSync2(claudeConfigPath, "utf-8"));
1489
+ if (!config.mcpServers) {
1490
+ config.mcpServers = {};
1491
+ }
1492
+ config.mcpServers.codemap = {
1493
+ command: "npx",
1494
+ args: ["codemap-ai", "mcp-server", "--path", projectPath]
1495
+ };
1496
+ writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2));
1497
+ return true;
1498
+ } catch (error) {
1499
+ return false;
1500
+ }
1501
+ }
1502
+ program.name("codemap").description("CodeMap Flow - Call graph analyzer for understanding code impact").version("3.1.1");
1283
1503
  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) => {
1284
- const rootPath = resolve2(path);
1285
- const outputDir = join(rootPath, ".codemap");
1504
+ const rootPath = resolve3(path);
1505
+ const outputDir = join3(rootPath, ".codemap");
1286
1506
  console.log(chalk.blue.bold("\n CodeMap Call Graph Indexer\n"));
1287
1507
  console.log(chalk.gray(` Project: ${rootPath}
1288
1508
  `));
1289
- if (!existsSync(outputDir)) {
1509
+ if (!existsSync2(outputDir)) {
1290
1510
  mkdirSync(outputDir, { recursive: true });
1291
1511
  }
1292
- const dbPath = join(outputDir, "graph.db");
1512
+ const dbPath = join3(outputDir, "graph.db");
1293
1513
  const storage = new FlowStorage(dbPath);
1294
1514
  const builder = new FlowBuilder(storage, {
1295
1515
  rootPath,
@@ -1358,10 +1578,150 @@ program.command("index").description("Build call graph for your codebase").argum
1358
1578
  storage.close();
1359
1579
  }
1360
1580
  });
1581
+ 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
+ const rootPath = resolve3(path);
1583
+ const outputDir = join3(rootPath, ".codemap");
1584
+ console.log(chalk.blue.bold("\n CodeMap Initialization\n"));
1585
+ console.log(chalk.gray(` Project: ${rootPath}
1586
+ `));
1587
+ if (existsSync2(join3(outputDir, "graph.db")) && !options.force) {
1588
+ console.log(chalk.yellow(" Already initialized. Use 'codemap reindex' to rebuild.\n"));
1589
+ return;
1590
+ }
1591
+ if (!existsSync2(outputDir)) {
1592
+ mkdirSync(outputDir, { recursive: true });
1593
+ }
1594
+ const dbPath = join3(outputDir, "graph.db");
1595
+ const storage = new FlowStorage(dbPath);
1596
+ const builder = new FlowBuilder(storage, {
1597
+ rootPath,
1598
+ include: options.include || DEFAULT_CONFIG.include,
1599
+ exclude: options.exclude || DEFAULT_CONFIG.exclude,
1600
+ forceReindex: true
1601
+ });
1602
+ const spinner = ora("Indexing codebase...").start();
1603
+ builder.setProgressCallback((progress) => {
1604
+ switch (progress.phase) {
1605
+ case "discovering":
1606
+ spinner.text = "Discovering files...";
1607
+ break;
1608
+ case "parsing":
1609
+ spinner.text = `Parsing (${progress.current}/${progress.total}): ${progress.currentFile}`;
1610
+ break;
1611
+ case "resolving":
1612
+ spinner.text = "Resolving cross-file calls...";
1613
+ break;
1614
+ case "complete":
1615
+ spinner.succeed(chalk.green("Indexing complete!"));
1616
+ break;
1617
+ }
1618
+ });
1619
+ try {
1620
+ const result = await builder.build();
1621
+ console.log(chalk.blue("\n Results:\n"));
1622
+ console.log(` ${chalk.bold("Indexed:")} ${result.indexed} files`);
1623
+ console.log(` ${chalk.bold("Resolved:")} ${result.resolved} function calls`);
1624
+ const stats = storage.getStats();
1625
+ if (stats.httpEndpoints > 0) {
1626
+ console.log(chalk.cyan(`
1627
+ ${chalk.bold("Framework Detection:")}`));
1628
+ console.log(` ${chalk.bold("HTTP Endpoints:")} ${stats.httpEndpoints} routes`);
1629
+ }
1630
+ const resolveRate = result.resolved + result.unresolved > 0 ? Math.round(result.resolved / (result.resolved + result.unresolved) * 100) : 0;
1631
+ console.log(chalk.gray(`
1632
+ Call resolution: ${resolveRate}%`));
1633
+ console.log(chalk.gray(` Database: ${dbPath}
1634
+ `));
1635
+ if (!options.skipMcp) {
1636
+ const mcpConfigured = configureMCPServer(rootPath);
1637
+ if (mcpConfigured) {
1638
+ console.log(chalk.green(" \u2713 MCP server configured in Claude Code\n"));
1639
+ } else {
1640
+ console.log(chalk.yellow(" \u26A0 Could not auto-configure MCP (Claude Code not found)"));
1641
+ console.log(chalk.gray(" Run manually: claude mcp add codemap -- npx codemap-ai mcp-server\n"));
1642
+ }
1643
+ }
1644
+ console.log(chalk.cyan(" Ready!"));
1645
+ console.log(chalk.gray(" Ask Claude: 'What would break if I change authenticate_user?'\n"));
1646
+ } catch (error) {
1647
+ spinner.fail(chalk.red("Initialization failed"));
1648
+ console.error(error);
1649
+ process.exit(1);
1650
+ } finally {
1651
+ storage.close();
1652
+ }
1653
+ });
1654
+ 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
+ const rootPath = resolve3(path);
1656
+ const dbPath = join3(rootPath, ".codemap", "graph.db");
1657
+ console.log(chalk.blue.bold("\n CodeMap Impact Analysis\n"));
1658
+ if (!existsSync2(dbPath)) {
1659
+ console.log(chalk.red(" Error: Not initialized. Run 'codemap init' first.\n"));
1660
+ process.exit(1);
1661
+ }
1662
+ const spinner = ora("Detecting changes...").start();
1663
+ try {
1664
+ const result = await analyzeImpact({ path: rootPath, verbose: options.verbose });
1665
+ if (result.changedFiles.length === 0) {
1666
+ spinner.succeed(chalk.green("No changes detected"));
1667
+ console.log(chalk.gray(" All files are up to date.\n"));
1668
+ return;
1669
+ }
1670
+ spinner.succeed(chalk.green(`Detected ${result.changedFiles.length} modified files`));
1671
+ console.log(chalk.blue("\n Modified Files:\n"));
1672
+ for (const file of result.changedFiles.slice(0, 10)) {
1673
+ console.log(chalk.gray(` \u2022 ${file}`));
1674
+ }
1675
+ if (result.changedFiles.length > 10) {
1676
+ console.log(chalk.gray(` ... and ${result.changedFiles.length - 10} more`));
1677
+ }
1678
+ if (result.changedFunctions.length > 0) {
1679
+ console.log(chalk.blue("\n Changed Functions:\n"));
1680
+ for (const func of result.changedFunctions) {
1681
+ console.log(chalk.gray(` \u2022 ${func.functionName} (${func.file}:${func.lineNumber})`));
1682
+ }
1683
+ }
1684
+ if (result.directCallers.size > 0) {
1685
+ console.log(chalk.blue("\n Impact Analysis:\n"));
1686
+ for (const [funcName, callers] of result.directCallers.entries()) {
1687
+ console.log(chalk.yellow(` ${funcName}:`));
1688
+ console.log(chalk.gray(` ${callers.length} direct callers`));
1689
+ if (options.verbose) {
1690
+ for (const caller of callers.slice(0, 5)) {
1691
+ console.log(chalk.gray(` \u2022 ${caller.name} (${caller.file}:${caller.line})`));
1692
+ }
1693
+ if (callers.length > 5) {
1694
+ console.log(chalk.gray(` ... and ${callers.length - 5} more`));
1695
+ }
1696
+ }
1697
+ }
1698
+ }
1699
+ if (result.affectedEndpoints.length > 0) {
1700
+ console.log(chalk.blue("\n Affected HTTP Endpoints:\n"));
1701
+ for (const endpoint of result.affectedEndpoints) {
1702
+ console.log(chalk.gray(` \u2022 ${endpoint}`));
1703
+ }
1704
+ }
1705
+ 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`);
1708
+ console.log();
1709
+ } catch (error) {
1710
+ spinner.fail(chalk.red("Impact analysis failed"));
1711
+ console.error(error);
1712
+ process.exit(1);
1713
+ }
1714
+ });
1715
+ program.command("reindex").description("Force full re-index of codebase").argument("[path]", "Path to the project root", ".").option("--include <patterns...>", "Glob patterns to include").option("--exclude <patterns...>", "Glob patterns to exclude").action(async (path, options) => {
1716
+ const indexCommand = program.commands.find((cmd) => cmd.name() === "index");
1717
+ if (indexCommand) {
1718
+ await indexCommand.parseAsync([path, "--force", ...options.include ? ["--include", ...options.include] : [], ...options.exclude ? ["--exclude", ...options.exclude] : []], { from: "user" });
1719
+ }
1720
+ });
1361
1721
  program.command("mcp-server").description("Start MCP server for Claude Code integration").option("-p, --path <path>", "Project root path", ".").action(async (options) => {
1362
- process.env.CODEMAP_PROJECT_ROOT = resolve2(options.path);
1363
- process.env.CODEMAP_DB_PATH = join(resolve2(options.path), ".codemap", "graph.db");
1364
- await import("./flow-server-QO7IWYLE.js");
1722
+ 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-B2SVQKDG.js");
1365
1725
  });
1366
1726
  program.parse();
1367
1727
  //# sourceMappingURL=cli.js.map