codebase-analyzer-mcp 2.1.0 → 2.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/cli/index.js +91 -18
- package/dist/mcp/server.js +91 -18
- package/package.json +1 -1
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Multi-layer codebase analysis tools",
|
|
9
|
-
"version": "2.1.
|
|
9
|
+
"version": "2.1.1"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "codebase-analyzer",
|
|
14
14
|
"description": "Multi-layer codebase analysis with Gemini AI. 4 agents, 1 command, 1 skill for architecture analysis, pattern detection, and dataflow tracing.",
|
|
15
|
-
"version": "2.1.
|
|
15
|
+
"version": "2.1.1",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "Jake Correa",
|
|
18
18
|
"url": "https://github.com/jaykaycodes"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebase-analyzer",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Multi-layer codebase analysis with Gemini AI. 4 agents, 1 command, 1 skill for architecture analysis, pattern detection, and dataflow tracing.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Jake Correa",
|
package/dist/cli/index.js
CHANGED
|
@@ -42998,11 +42998,39 @@ var init_node = __esm(() => {
|
|
|
42998
42998
|
});
|
|
42999
42999
|
|
|
43000
43000
|
// src/core/gemini.ts
|
|
43001
|
+
import { readFileSync } from "fs";
|
|
43002
|
+
import { join as join2 } from "path";
|
|
43003
|
+
import { homedir } from "os";
|
|
43004
|
+
function resolveApiKey() {
|
|
43005
|
+
if (process.env.GEMINI_API_KEY) {
|
|
43006
|
+
return process.env.GEMINI_API_KEY;
|
|
43007
|
+
}
|
|
43008
|
+
try {
|
|
43009
|
+
const configPath = join2(homedir(), ".config", "codebase-analyzer", "config.json");
|
|
43010
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
43011
|
+
if (config.geminiApiKey) {
|
|
43012
|
+
return config.geminiApiKey;
|
|
43013
|
+
}
|
|
43014
|
+
} catch {}
|
|
43015
|
+
return;
|
|
43016
|
+
}
|
|
43017
|
+
function hasGeminiKey() {
|
|
43018
|
+
return !!resolveApiKey();
|
|
43019
|
+
}
|
|
43001
43020
|
function getClient() {
|
|
43002
43021
|
if (!client) {
|
|
43003
|
-
const apiKey =
|
|
43022
|
+
const apiKey = resolveApiKey();
|
|
43004
43023
|
if (!apiKey) {
|
|
43005
|
-
throw new Error(
|
|
43024
|
+
throw new Error(`GEMINI_API_KEY not found. Set it up using one of these methods:
|
|
43025
|
+
|
|
43026
|
+
` + `Option 1: Config file (recommended for plugin installs):
|
|
43027
|
+
` + ` mkdir -p ~/.config/codebase-analyzer
|
|
43028
|
+
` + ` echo '{"geminiApiKey":"your_key"}' > ~/.config/codebase-analyzer/config.json
|
|
43029
|
+
|
|
43030
|
+
` + `Option 2: MCP server env (for ~/.mcp.json installs):
|
|
43031
|
+
` + ` "env": { "GEMINI_API_KEY": "your_key" }
|
|
43032
|
+
|
|
43033
|
+
` + "Get a free key at https://aistudio.google.com/apikey");
|
|
43006
43034
|
}
|
|
43007
43035
|
client = new GoogleGenAI({ apiKey });
|
|
43008
43036
|
}
|
|
@@ -43104,6 +43132,21 @@ var init_gemini = __esm(() => {
|
|
|
43104
43132
|
|
|
43105
43133
|
// src/core/layers/semantic.ts
|
|
43106
43134
|
async function semanticAnalysis(surface, structural, options = {}) {
|
|
43135
|
+
if (!hasGeminiKey()) {
|
|
43136
|
+
throw new Error(`Semantic analysis (deep depth) requires GEMINI_API_KEY.
|
|
43137
|
+
|
|
43138
|
+
` + `To set it up, add the env var to your MCP server config in ~/.mcp.json:
|
|
43139
|
+
|
|
43140
|
+
` + ` "codebase-analyzer": {
|
|
43141
|
+
` + ` "command": "npx",
|
|
43142
|
+
` + ` "args": ["-y", "codebase-analyzer-mcp"],
|
|
43143
|
+
` + ` "env": { "GEMINI_API_KEY": "your_key" }
|
|
43144
|
+
` + ` }
|
|
43145
|
+
|
|
43146
|
+
` + `Get a free key at https://aistudio.google.com/apikey
|
|
43147
|
+
|
|
43148
|
+
` + "Use --depth surface or --depth standard for free analysis without an API key.");
|
|
43149
|
+
}
|
|
43107
43150
|
const repoName = surface.repositoryMap.name;
|
|
43108
43151
|
const languages = surface.repositoryMap.languages.slice(0, 5).map((l) => `${l.language} (${l.percentage}%)`).join(", ");
|
|
43109
43152
|
const fileCount = surface.repositoryMap.fileCount;
|
|
@@ -43881,7 +43924,7 @@ var init_logger = __esm(() => {
|
|
|
43881
43924
|
});
|
|
43882
43925
|
|
|
43883
43926
|
// src/core/orchestrator.ts
|
|
43884
|
-
import { join as
|
|
43927
|
+
import { join as join3 } from "path";
|
|
43885
43928
|
import { readFile as readFile2 } from "fs/promises";
|
|
43886
43929
|
async function orchestrateAnalysis(repoPath, options = {}) {
|
|
43887
43930
|
const startTime = Date.now();
|
|
@@ -43962,7 +44005,7 @@ async function orchestrateAnalysis(repoPath, options = {}) {
|
|
|
43962
44005
|
});
|
|
43963
44006
|
const allFiles = [];
|
|
43964
44007
|
for (const module of modulesToAnalyze) {
|
|
43965
|
-
const moduleDir =
|
|
44008
|
+
const moduleDir = join3(repoPath, module.path);
|
|
43966
44009
|
}
|
|
43967
44010
|
structural = await analyzeModulesInParallel(repoPath, modulesToAnalyze, state);
|
|
43968
44011
|
logger.stopSpinner(`Structural: ${structural.length} modules analyzed`);
|
|
@@ -44053,7 +44096,7 @@ async function analyzeModulesInParallel(repoPath, modules, state) {
|
|
|
44053
44096
|
state.tasks.push(...batchTasks);
|
|
44054
44097
|
const fileContents = new Map;
|
|
44055
44098
|
await Promise.all(batch.map(async (module) => {
|
|
44056
|
-
const modulePath =
|
|
44099
|
+
const modulePath = join3(repoPath, module.path);
|
|
44057
44100
|
try {
|
|
44058
44101
|
const { glob: glob2 } = await Promise.resolve().then(() => (init_esm6(), exports_esm));
|
|
44059
44102
|
const files = await glob2("**/*", {
|
|
@@ -44063,8 +44106,8 @@ async function analyzeModulesInParallel(repoPath, modules, state) {
|
|
|
44063
44106
|
ignore: ["**/node_modules/**", "**/.git/**"]
|
|
44064
44107
|
});
|
|
44065
44108
|
for (const file of files.slice(0, 50)) {
|
|
44066
|
-
const filePath =
|
|
44067
|
-
const fullPath =
|
|
44109
|
+
const filePath = join3(module.path, file);
|
|
44110
|
+
const fullPath = join3(repoPath, filePath);
|
|
44068
44111
|
try {
|
|
44069
44112
|
const content = await readFile2(fullPath, "utf-8");
|
|
44070
44113
|
if (content.length < 1e5) {
|
|
@@ -44158,7 +44201,7 @@ var init_orchestrator = __esm(() => {
|
|
|
44158
44201
|
|
|
44159
44202
|
// src/core/repo-loader.ts
|
|
44160
44203
|
import { readFile as readFile3, stat as stat4 } from "node:fs/promises";
|
|
44161
|
-
import { join as
|
|
44204
|
+
import { join as join4 } from "node:path";
|
|
44162
44205
|
async function resolveSource(source) {
|
|
44163
44206
|
if (await isGitHubUrl(source)) {
|
|
44164
44207
|
const repoPath = await cloneGitHubRepo(source);
|
|
@@ -44185,7 +44228,7 @@ async function cloneGitHubRepo(url) {
|
|
|
44185
44228
|
const { execSync } = await import("node:child_process");
|
|
44186
44229
|
const { mkdtemp } = await import("node:fs/promises");
|
|
44187
44230
|
const { tmpdir } = await import("node:os");
|
|
44188
|
-
const tempDir = await mkdtemp(
|
|
44231
|
+
const tempDir = await mkdtemp(join4(tmpdir(), "cba-"));
|
|
44189
44232
|
const normalizedUrl = url.replace(/\.git$/, "").replace(/\/$/, "");
|
|
44190
44233
|
try {
|
|
44191
44234
|
execSync(`git clone --depth 1 ${normalizedUrl}.git ${tempDir}`, {
|
|
@@ -44208,7 +44251,7 @@ var package_default;
|
|
|
44208
44251
|
var init_package = __esm(() => {
|
|
44209
44252
|
package_default = {
|
|
44210
44253
|
name: "codebase-analyzer-mcp",
|
|
44211
|
-
version: "2.1.
|
|
44254
|
+
version: "2.1.1",
|
|
44212
44255
|
description: "Multi-layer codebase analysis with Gemini AI. MCP server + Claude plugin with progressive disclosure.",
|
|
44213
44256
|
type: "module",
|
|
44214
44257
|
main: "dist/mcp/server.js",
|
|
@@ -58152,9 +58195,24 @@ __export(exports_patterns, {
|
|
|
58152
58195
|
executeFindPatterns: () => executeFindPatterns,
|
|
58153
58196
|
DETECTABLE_PATTERNS: () => DETECTABLE_PATTERNS
|
|
58154
58197
|
});
|
|
58155
|
-
import { join as
|
|
58198
|
+
import { join as join5 } from "path";
|
|
58156
58199
|
import { readFile as readFile4 } from "fs/promises";
|
|
58157
58200
|
async function executeFindPatterns(input) {
|
|
58201
|
+
if (!hasGeminiKey()) {
|
|
58202
|
+
throw new Error(`find_patterns requires GEMINI_API_KEY for AI-powered pattern detection.
|
|
58203
|
+
|
|
58204
|
+
` + `To set it up, add the env var to your MCP server config in ~/.mcp.json:
|
|
58205
|
+
|
|
58206
|
+
` + ` "codebase-analyzer": {
|
|
58207
|
+
` + ` "command": "npx",
|
|
58208
|
+
` + ` "args": ["-y", "codebase-analyzer-mcp"],
|
|
58209
|
+
` + ` "env": { "GEMINI_API_KEY": "your_key" }
|
|
58210
|
+
` + ` }
|
|
58211
|
+
|
|
58212
|
+
` + `Get a free key at https://aistudio.google.com/apikey
|
|
58213
|
+
|
|
58214
|
+
` + "Alternatively, use analyze_repo (free, no API key needed) or query_repo (degrades gracefully without Gemini).");
|
|
58215
|
+
}
|
|
58158
58216
|
const { source, patternTypes } = input;
|
|
58159
58217
|
const { repoPath, cleanup } = await resolveSource(source);
|
|
58160
58218
|
try {
|
|
@@ -58166,7 +58224,7 @@ async function executeFindPatterns(input) {
|
|
|
58166
58224
|
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**"]
|
|
58167
58225
|
});
|
|
58168
58226
|
for (const file2 of keyFiles.slice(0, 50)) {
|
|
58169
|
-
const fullPath =
|
|
58227
|
+
const fullPath = join5(repoPath, file2);
|
|
58170
58228
|
try {
|
|
58171
58229
|
const content = await readFile4(fullPath, "utf-8");
|
|
58172
58230
|
if (content.length < 50000) {
|
|
@@ -58264,9 +58322,24 @@ __export(exports_dataflow, {
|
|
|
58264
58322
|
traceDataflowSchema: () => traceDataflowSchema,
|
|
58265
58323
|
executeTraceDataflow: () => executeTraceDataflow
|
|
58266
58324
|
});
|
|
58267
|
-
import { join as
|
|
58325
|
+
import { join as join6 } from "path";
|
|
58268
58326
|
import { readFile as readFile5 } from "fs/promises";
|
|
58269
58327
|
async function executeTraceDataflow(input) {
|
|
58328
|
+
if (!hasGeminiKey()) {
|
|
58329
|
+
throw new Error(`trace_dataflow requires GEMINI_API_KEY for AI-powered dataflow analysis.
|
|
58330
|
+
|
|
58331
|
+
` + `To set it up, add the env var to your MCP server config in ~/.mcp.json:
|
|
58332
|
+
|
|
58333
|
+
` + ` "codebase-analyzer": {
|
|
58334
|
+
` + ` "command": "npx",
|
|
58335
|
+
` + ` "args": ["-y", "codebase-analyzer-mcp"],
|
|
58336
|
+
` + ` "env": { "GEMINI_API_KEY": "your_key" }
|
|
58337
|
+
` + ` }
|
|
58338
|
+
|
|
58339
|
+
` + `Get a free key at https://aistudio.google.com/apikey
|
|
58340
|
+
|
|
58341
|
+
` + "Alternatively, use query_repo which degrades gracefully without Gemini.");
|
|
58342
|
+
}
|
|
58270
58343
|
const { source, from, to } = input;
|
|
58271
58344
|
const { repoPath, cleanup } = await resolveSource(source);
|
|
58272
58345
|
try {
|
|
@@ -58278,7 +58351,7 @@ async function executeTraceDataflow(input) {
|
|
|
58278
58351
|
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**", "**/*.test.*", "**/*.spec.*"]
|
|
58279
58352
|
});
|
|
58280
58353
|
for (const file2 of codeFiles.slice(0, 60)) {
|
|
58281
|
-
const fullPath =
|
|
58354
|
+
const fullPath = join6(repoPath, file2);
|
|
58282
58355
|
try {
|
|
58283
58356
|
const content = await readFile5(fullPath, "utf-8");
|
|
58284
58357
|
if (content.length < 50000) {
|
|
@@ -58377,7 +58450,7 @@ __export(exports_query, {
|
|
|
58377
58450
|
queryRepoSchema: () => queryRepoSchema,
|
|
58378
58451
|
executeQueryRepo: () => executeQueryRepo
|
|
58379
58452
|
});
|
|
58380
|
-
import { basename as basename4, join as
|
|
58453
|
+
import { basename as basename4, join as join7 } from "path";
|
|
58381
58454
|
import { readFile as readFile6 } from "fs/promises";
|
|
58382
58455
|
function extractSourceName(source) {
|
|
58383
58456
|
const githubMatch = source.match(/github\.com\/([^\/]+\/[^\/]+)/);
|
|
@@ -58470,7 +58543,7 @@ async function executeQueryRepo(input) {
|
|
|
58470
58543
|
for (const filePath of filesToRead) {
|
|
58471
58544
|
if (totalChars >= MAX_TOTAL_CHARS)
|
|
58472
58545
|
break;
|
|
58473
|
-
const fullPath =
|
|
58546
|
+
const fullPath = join7(repoPath, filePath);
|
|
58474
58547
|
try {
|
|
58475
58548
|
const content = await readFile6(fullPath, "utf-8");
|
|
58476
58549
|
const truncated = content.length > MAX_PER_FILE ? content.slice(0, MAX_PER_FILE) + `
|
|
@@ -73713,7 +73786,7 @@ var init_expand = __esm(() => {
|
|
|
73713
73786
|
|
|
73714
73787
|
// src/mcp/tools/read-files.ts
|
|
73715
73788
|
import { readFile as readFile7, stat as stat5 } from "fs/promises";
|
|
73716
|
-
import { join as
|
|
73789
|
+
import { join as join8, resolve, normalize as normalize2 } from "path";
|
|
73717
73790
|
async function executeReadFiles(input) {
|
|
73718
73791
|
const { analysisId, paths, maxLines = 500 } = input;
|
|
73719
73792
|
const cached2 = analysisCache.getByAnalysisId(analysisId);
|
|
@@ -73742,7 +73815,7 @@ async function executeReadFiles(input) {
|
|
|
73742
73815
|
if (normalized.startsWith("..") || normalized.startsWith("/")) {
|
|
73743
73816
|
return { path: filePath, error: "Invalid path: must be relative and within the repository" };
|
|
73744
73817
|
}
|
|
73745
|
-
const fullPath = resolve(
|
|
73818
|
+
const fullPath = resolve(join8(resolvedRepoPath, normalized));
|
|
73746
73819
|
if (!fullPath.startsWith(resolvedRepoPath)) {
|
|
73747
73820
|
return { path: filePath, error: "Invalid path: traversal outside repository" };
|
|
73748
73821
|
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -53283,7 +53283,7 @@ class StdioServerTransport {
|
|
|
53283
53283
|
}
|
|
53284
53284
|
|
|
53285
53285
|
// src/core/orchestrator.ts
|
|
53286
|
-
import { join as
|
|
53286
|
+
import { join as join3 } from "path";
|
|
53287
53287
|
import { readFile as readFile2 } from "fs/promises";
|
|
53288
53288
|
|
|
53289
53289
|
// src/core/layers/surface.ts
|
|
@@ -69556,12 +69556,40 @@ function getApiKeyFromEnv() {
|
|
|
69556
69556
|
}
|
|
69557
69557
|
|
|
69558
69558
|
// src/core/gemini.ts
|
|
69559
|
+
import { readFileSync } from "fs";
|
|
69560
|
+
import { join as join2 } from "path";
|
|
69561
|
+
import { homedir } from "os";
|
|
69559
69562
|
var client = null;
|
|
69563
|
+
function resolveApiKey() {
|
|
69564
|
+
if (process.env.GEMINI_API_KEY) {
|
|
69565
|
+
return process.env.GEMINI_API_KEY;
|
|
69566
|
+
}
|
|
69567
|
+
try {
|
|
69568
|
+
const configPath = join2(homedir(), ".config", "codebase-analyzer", "config.json");
|
|
69569
|
+
const config2 = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
69570
|
+
if (config2.geminiApiKey) {
|
|
69571
|
+
return config2.geminiApiKey;
|
|
69572
|
+
}
|
|
69573
|
+
} catch {}
|
|
69574
|
+
return;
|
|
69575
|
+
}
|
|
69576
|
+
function hasGeminiKey() {
|
|
69577
|
+
return !!resolveApiKey();
|
|
69578
|
+
}
|
|
69560
69579
|
function getClient() {
|
|
69561
69580
|
if (!client) {
|
|
69562
|
-
const apiKey =
|
|
69581
|
+
const apiKey = resolveApiKey();
|
|
69563
69582
|
if (!apiKey) {
|
|
69564
|
-
throw new Error(
|
|
69583
|
+
throw new Error(`GEMINI_API_KEY not found. Set it up using one of these methods:
|
|
69584
|
+
|
|
69585
|
+
` + `Option 1: Config file (recommended for plugin installs):
|
|
69586
|
+
` + ` mkdir -p ~/.config/codebase-analyzer
|
|
69587
|
+
` + ` echo '{"geminiApiKey":"your_key"}' > ~/.config/codebase-analyzer/config.json
|
|
69588
|
+
|
|
69589
|
+
` + `Option 2: MCP server env (for ~/.mcp.json installs):
|
|
69590
|
+
` + ` "env": { "GEMINI_API_KEY": "your_key" }
|
|
69591
|
+
|
|
69592
|
+
` + "Get a free key at https://aistudio.google.com/apikey");
|
|
69565
69593
|
}
|
|
69566
69594
|
client = new GoogleGenAI({ apiKey });
|
|
69567
69595
|
}
|
|
@@ -69731,6 +69759,21 @@ Focus on:
|
|
|
69731
69759
|
|
|
69732
69760
|
Be specific and reference actual file paths when possible.`;
|
|
69733
69761
|
async function semanticAnalysis(surface, structural, options = {}) {
|
|
69762
|
+
if (!hasGeminiKey()) {
|
|
69763
|
+
throw new Error(`Semantic analysis (deep depth) requires GEMINI_API_KEY.
|
|
69764
|
+
|
|
69765
|
+
` + `To set it up, add the env var to your MCP server config in ~/.mcp.json:
|
|
69766
|
+
|
|
69767
|
+
` + ` "codebase-analyzer": {
|
|
69768
|
+
` + ` "command": "npx",
|
|
69769
|
+
` + ` "args": ["-y", "codebase-analyzer-mcp"],
|
|
69770
|
+
` + ` "env": { "GEMINI_API_KEY": "your_key" }
|
|
69771
|
+
` + ` }
|
|
69772
|
+
|
|
69773
|
+
` + `Get a free key at https://aistudio.google.com/apikey
|
|
69774
|
+
|
|
69775
|
+
` + "Use --depth surface or --depth standard for free analysis without an API key.");
|
|
69776
|
+
}
|
|
69734
69777
|
const repoName = surface.repositoryMap.name;
|
|
69735
69778
|
const languages = surface.repositoryMap.languages.slice(0, 5).map((l) => `${l.language} (${l.percentage}%)`).join(", ");
|
|
69736
69779
|
const fileCount = surface.repositoryMap.fileCount;
|
|
@@ -70245,7 +70288,7 @@ async function orchestrateAnalysis(repoPath, options = {}) {
|
|
|
70245
70288
|
});
|
|
70246
70289
|
const allFiles = [];
|
|
70247
70290
|
for (const module of modulesToAnalyze) {
|
|
70248
|
-
const moduleDir =
|
|
70291
|
+
const moduleDir = join3(repoPath, module.path);
|
|
70249
70292
|
}
|
|
70250
70293
|
structural = await analyzeModulesInParallel(repoPath, modulesToAnalyze, state);
|
|
70251
70294
|
logger.stopSpinner(`Structural: ${structural.length} modules analyzed`);
|
|
@@ -70336,7 +70379,7 @@ async function analyzeModulesInParallel(repoPath, modules, state) {
|
|
|
70336
70379
|
state.tasks.push(...batchTasks);
|
|
70337
70380
|
const fileContents = new Map;
|
|
70338
70381
|
await Promise.all(batch.map(async (module) => {
|
|
70339
|
-
const modulePath =
|
|
70382
|
+
const modulePath = join3(repoPath, module.path);
|
|
70340
70383
|
try {
|
|
70341
70384
|
const { glob: glob2 } = await Promise.resolve().then(() => (init_esm6(), exports_esm));
|
|
70342
70385
|
const files = await glob2("**/*", {
|
|
@@ -70346,8 +70389,8 @@ async function analyzeModulesInParallel(repoPath, modules, state) {
|
|
|
70346
70389
|
ignore: ["**/node_modules/**", "**/.git/**"]
|
|
70347
70390
|
});
|
|
70348
70391
|
for (const file2 of files.slice(0, 50)) {
|
|
70349
|
-
const filePath =
|
|
70350
|
-
const fullPath =
|
|
70392
|
+
const filePath = join3(module.path, file2);
|
|
70393
|
+
const fullPath = join3(repoPath, filePath);
|
|
70351
70394
|
try {
|
|
70352
70395
|
const content = await readFile2(fullPath, "utf-8");
|
|
70353
70396
|
if (content.length < 1e5) {
|
|
@@ -70504,7 +70547,7 @@ import { basename as basename4 } from "path";
|
|
|
70504
70547
|
// src/core/repo-loader.ts
|
|
70505
70548
|
init_esm6();
|
|
70506
70549
|
import { readFile as readFile3, stat as stat4 } from "node:fs/promises";
|
|
70507
|
-
import { join as
|
|
70550
|
+
import { join as join4 } from "node:path";
|
|
70508
70551
|
async function resolveSource(source) {
|
|
70509
70552
|
if (await isGitHubUrl(source)) {
|
|
70510
70553
|
const repoPath = await cloneGitHubRepo(source);
|
|
@@ -70531,7 +70574,7 @@ async function cloneGitHubRepo(url2) {
|
|
|
70531
70574
|
const { execSync } = await import("node:child_process");
|
|
70532
70575
|
const { mkdtemp } = await import("node:fs/promises");
|
|
70533
70576
|
const { tmpdir } = await import("node:os");
|
|
70534
|
-
const tempDir = await mkdtemp(
|
|
70577
|
+
const tempDir = await mkdtemp(join4(tmpdir(), "cba-"));
|
|
70535
70578
|
const normalizedUrl = url2.replace(/\.git$/, "").replace(/\/$/, "");
|
|
70536
70579
|
try {
|
|
70537
70580
|
execSync(`git clone --depth 1 ${normalizedUrl}.git ${tempDir}`, {
|
|
@@ -70614,7 +70657,7 @@ async function executeExpandSection(input) {
|
|
|
70614
70657
|
}
|
|
70615
70658
|
// src/mcp/tools/patterns.ts
|
|
70616
70659
|
init_esm6();
|
|
70617
|
-
import { join as
|
|
70660
|
+
import { join as join5 } from "path";
|
|
70618
70661
|
import { readFile as readFile4 } from "fs/promises";
|
|
70619
70662
|
var DETECTABLE_PATTERNS = [
|
|
70620
70663
|
"singleton",
|
|
@@ -70641,6 +70684,21 @@ var findPatternsSchema = {
|
|
|
70641
70684
|
patternTypes: exports_external.array(exports_external.string()).optional().describe(`Optional: specific patterns to look for. Available: ${DETECTABLE_PATTERNS.join(", ")}`)
|
|
70642
70685
|
};
|
|
70643
70686
|
async function executeFindPatterns(input) {
|
|
70687
|
+
if (!hasGeminiKey()) {
|
|
70688
|
+
throw new Error(`find_patterns requires GEMINI_API_KEY for AI-powered pattern detection.
|
|
70689
|
+
|
|
70690
|
+
` + `To set it up, add the env var to your MCP server config in ~/.mcp.json:
|
|
70691
|
+
|
|
70692
|
+
` + ` "codebase-analyzer": {
|
|
70693
|
+
` + ` "command": "npx",
|
|
70694
|
+
` + ` "args": ["-y", "codebase-analyzer-mcp"],
|
|
70695
|
+
` + ` "env": { "GEMINI_API_KEY": "your_key" }
|
|
70696
|
+
` + ` }
|
|
70697
|
+
|
|
70698
|
+
` + `Get a free key at https://aistudio.google.com/apikey
|
|
70699
|
+
|
|
70700
|
+
` + "Alternatively, use analyze_repo (free, no API key needed) or query_repo (degrades gracefully without Gemini).");
|
|
70701
|
+
}
|
|
70644
70702
|
const { source, patternTypes } = input;
|
|
70645
70703
|
const { repoPath, cleanup } = await resolveSource(source);
|
|
70646
70704
|
try {
|
|
@@ -70652,7 +70710,7 @@ async function executeFindPatterns(input) {
|
|
|
70652
70710
|
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**"]
|
|
70653
70711
|
});
|
|
70654
70712
|
for (const file2 of keyFiles.slice(0, 50)) {
|
|
70655
|
-
const fullPath =
|
|
70713
|
+
const fullPath = join5(repoPath, file2);
|
|
70656
70714
|
try {
|
|
70657
70715
|
const content = await readFile4(fullPath, "utf-8");
|
|
70658
70716
|
if (content.length < 50000) {
|
|
@@ -70713,7 +70771,7 @@ Respond with this exact JSON structure:
|
|
|
70713
70771
|
}
|
|
70714
70772
|
// src/mcp/tools/dataflow.ts
|
|
70715
70773
|
init_esm6();
|
|
70716
|
-
import { join as
|
|
70774
|
+
import { join as join6 } from "path";
|
|
70717
70775
|
import { readFile as readFile5 } from "fs/promises";
|
|
70718
70776
|
var traceDataflowSchema = {
|
|
70719
70777
|
source: exports_external.string().describe("Local path or GitHub URL to the repository"),
|
|
@@ -70721,6 +70779,21 @@ var traceDataflowSchema = {
|
|
|
70721
70779
|
to: exports_external.string().optional().describe("Optional: destination to trace to (if known)")
|
|
70722
70780
|
};
|
|
70723
70781
|
async function executeTraceDataflow(input) {
|
|
70782
|
+
if (!hasGeminiKey()) {
|
|
70783
|
+
throw new Error(`trace_dataflow requires GEMINI_API_KEY for AI-powered dataflow analysis.
|
|
70784
|
+
|
|
70785
|
+
` + `To set it up, add the env var to your MCP server config in ~/.mcp.json:
|
|
70786
|
+
|
|
70787
|
+
` + ` "codebase-analyzer": {
|
|
70788
|
+
` + ` "command": "npx",
|
|
70789
|
+
` + ` "args": ["-y", "codebase-analyzer-mcp"],
|
|
70790
|
+
` + ` "env": { "GEMINI_API_KEY": "your_key" }
|
|
70791
|
+
` + ` }
|
|
70792
|
+
|
|
70793
|
+
` + `Get a free key at https://aistudio.google.com/apikey
|
|
70794
|
+
|
|
70795
|
+
` + "Alternatively, use query_repo which degrades gracefully without Gemini.");
|
|
70796
|
+
}
|
|
70724
70797
|
const { source, from, to } = input;
|
|
70725
70798
|
const { repoPath, cleanup } = await resolveSource(source);
|
|
70726
70799
|
try {
|
|
@@ -70732,7 +70805,7 @@ async function executeTraceDataflow(input) {
|
|
|
70732
70805
|
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**", "**/*.test.*", "**/*.spec.*"]
|
|
70733
70806
|
});
|
|
70734
70807
|
for (const file2 of codeFiles.slice(0, 60)) {
|
|
70735
|
-
const fullPath =
|
|
70808
|
+
const fullPath = join6(repoPath, file2);
|
|
70736
70809
|
try {
|
|
70737
70810
|
const content = await readFile5(fullPath, "utf-8");
|
|
70738
70811
|
if (content.length < 50000) {
|
|
@@ -70813,7 +70886,7 @@ If you cannot find the entry point, explain what you looked for in the summary a
|
|
|
70813
70886
|
}
|
|
70814
70887
|
// src/mcp/tools/read-files.ts
|
|
70815
70888
|
import { readFile as readFile6, stat as stat5 } from "fs/promises";
|
|
70816
|
-
import { join as
|
|
70889
|
+
import { join as join7, resolve, normalize as normalize2 } from "path";
|
|
70817
70890
|
var readFilesSchema = {
|
|
70818
70891
|
analysisId: exports_external.string().describe("The analysisId from a previous analyze_repo result"),
|
|
70819
70892
|
paths: exports_external.array(exports_external.string()).min(1).max(20).describe("Relative file paths from the repository (max 20)"),
|
|
@@ -70847,7 +70920,7 @@ async function executeReadFiles(input) {
|
|
|
70847
70920
|
if (normalized.startsWith("..") || normalized.startsWith("/")) {
|
|
70848
70921
|
return { path: filePath, error: "Invalid path: must be relative and within the repository" };
|
|
70849
70922
|
}
|
|
70850
|
-
const fullPath = resolve(
|
|
70923
|
+
const fullPath = resolve(join7(resolvedRepoPath, normalized));
|
|
70851
70924
|
if (!fullPath.startsWith(resolvedRepoPath)) {
|
|
70852
70925
|
return { path: filePath, error: "Invalid path: traversal outside repository" };
|
|
70853
70926
|
}
|
|
@@ -70874,7 +70947,7 @@ async function executeReadFiles(input) {
|
|
|
70874
70947
|
};
|
|
70875
70948
|
}
|
|
70876
70949
|
// src/mcp/tools/query.ts
|
|
70877
|
-
import { basename as basename5, join as
|
|
70950
|
+
import { basename as basename5, join as join8 } from "path";
|
|
70878
70951
|
import { readFile as readFile7 } from "fs/promises";
|
|
70879
70952
|
var queryRepoSchema = {
|
|
70880
70953
|
source: exports_external.string().describe("Local path or GitHub URL to the repository"),
|
|
@@ -70971,7 +71044,7 @@ async function executeQueryRepo(input) {
|
|
|
70971
71044
|
for (const filePath of filesToRead) {
|
|
70972
71045
|
if (totalChars >= MAX_TOTAL_CHARS)
|
|
70973
71046
|
break;
|
|
70974
|
-
const fullPath =
|
|
71047
|
+
const fullPath = join8(repoPath, filePath);
|
|
70975
71048
|
try {
|
|
70976
71049
|
const content = await readFile7(fullPath, "utf-8");
|
|
70977
71050
|
const truncated = content.length > MAX_PER_FILE ? content.slice(0, MAX_PER_FILE) + `
|
|
@@ -71063,7 +71136,7 @@ function buildFallbackAnswer(question, analysisId, cached2, scored, fileContents
|
|
|
71063
71136
|
// package.json
|
|
71064
71137
|
var package_default = {
|
|
71065
71138
|
name: "codebase-analyzer-mcp",
|
|
71066
|
-
version: "2.1.
|
|
71139
|
+
version: "2.1.1",
|
|
71067
71140
|
description: "Multi-layer codebase analysis with Gemini AI. MCP server + Claude plugin with progressive disclosure.",
|
|
71068
71141
|
type: "module",
|
|
71069
71142
|
main: "dist/mcp/server.js",
|
package/package.json
CHANGED