dep-oracle 1.1.4 → 1.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/README.md +26 -1
- package/action/action.yml +1 -1
- package/dist/action/index.js +11325 -0
- package/dist/{chunk-7WUFMWUI.js → chunk-TXSNFX3N.js} +46 -10
- package/dist/chunk-TXSNFX3N.js.map +1 -0
- package/dist/cli/index.js +46 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +96 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +39 -28
- package/dist/mcp/server.js.map +1 -1
- package/package.json +2 -1
- package/server.json +55 -0
- package/dist/chunk-7WUFMWUI.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
isVerbose,
|
|
30
30
|
logger,
|
|
31
31
|
setVerbose
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-TXSNFX3N.js";
|
|
33
33
|
|
|
34
34
|
// src/analyzers/trend-predictor.ts
|
|
35
35
|
var HIGH_CONFIDENCE_THRESHOLD = 3;
|
|
@@ -209,8 +209,8 @@ var RateLimiter = class {
|
|
|
209
209
|
this.tokens--;
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
212
|
-
return new Promise((
|
|
213
|
-
this.waitQueue.push(
|
|
212
|
+
return new Promise((resolve2) => {
|
|
213
|
+
this.waitQueue.push(resolve2);
|
|
214
214
|
this.scheduleRefill();
|
|
215
215
|
});
|
|
216
216
|
}
|
|
@@ -254,8 +254,8 @@ var RateLimiter = class {
|
|
|
254
254
|
drainWaitQueue() {
|
|
255
255
|
while (this.waitQueue.length > 0 && this.tokens > 0) {
|
|
256
256
|
this.tokens--;
|
|
257
|
-
const
|
|
258
|
-
|
|
257
|
+
const resolve2 = this.waitQueue.shift();
|
|
258
|
+
resolve2?.();
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
};
|
|
@@ -284,6 +284,95 @@ async function loadConfig(overrides) {
|
|
|
284
284
|
}
|
|
285
285
|
return ConfigSchema.parse(merged);
|
|
286
286
|
}
|
|
287
|
+
|
|
288
|
+
// src/index.ts
|
|
289
|
+
import { resolve } from "path";
|
|
290
|
+
async function detectProject(dir) {
|
|
291
|
+
const parsers = [new NpmParser(), new PythonParser()];
|
|
292
|
+
for (const parser of parsers) {
|
|
293
|
+
if (await parser.detect(dir)) return parser.parse(dir);
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
async function scan(options) {
|
|
298
|
+
const dir = resolve(options?.dir ?? process.cwd());
|
|
299
|
+
const cache = new CacheManager();
|
|
300
|
+
const orchestrator = new CollectorOrchestrator(cache, {
|
|
301
|
+
githubToken: options?.githubToken,
|
|
302
|
+
offline: options?.offline
|
|
303
|
+
});
|
|
304
|
+
const trustEngine = new TrustScoreEngine();
|
|
305
|
+
const zombieDetector = new ZombieDetector();
|
|
306
|
+
const migrationAdvisor = new MigrationAdvisor();
|
|
307
|
+
const typosquatDetector = new TyposquatDetector();
|
|
308
|
+
const tree = await detectProject(dir);
|
|
309
|
+
if (!tree) {
|
|
310
|
+
return {
|
|
311
|
+
tree: { root: dir, manifest: "", nodes: /* @__PURE__ */ new Map(), totalDirect: 0, totalTransitive: 0 },
|
|
312
|
+
reports: [],
|
|
313
|
+
overallScore: 0,
|
|
314
|
+
summary: "No supported manifest file found."
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const importGraph = await buildImportGraph(dir);
|
|
318
|
+
const directNodes = Array.from(tree.nodes.values()).filter((n) => n.isDirect);
|
|
319
|
+
const reports = [];
|
|
320
|
+
for (const node of directNodes) {
|
|
321
|
+
const blastRadius = getBlastRadius(node.name, importGraph);
|
|
322
|
+
const results = await orchestrator.collectAll(node.name, node.version);
|
|
323
|
+
const trustResult = trustEngine.calculate(results);
|
|
324
|
+
const zombie = zombieDetector.detect(results.registry.data, results.github.data);
|
|
325
|
+
const typosquat = typosquatDetector.check(node.name);
|
|
326
|
+
const alternatives = migrationAdvisor.suggest(node.name, zombie.isZombie ? "zombie dependency" : "low trust score").map((a) => a.alternative);
|
|
327
|
+
const trend = results.popularity.data?.trend ?? "stable";
|
|
328
|
+
reports.push({
|
|
329
|
+
package: node.name,
|
|
330
|
+
version: node.version,
|
|
331
|
+
trustScore: trustResult.trustScore,
|
|
332
|
+
metrics: trustResult.metrics,
|
|
333
|
+
isZombie: zombie.isZombie,
|
|
334
|
+
blastRadius,
|
|
335
|
+
alternatives,
|
|
336
|
+
trend,
|
|
337
|
+
typosquatRisk: typosquat.isRisky ? 1 : 0
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
const overallScore = reports.length > 0 ? Math.round(reports.reduce((sum, r) => sum + r.trustScore, 0) / reports.length) : 0;
|
|
341
|
+
return {
|
|
342
|
+
tree,
|
|
343
|
+
reports,
|
|
344
|
+
overallScore,
|
|
345
|
+
summary: `Scanned ${reports.length} direct dependencies. Overall trust score: ${overallScore}/100. ${reports.filter((r) => r.isZombie).length} zombie(s) detected.`
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
async function checkPackage(packageName, version, options) {
|
|
349
|
+
const cache = new CacheManager();
|
|
350
|
+
const orchestrator = new CollectorOrchestrator(cache, {
|
|
351
|
+
githubToken: options?.githubToken,
|
|
352
|
+
offline: options?.offline
|
|
353
|
+
});
|
|
354
|
+
const trustEngine = new TrustScoreEngine();
|
|
355
|
+
const zombieDetector = new ZombieDetector();
|
|
356
|
+
const migrationAdvisor = new MigrationAdvisor();
|
|
357
|
+
const typosquatDetector = new TyposquatDetector();
|
|
358
|
+
const results = await orchestrator.collectAll(packageName, version ?? "latest");
|
|
359
|
+
const trustResult = trustEngine.calculate(results);
|
|
360
|
+
const zombie = zombieDetector.detect(results.registry.data, results.github.data);
|
|
361
|
+
const typosquat = typosquatDetector.check(packageName);
|
|
362
|
+
const alternatives = migrationAdvisor.suggest(packageName, zombie.isZombie ? "zombie dependency" : "low trust score").map((a) => a.alternative);
|
|
363
|
+
const trend = results.popularity.data?.trend ?? "stable";
|
|
364
|
+
return {
|
|
365
|
+
package: packageName,
|
|
366
|
+
version: version ?? "latest",
|
|
367
|
+
trustScore: trustResult.trustScore,
|
|
368
|
+
metrics: trustResult.metrics,
|
|
369
|
+
isZombie: zombie.isZombie,
|
|
370
|
+
blastRadius: 0,
|
|
371
|
+
alternatives,
|
|
372
|
+
trend,
|
|
373
|
+
typosquatRisk: typosquat.isRisky ? 1 : 0
|
|
374
|
+
};
|
|
375
|
+
}
|
|
287
376
|
export {
|
|
288
377
|
BaseParser,
|
|
289
378
|
BlastRadiusCalculator,
|
|
@@ -304,6 +393,7 @@ export {
|
|
|
304
393
|
TyposquatDetector,
|
|
305
394
|
ZombieDetector,
|
|
306
395
|
buildImportGraph,
|
|
396
|
+
checkPackage,
|
|
307
397
|
collectorCached,
|
|
308
398
|
collectorError,
|
|
309
399
|
collectorOffline,
|
|
@@ -320,6 +410,7 @@ export {
|
|
|
320
410
|
logger,
|
|
321
411
|
npmRateLimiter,
|
|
322
412
|
pypiRateLimiter,
|
|
413
|
+
scan,
|
|
323
414
|
setVerbose
|
|
324
415
|
};
|
|
325
416
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/analyzers/trend-predictor.ts","../src/utils/rate-limiter.ts","../src/cli/config.ts"],"sourcesContent":["import type {\n RegistryData,\n PopularityData,\n GitHubData,\n} from '../parsers/schema.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type TrendDirection = 'rising' | 'stable' | 'declining' | 'unknown';\n\nexport interface TrendResult {\n /** Overall trend direction. */\n trend: TrendDirection;\n /** Confidence in the prediction (0-1). */\n confidence: number;\n /**\n * Estimated trust score change over the next 3 months.\n * Negative values indicate a projected decline (e.g. -5 means the score\n * is expected to drop by roughly 5 points).\n */\n riskProjection3m: number;\n /** Human-readable explanation of the trend assessment. */\n reason: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Minimum signals required for a high-confidence prediction. */\nconst HIGH_CONFIDENCE_THRESHOLD = 3;\n\n// ---------------------------------------------------------------------------\n// TrendPredictor\n// ---------------------------------------------------------------------------\n\nexport class TrendPredictor {\n /**\n * Predict the trajectory of a package based on registry, popularity,\n * and GitHub signals.\n */\n predict(\n registry: RegistryData | null,\n popularity: PopularityData | null,\n github: GitHubData | null,\n ): TrendResult {\n const signals: TrendSignal[] = [];\n const reasons: string[] = [];\n\n // -----------------------------------------------------------------------\n // Signal 1: Download trend from PopularityData\n // -----------------------------------------------------------------------\n if (popularity !== null) {\n switch (popularity.trend) {\n case 'rising':\n signals.push({ direction: 'rising', weight: 0.4 });\n reasons.push('Downloads are trending upward');\n break;\n case 'declining':\n signals.push({ direction: 'declining', weight: 0.4 });\n reasons.push('Downloads are trending downward');\n break;\n case 'stable':\n signals.push({ direction: 'stable', weight: 0.3 });\n reasons.push('Downloads are stable');\n break;\n }\n }\n\n // -----------------------------------------------------------------------\n // Signal 2: Commit frequency from GitHub\n // -----------------------------------------------------------------------\n if (github !== null) {\n const commits30d = github.recentCommitCount;\n\n if (commits30d >= 30) {\n signals.push({ direction: 'rising', weight: 0.3 });\n reasons.push(`High commit activity (${commits30d} commits in 30 days)`);\n } else if (commits30d >= 10) {\n signals.push({ direction: 'stable', weight: 0.2 });\n reasons.push(`Moderate commit activity (${commits30d} commits in 30 days)`);\n } else if (commits30d >= 1) {\n signals.push({ direction: 'stable', weight: 0.15 });\n reasons.push(`Low commit activity (${commits30d} commits in 30 days)`);\n } else {\n signals.push({ direction: 'declining', weight: 0.3 });\n reasons.push('No commits in the last 30 days');\n }\n }\n\n // -----------------------------------------------------------------------\n // Signal 3: Version release cadence from RegistryData\n // -----------------------------------------------------------------------\n if (registry !== null) {\n if (registry.lastPublishDate !== null) {\n const lastPublish = new Date(registry.lastPublishDate);\n const monthsSincePublish = monthsBetween(lastPublish, new Date());\n\n if (monthsSincePublish < 2) {\n signals.push({ direction: 'rising', weight: 0.2 });\n reasons.push('Recent version published within last 2 months');\n } else if (monthsSincePublish < 6) {\n signals.push({ direction: 'stable', weight: 0.15 });\n reasons.push(`Last publish ${Math.round(monthsSincePublish)} months ago`);\n } else if (monthsSincePublish < 12) {\n signals.push({ direction: 'declining', weight: 0.2 });\n reasons.push(`No new version in ${Math.round(monthsSincePublish)} months`);\n } else {\n signals.push({ direction: 'declining', weight: 0.3 });\n reasons.push(`No new version in ${Math.round(monthsSincePublish)} months — possibly abandoned`);\n }\n }\n\n // Version count as a maturity signal\n if (registry.versionCount > 50) {\n signals.push({ direction: 'stable', weight: 0.1 });\n reasons.push(`Mature package with ${registry.versionCount} versions`);\n }\n\n // Deprecated flag is a strong declining signal (string | null)\n if (registry.deprecated !== null) {\n signals.push({ direction: 'declining', weight: 0.5 });\n reasons.push('Package is marked as deprecated');\n }\n }\n\n // -----------------------------------------------------------------------\n // Signal 4: Star/fork ratio as community interest (GitHub)\n // -----------------------------------------------------------------------\n if (github !== null && github.stars > 0) {\n const forkRatio = github.forks / github.stars;\n if (forkRatio > 0.3) {\n // High fork ratio suggests active community interest\n signals.push({ direction: 'rising', weight: 0.1 });\n reasons.push('High fork-to-star ratio indicates active community');\n }\n }\n\n // -----------------------------------------------------------------------\n // Aggregate signals\n // -----------------------------------------------------------------------\n if (signals.length === 0) {\n return {\n trend: 'unknown',\n confidence: 0,\n riskProjection3m: 0,\n reason: 'Insufficient data to determine trend',\n };\n }\n\n const { trend, confidence } = this.aggregateSignals(signals);\n const riskProjection3m = this.calculateRiskProjection(trend, confidence, signals);\n\n return {\n trend,\n confidence: Math.round(confidence * 100) / 100,\n riskProjection3m: Math.round(riskProjection3m),\n reason: reasons.join('. ') + '.',\n };\n }\n\n // -------------------------------------------------------------------------\n // Internal helpers\n // -------------------------------------------------------------------------\n\n /**\n * Aggregate multiple trend signals into a single direction and confidence.\n * Uses weighted voting: each signal votes for its direction with its weight.\n */\n private aggregateSignals(signals: TrendSignal[]): {\n trend: TrendDirection;\n confidence: number;\n } {\n const votes: Record<Exclude<TrendDirection, 'unknown'>, number> = {\n rising: 0,\n stable: 0,\n declining: 0,\n };\n\n let totalWeight = 0;\n\n for (const signal of signals) {\n votes[signal.direction] += signal.weight;\n totalWeight += signal.weight;\n }\n\n // Find the winning direction\n let maxVote = 0;\n let trend: TrendDirection = 'stable';\n\n for (const [direction, vote] of Object.entries(votes)) {\n if (vote > maxVote) {\n maxVote = vote;\n trend = direction as TrendDirection;\n }\n }\n\n // Confidence is based on:\n // 1. How dominant the winning direction is (proportion of total weight)\n // 2. How many signals we have (more signals = higher confidence)\n const dominance = totalWeight > 0 ? maxVote / totalWeight : 0;\n const signalCountFactor = Math.min(signals.length / HIGH_CONFIDENCE_THRESHOLD, 1);\n const confidence = Math.min(dominance * signalCountFactor, 1);\n\n return { trend, confidence };\n }\n\n /**\n * Estimate how much the trust score might change in 3 months based on\n * the trend and confidence.\n */\n private calculateRiskProjection(\n trend: TrendDirection,\n confidence: number,\n signals: TrendSignal[],\n ): number {\n // Base projection ranges\n const projectionMap: Record<TrendDirection, number> = {\n rising: 5,\n stable: 0,\n declining: -10,\n unknown: 0,\n };\n\n let base = projectionMap[trend];\n\n // Scale by confidence\n base *= confidence;\n\n // Strong negative signals amplify the projection\n const decliningWeight = signals\n .filter((s) => s.direction === 'declining')\n .reduce((sum, s) => sum + s.weight, 0);\n\n if (decliningWeight > 0.5) {\n // Multiple strong declining signals — amplify the negative projection\n base -= Math.round(decliningWeight * 5);\n }\n\n // Clamp to reasonable range\n return Math.max(-20, Math.min(10, base));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\ninterface TrendSignal {\n direction: 'rising' | 'stable' | 'declining';\n weight: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction monthsBetween(from: Date, to: Date): number {\n const years = to.getFullYear() - from.getFullYear();\n const months = to.getMonth() - from.getMonth();\n const days = to.getDate() - from.getDate();\n return years * 12 + months + (days < 0 ? -0.5 : 0);\n}\n","/**\n * Token-bucket rate limiter for controlling outbound HTTP request frequency.\n *\n * Usage:\n * ```ts\n * const limiter = new RateLimiter(10, 60_000); // 10 requests per minute\n * await limiter.acquire(); // blocks if bucket is empty\n * await fetch(url);\n * ```\n */\nexport class RateLimiter {\n private tokens: number;\n private readonly maxTokens: number;\n private readonly refillIntervalMs: number;\n private lastRefill: number;\n private waitQueue: Array<() => void> = [];\n\n /**\n * @param maxRequests Maximum number of requests allowed in the window\n * @param windowMs Window duration in milliseconds\n */\n constructor(maxRequests: number, windowMs: number) {\n this.maxTokens = maxRequests;\n this.tokens = maxRequests;\n this.refillIntervalMs = windowMs;\n this.lastRefill = Date.now();\n }\n\n /**\n * Acquire a token. Resolves immediately when tokens are available,\n * otherwise waits until the bucket is refilled.\n */\n async acquire(): Promise<void> {\n this.refill();\n\n if (this.tokens > 0) {\n this.tokens--;\n return;\n }\n\n // No tokens available — wait for the next refill cycle\n return new Promise<void>((resolve) => {\n this.waitQueue.push(resolve);\n this.scheduleRefill();\n });\n }\n\n /**\n * Return the number of tokens currently available (without waiting).\n */\n get remaining(): number {\n this.refill();\n return this.tokens;\n }\n\n /**\n * Return the number of milliseconds until the next refill.\n */\n get msUntilRefill(): number {\n const elapsed = Date.now() - this.lastRefill;\n return Math.max(0, this.refillIntervalMs - elapsed);\n }\n\n // -----------------------------------------------------------------------\n // Internal\n // -----------------------------------------------------------------------\n\n private refill(): void {\n const now = Date.now();\n const elapsed = now - this.lastRefill;\n\n if (elapsed >= this.refillIntervalMs) {\n // Full refill\n const periods = Math.floor(elapsed / this.refillIntervalMs);\n this.tokens = Math.min(this.maxTokens, this.tokens + periods * this.maxTokens);\n this.lastRefill = now - (elapsed % this.refillIntervalMs);\n this.drainWaitQueue();\n }\n }\n\n private scheduleRefill(): void {\n const delay = this.msUntilRefill;\n if (delay <= 0) {\n this.refill();\n return;\n }\n\n setTimeout(() => {\n this.refill();\n }, delay);\n }\n\n private drainWaitQueue(): void {\n while (this.waitQueue.length > 0 && this.tokens > 0) {\n this.tokens--;\n const resolve = this.waitQueue.shift();\n resolve?.();\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Pre-configured limiters\n// ---------------------------------------------------------------------------\n\n/**\n * GitHub API rate limiter: 5000 requests per hour (authenticated).\n *\n * GitHub's unauthenticated limit is 60/hr, but dep-oracle is designed to\n * work with a token so we use the authenticated limit.\n */\nexport const githubRateLimiter = new RateLimiter(5000, 3_600_000);\n\n/**\n * npm registry rate limiter: generous default of 300 requests per minute.\n * The npm registry does not publish official limits, but this is safe.\n */\nexport const npmRateLimiter = new RateLimiter(300, 60_000);\n\n/**\n * PyPI rate limiter: conservative 100 requests per minute.\n */\nexport const pypiRateLimiter = new RateLimiter(100, 60_000);\n","/**\n * Configuration loader for dep-oracle.\n *\n * Uses cosmiconfig to search for configuration in:\n * - package.json \"dep-oracle\" field\n * - .dep-oraclerc / .dep-oraclerc.json / .dep-oraclerc.yaml / .dep-oraclerc.yml\n * - dep-oracle.config.js / dep-oracle.config.cjs / dep-oracle.config.mjs\n *\n * Merge order: defaults < file config < environment variables < CLI overrides.\n */\n\nimport { cosmiconfig } from 'cosmiconfig';\nimport { ConfigSchema, type Config } from '../parsers/schema.js';\n\nconst explorer = cosmiconfig('dep-oracle');\n\n/**\n * Load and validate project configuration.\n *\n * @param overrides - Partial config values from CLI flags or programmatic use.\n * These take highest precedence.\n * @returns Fully validated Config object with defaults applied.\n */\nexport async function loadConfig(overrides?: Partial<Config>): Promise<Config> {\n let fileConfig: Record<string, unknown> = {};\n\n try {\n const result = await explorer.search();\n if (result?.config && typeof result.config === 'object') {\n fileConfig = result.config as Record<string, unknown>;\n }\n } catch {\n // Config file is optional -- continue with defaults if search fails\n }\n\n // Merge: file config < CLI overrides\n const merged: Record<string, unknown> = { ...fileConfig, ...overrides };\n\n // Read GitHub token from environment variables when not set by file or override\n if (!merged.githubToken && process.env.GITHUB_TOKEN) {\n merged.githubToken = process.env.GITHUB_TOKEN;\n }\n if (!merged.githubToken && process.env.DEP_ORACLE_GITHUB_TOKEN) {\n merged.githubToken = process.env.DEP_ORACLE_GITHUB_TOKEN;\n }\n\n return ConfigSchema.parse(merged);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/analyzers/trend-predictor.ts","../src/utils/rate-limiter.ts","../src/cli/config.ts","../src/index.ts"],"sourcesContent":["import type {\n RegistryData,\n PopularityData,\n GitHubData,\n} from '../parsers/schema.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type TrendDirection = 'rising' | 'stable' | 'declining' | 'unknown';\n\nexport interface TrendResult {\n /** Overall trend direction. */\n trend: TrendDirection;\n /** Confidence in the prediction (0-1). */\n confidence: number;\n /**\n * Estimated trust score change over the next 3 months.\n * Negative values indicate a projected decline (e.g. -5 means the score\n * is expected to drop by roughly 5 points).\n */\n riskProjection3m: number;\n /** Human-readable explanation of the trend assessment. */\n reason: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Minimum signals required for a high-confidence prediction. */\nconst HIGH_CONFIDENCE_THRESHOLD = 3;\n\n// ---------------------------------------------------------------------------\n// TrendPredictor\n// ---------------------------------------------------------------------------\n\nexport class TrendPredictor {\n /**\n * Predict the trajectory of a package based on registry, popularity,\n * and GitHub signals.\n */\n predict(\n registry: RegistryData | null,\n popularity: PopularityData | null,\n github: GitHubData | null,\n ): TrendResult {\n const signals: TrendSignal[] = [];\n const reasons: string[] = [];\n\n // -----------------------------------------------------------------------\n // Signal 1: Download trend from PopularityData\n // -----------------------------------------------------------------------\n if (popularity !== null) {\n switch (popularity.trend) {\n case 'rising':\n signals.push({ direction: 'rising', weight: 0.4 });\n reasons.push('Downloads are trending upward');\n break;\n case 'declining':\n signals.push({ direction: 'declining', weight: 0.4 });\n reasons.push('Downloads are trending downward');\n break;\n case 'stable':\n signals.push({ direction: 'stable', weight: 0.3 });\n reasons.push('Downloads are stable');\n break;\n }\n }\n\n // -----------------------------------------------------------------------\n // Signal 2: Commit frequency from GitHub\n // -----------------------------------------------------------------------\n if (github !== null) {\n const commits30d = github.recentCommitCount;\n\n if (commits30d >= 30) {\n signals.push({ direction: 'rising', weight: 0.3 });\n reasons.push(`High commit activity (${commits30d} commits in 30 days)`);\n } else if (commits30d >= 10) {\n signals.push({ direction: 'stable', weight: 0.2 });\n reasons.push(`Moderate commit activity (${commits30d} commits in 30 days)`);\n } else if (commits30d >= 1) {\n signals.push({ direction: 'stable', weight: 0.15 });\n reasons.push(`Low commit activity (${commits30d} commits in 30 days)`);\n } else {\n signals.push({ direction: 'declining', weight: 0.3 });\n reasons.push('No commits in the last 30 days');\n }\n }\n\n // -----------------------------------------------------------------------\n // Signal 3: Version release cadence from RegistryData\n // -----------------------------------------------------------------------\n if (registry !== null) {\n if (registry.lastPublishDate !== null) {\n const lastPublish = new Date(registry.lastPublishDate);\n const monthsSincePublish = monthsBetween(lastPublish, new Date());\n\n if (monthsSincePublish < 2) {\n signals.push({ direction: 'rising', weight: 0.2 });\n reasons.push('Recent version published within last 2 months');\n } else if (monthsSincePublish < 6) {\n signals.push({ direction: 'stable', weight: 0.15 });\n reasons.push(`Last publish ${Math.round(monthsSincePublish)} months ago`);\n } else if (monthsSincePublish < 12) {\n signals.push({ direction: 'declining', weight: 0.2 });\n reasons.push(`No new version in ${Math.round(monthsSincePublish)} months`);\n } else {\n signals.push({ direction: 'declining', weight: 0.3 });\n reasons.push(`No new version in ${Math.round(monthsSincePublish)} months — possibly abandoned`);\n }\n }\n\n // Version count as a maturity signal\n if (registry.versionCount > 50) {\n signals.push({ direction: 'stable', weight: 0.1 });\n reasons.push(`Mature package with ${registry.versionCount} versions`);\n }\n\n // Deprecated flag is a strong declining signal (string | null)\n if (registry.deprecated !== null) {\n signals.push({ direction: 'declining', weight: 0.5 });\n reasons.push('Package is marked as deprecated');\n }\n }\n\n // -----------------------------------------------------------------------\n // Signal 4: Star/fork ratio as community interest (GitHub)\n // -----------------------------------------------------------------------\n if (github !== null && github.stars > 0) {\n const forkRatio = github.forks / github.stars;\n if (forkRatio > 0.3) {\n // High fork ratio suggests active community interest\n signals.push({ direction: 'rising', weight: 0.1 });\n reasons.push('High fork-to-star ratio indicates active community');\n }\n }\n\n // -----------------------------------------------------------------------\n // Aggregate signals\n // -----------------------------------------------------------------------\n if (signals.length === 0) {\n return {\n trend: 'unknown',\n confidence: 0,\n riskProjection3m: 0,\n reason: 'Insufficient data to determine trend',\n };\n }\n\n const { trend, confidence } = this.aggregateSignals(signals);\n const riskProjection3m = this.calculateRiskProjection(trend, confidence, signals);\n\n return {\n trend,\n confidence: Math.round(confidence * 100) / 100,\n riskProjection3m: Math.round(riskProjection3m),\n reason: reasons.join('. ') + '.',\n };\n }\n\n // -------------------------------------------------------------------------\n // Internal helpers\n // -------------------------------------------------------------------------\n\n /**\n * Aggregate multiple trend signals into a single direction and confidence.\n * Uses weighted voting: each signal votes for its direction with its weight.\n */\n private aggregateSignals(signals: TrendSignal[]): {\n trend: TrendDirection;\n confidence: number;\n } {\n const votes: Record<Exclude<TrendDirection, 'unknown'>, number> = {\n rising: 0,\n stable: 0,\n declining: 0,\n };\n\n let totalWeight = 0;\n\n for (const signal of signals) {\n votes[signal.direction] += signal.weight;\n totalWeight += signal.weight;\n }\n\n // Find the winning direction\n let maxVote = 0;\n let trend: TrendDirection = 'stable';\n\n for (const [direction, vote] of Object.entries(votes)) {\n if (vote > maxVote) {\n maxVote = vote;\n trend = direction as TrendDirection;\n }\n }\n\n // Confidence is based on:\n // 1. How dominant the winning direction is (proportion of total weight)\n // 2. How many signals we have (more signals = higher confidence)\n const dominance = totalWeight > 0 ? maxVote / totalWeight : 0;\n const signalCountFactor = Math.min(signals.length / HIGH_CONFIDENCE_THRESHOLD, 1);\n const confidence = Math.min(dominance * signalCountFactor, 1);\n\n return { trend, confidence };\n }\n\n /**\n * Estimate how much the trust score might change in 3 months based on\n * the trend and confidence.\n */\n private calculateRiskProjection(\n trend: TrendDirection,\n confidence: number,\n signals: TrendSignal[],\n ): number {\n // Base projection ranges\n const projectionMap: Record<TrendDirection, number> = {\n rising: 5,\n stable: 0,\n declining: -10,\n unknown: 0,\n };\n\n let base = projectionMap[trend];\n\n // Scale by confidence\n base *= confidence;\n\n // Strong negative signals amplify the projection\n const decliningWeight = signals\n .filter((s) => s.direction === 'declining')\n .reduce((sum, s) => sum + s.weight, 0);\n\n if (decliningWeight > 0.5) {\n // Multiple strong declining signals — amplify the negative projection\n base -= Math.round(decliningWeight * 5);\n }\n\n // Clamp to reasonable range\n return Math.max(-20, Math.min(10, base));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\ninterface TrendSignal {\n direction: 'rising' | 'stable' | 'declining';\n weight: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction monthsBetween(from: Date, to: Date): number {\n const years = to.getFullYear() - from.getFullYear();\n const months = to.getMonth() - from.getMonth();\n const days = to.getDate() - from.getDate();\n return years * 12 + months + (days < 0 ? -0.5 : 0);\n}\n","/**\n * Token-bucket rate limiter for controlling outbound HTTP request frequency.\n *\n * Usage:\n * ```ts\n * const limiter = new RateLimiter(10, 60_000); // 10 requests per minute\n * await limiter.acquire(); // blocks if bucket is empty\n * await fetch(url);\n * ```\n */\nexport class RateLimiter {\n private tokens: number;\n private readonly maxTokens: number;\n private readonly refillIntervalMs: number;\n private lastRefill: number;\n private waitQueue: Array<() => void> = [];\n\n /**\n * @param maxRequests Maximum number of requests allowed in the window\n * @param windowMs Window duration in milliseconds\n */\n constructor(maxRequests: number, windowMs: number) {\n this.maxTokens = maxRequests;\n this.tokens = maxRequests;\n this.refillIntervalMs = windowMs;\n this.lastRefill = Date.now();\n }\n\n /**\n * Acquire a token. Resolves immediately when tokens are available,\n * otherwise waits until the bucket is refilled.\n */\n async acquire(): Promise<void> {\n this.refill();\n\n if (this.tokens > 0) {\n this.tokens--;\n return;\n }\n\n // No tokens available — wait for the next refill cycle\n return new Promise<void>((resolve) => {\n this.waitQueue.push(resolve);\n this.scheduleRefill();\n });\n }\n\n /**\n * Return the number of tokens currently available (without waiting).\n */\n get remaining(): number {\n this.refill();\n return this.tokens;\n }\n\n /**\n * Return the number of milliseconds until the next refill.\n */\n get msUntilRefill(): number {\n const elapsed = Date.now() - this.lastRefill;\n return Math.max(0, this.refillIntervalMs - elapsed);\n }\n\n // -----------------------------------------------------------------------\n // Internal\n // -----------------------------------------------------------------------\n\n private refill(): void {\n const now = Date.now();\n const elapsed = now - this.lastRefill;\n\n if (elapsed >= this.refillIntervalMs) {\n // Full refill\n const periods = Math.floor(elapsed / this.refillIntervalMs);\n this.tokens = Math.min(this.maxTokens, this.tokens + periods * this.maxTokens);\n this.lastRefill = now - (elapsed % this.refillIntervalMs);\n this.drainWaitQueue();\n }\n }\n\n private scheduleRefill(): void {\n const delay = this.msUntilRefill;\n if (delay <= 0) {\n this.refill();\n return;\n }\n\n setTimeout(() => {\n this.refill();\n }, delay);\n }\n\n private drainWaitQueue(): void {\n while (this.waitQueue.length > 0 && this.tokens > 0) {\n this.tokens--;\n const resolve = this.waitQueue.shift();\n resolve?.();\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Pre-configured limiters\n// ---------------------------------------------------------------------------\n\n/**\n * GitHub API rate limiter: 5000 requests per hour (authenticated).\n *\n * GitHub's unauthenticated limit is 60/hr, but dep-oracle is designed to\n * work with a token so we use the authenticated limit.\n */\nexport const githubRateLimiter = new RateLimiter(5000, 3_600_000);\n\n/**\n * npm registry rate limiter: generous default of 300 requests per minute.\n * The npm registry does not publish official limits, but this is safe.\n */\nexport const npmRateLimiter = new RateLimiter(300, 60_000);\n\n/**\n * PyPI rate limiter: conservative 100 requests per minute.\n */\nexport const pypiRateLimiter = new RateLimiter(100, 60_000);\n","/**\n * Configuration loader for dep-oracle.\n *\n * Uses cosmiconfig to search for configuration in:\n * - package.json \"dep-oracle\" field\n * - .dep-oraclerc / .dep-oraclerc.json / .dep-oraclerc.yaml / .dep-oraclerc.yml\n * - dep-oracle.config.js / dep-oracle.config.cjs / dep-oracle.config.mjs\n *\n * Merge order: defaults < file config < environment variables < CLI overrides.\n */\n\nimport { cosmiconfig } from 'cosmiconfig';\nimport { ConfigSchema, type Config } from '../parsers/schema.js';\n\nconst explorer = cosmiconfig('dep-oracle');\n\n/**\n * Load and validate project configuration.\n *\n * @param overrides - Partial config values from CLI flags or programmatic use.\n * These take highest precedence.\n * @returns Fully validated Config object with defaults applied.\n */\nexport async function loadConfig(overrides?: Partial<Config>): Promise<Config> {\n let fileConfig: Record<string, unknown> = {};\n\n try {\n const result = await explorer.search();\n if (result?.config && typeof result.config === 'object') {\n fileConfig = result.config as Record<string, unknown>;\n }\n } catch {\n // Config file is optional -- continue with defaults if search fails\n }\n\n // Merge: file config < CLI overrides\n const merged: Record<string, unknown> = { ...fileConfig, ...overrides };\n\n // Read GitHub token from environment variables when not set by file or override\n if (!merged.githubToken && process.env.GITHUB_TOKEN) {\n merged.githubToken = process.env.GITHUB_TOKEN;\n }\n if (!merged.githubToken && process.env.DEP_ORACLE_GITHUB_TOKEN) {\n merged.githubToken = process.env.DEP_ORACLE_GITHUB_TOKEN;\n }\n\n return ConfigSchema.parse(merged);\n}\n","/**\n * dep-oracle -- public API for programmatic use.\n *\n * This module re-exports the key types, parsers, collectors, analyzers,\n * and cache infrastructure so consumers can integrate dep-oracle into\n * their own tooling without going through the CLI.\n *\n * Usage:\n * import { NpmParser, CollectorOrchestrator, TrustScoreEngine } from 'dep-oracle';\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type {\n DependencyTree,\n DependencyNode,\n TrustReport,\n ScanResult,\n Config,\n CollectorResult,\n CollectorStatus,\n TrustMetrics,\n Registry,\n RegistryData,\n GitHubData,\n SecurityData,\n FundingData,\n PopularityData,\n LicenseData,\n DownloadTrend,\n LicenseRisk,\n VulnerabilityEntry,\n} from './parsers/schema.js';\n\n// ---------------------------------------------------------------------------\n// Schema utilities\n// ---------------------------------------------------------------------------\n\nexport {\n ConfigSchema,\n DependencyNodeSchema,\n DependencyTreeSchema,\n TrustReportSchema,\n ScanResultSchema,\n TrustMetricsSchema,\n createDependencyNode,\n createDependencyTree,\n collectorSuccess,\n collectorError,\n collectorCached,\n collectorOffline,\n} from './parsers/schema.js';\n\n// ---------------------------------------------------------------------------\n// Parsers\n// ---------------------------------------------------------------------------\n\nexport { BaseParser } from './parsers/base.js';\nexport { NpmParser } from './parsers/npm.js';\nexport { PythonParser } from './parsers/python.js';\n\n// ---------------------------------------------------------------------------\n// Cache\n// ---------------------------------------------------------------------------\n\nexport { CacheManager } from './cache/store.js';\n\n// ---------------------------------------------------------------------------\n// Collectors\n// ---------------------------------------------------------------------------\n\nexport { CollectorOrchestrator } from './collectors/orchestrator.js';\nexport type { AllCollectorResults, OrchestratorOptions } from './collectors/orchestrator.js';\n\n// ---------------------------------------------------------------------------\n// Analyzers\n// ---------------------------------------------------------------------------\n\nexport { TrustScoreEngine } from './analyzers/trust-score.js';\nexport type { TrustScoreResult } from './analyzers/trust-score.js';\n\nexport { ZombieDetector } from './analyzers/zombie-detector.js';\nexport type { ZombieResult, ZombieSeverity } from './analyzers/zombie-detector.js';\n\nexport { BlastRadiusCalculator } from './analyzers/blast-radius.js';\nexport type { BlastRadiusResult } from './analyzers/blast-radius.js';\n\nexport { TyposquatDetector } from './analyzers/typosquat.js';\nexport type { TyposquatResult } from './analyzers/typosquat.js';\n\nexport { TrendPredictor } from './analyzers/trend-predictor.js';\nexport type { TrendResult, TrendDirection } from './analyzers/trend-predictor.js';\n\nexport { MigrationAdvisor } from './analyzers/migration-advisor.js';\nexport type { MigrationSuggestion, MigrationDifficulty } from './analyzers/migration-advisor.js';\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\nexport { buildImportGraph, getBlastRadius, getImportingFiles } from './utils/graph.js';\nexport type { ImportGraph } from './utils/graph.js';\n\nexport { logger, createLogger, isDebug, setVerbose, isVerbose } from './utils/logger.js';\n\nexport { RateLimiter, githubRateLimiter, npmRateLimiter, pypiRateLimiter } from './utils/rate-limiter.js';\n\n// ---------------------------------------------------------------------------\n// Config loader\n// ---------------------------------------------------------------------------\n\nexport { loadConfig } from './cli/config.js';\n\n// ---------------------------------------------------------------------------\n// Convenience API — matches README documentation\n// ---------------------------------------------------------------------------\n\nimport { resolve } from 'node:path';\nimport type { DependencyTree, TrustReport, ScanResult } from './parsers/schema.js';\nimport { NpmParser } from './parsers/npm.js';\nimport { PythonParser } from './parsers/python.js';\nimport { CacheManager } from './cache/store.js';\nimport { CollectorOrchestrator } from './collectors/orchestrator.js';\nimport { TrustScoreEngine } from './analyzers/trust-score.js';\nimport { ZombieDetector } from './analyzers/zombie-detector.js';\nimport { MigrationAdvisor } from './analyzers/migration-advisor.js';\nimport { TyposquatDetector } from './analyzers/typosquat.js';\nimport { buildImportGraph, getBlastRadius } from './utils/graph.js';\n\nasync function detectProject(dir: string): Promise<DependencyTree | null> {\n const parsers = [new NpmParser(), new PythonParser()];\n for (const parser of parsers) {\n if (await parser.detect(dir)) return parser.parse(dir);\n }\n return null;\n}\n\n/**\n * Scan a project directory and return a full trust report.\n *\n * ```typescript\n * import { scan } from 'dep-oracle';\n * const report = await scan({ dir: './my-project' });\n * console.log(report.overallScore);\n * ```\n */\nexport async function scan(options?: {\n dir?: string;\n githubToken?: string;\n offline?: boolean;\n}): Promise<ScanResult> {\n const dir = resolve(options?.dir ?? process.cwd());\n const cache = new CacheManager();\n const orchestrator = new CollectorOrchestrator(cache, {\n githubToken: options?.githubToken,\n offline: options?.offline,\n });\n const trustEngine = new TrustScoreEngine();\n const zombieDetector = new ZombieDetector();\n const migrationAdvisor = new MigrationAdvisor();\n const typosquatDetector = new TyposquatDetector();\n\n const tree = await detectProject(dir);\n if (!tree) {\n return {\n tree: { root: dir, manifest: '', nodes: new Map(), totalDirect: 0, totalTransitive: 0 },\n reports: [],\n overallScore: 0,\n summary: 'No supported manifest file found.',\n };\n }\n\n const importGraph = await buildImportGraph(dir);\n const directNodes = Array.from(tree.nodes.values()).filter((n) => n.isDirect);\n const reports: TrustReport[] = [];\n\n for (const node of directNodes) {\n const blastRadius = getBlastRadius(node.name, importGraph);\n const results = await orchestrator.collectAll(node.name, node.version);\n const trustResult = trustEngine.calculate(results);\n const zombie = zombieDetector.detect(results.registry.data, results.github.data);\n const typosquat = typosquatDetector.check(node.name);\n const alternatives = migrationAdvisor\n .suggest(node.name, zombie.isZombie ? 'zombie dependency' : 'low trust score')\n .map((a) => a.alternative);\n const trend = results.popularity.data?.trend ?? 'stable';\n\n reports.push({\n package: node.name,\n version: node.version,\n trustScore: trustResult.trustScore,\n metrics: trustResult.metrics,\n isZombie: zombie.isZombie,\n blastRadius,\n alternatives,\n trend,\n typosquatRisk: typosquat.isRisky ? 1.0 : 0.0,\n });\n }\n\n const overallScore =\n reports.length > 0\n ? Math.round(reports.reduce((sum, r) => sum + r.trustScore, 0) / reports.length)\n : 0;\n\n return {\n tree,\n reports,\n overallScore,\n summary:\n `Scanned ${reports.length} direct dependencies. ` +\n `Overall trust score: ${overallScore}/100. ` +\n `${reports.filter((r) => r.isZombie).length} zombie(s) detected.`,\n };\n}\n\n/**\n * Check a single package and return its trust report.\n *\n * ```typescript\n * import { checkPackage } from 'dep-oracle';\n * const result = await checkPackage('express');\n * console.log(result.trustScore); // 74\n * ```\n */\nexport async function checkPackage(\n packageName: string,\n version?: string,\n options?: { githubToken?: string; offline?: boolean },\n): Promise<TrustReport> {\n const cache = new CacheManager();\n const orchestrator = new CollectorOrchestrator(cache, {\n githubToken: options?.githubToken,\n offline: options?.offline,\n });\n const trustEngine = new TrustScoreEngine();\n const zombieDetector = new ZombieDetector();\n const migrationAdvisor = new MigrationAdvisor();\n const typosquatDetector = new TyposquatDetector();\n\n const results = await orchestrator.collectAll(packageName, version ?? 'latest');\n const trustResult = trustEngine.calculate(results);\n const zombie = zombieDetector.detect(results.registry.data, results.github.data);\n const typosquat = typosquatDetector.check(packageName);\n const alternatives = migrationAdvisor\n .suggest(packageName, zombie.isZombie ? 'zombie dependency' : 'low trust score')\n .map((a) => a.alternative);\n const trend = results.popularity.data?.trend ?? 'stable';\n\n return {\n package: packageName,\n version: version ?? 'latest',\n trustScore: trustResult.trustScore,\n metrics: trustResult.metrics,\n isZombie: zombie.isZombie,\n blastRadius: 0,\n alternatives,\n trend,\n typosquatRisk: typosquat.isRisky ? 1.0 : 0.0,\n };\n}\n"],"mappings":"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","names":["resolve"]}
|
package/dist/mcp/server.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
ZombieDetector,
|
|
11
11
|
buildImportGraph,
|
|
12
12
|
getBlastRadius
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-TXSNFX3N.js";
|
|
14
14
|
|
|
15
15
|
// src/mcp/server.ts
|
|
16
16
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -70,6 +70,32 @@ var JsonReporter = class {
|
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
// src/mcp/tools.ts
|
|
73
|
+
var VALID_PACKAGE_NAME = /^(@[a-z0-9-~][a-z0-9._-~]*\/)?[a-z0-9-~][a-z0-9._-~]*$/i;
|
|
74
|
+
function validateDir(input) {
|
|
75
|
+
const dir = resolve(String(input ?? process.cwd()));
|
|
76
|
+
const normalized = dir.replace(/\\/g, "/");
|
|
77
|
+
if (normalized.includes("/../") || normalized.endsWith("/..")) {
|
|
78
|
+
throw new Error("Path traversal not allowed");
|
|
79
|
+
}
|
|
80
|
+
return dir;
|
|
81
|
+
}
|
|
82
|
+
function validateOutputPath(output, baseDir) {
|
|
83
|
+
const resolved = resolve(output);
|
|
84
|
+
const base = resolve(baseDir);
|
|
85
|
+
if (!resolved.startsWith(base)) {
|
|
86
|
+
throw new Error("Output path must be within the project directory");
|
|
87
|
+
}
|
|
88
|
+
return resolved;
|
|
89
|
+
}
|
|
90
|
+
function validatePackageName(name) {
|
|
91
|
+
const trimmed = name.trim();
|
|
92
|
+
if (!trimmed) throw new Error("Package name is required");
|
|
93
|
+
if (trimmed.length > 214) throw new Error("Package name too long");
|
|
94
|
+
if (!VALID_PACKAGE_NAME.test(trimmed)) {
|
|
95
|
+
throw new Error(`Invalid package name: "${trimmed}"`);
|
|
96
|
+
}
|
|
97
|
+
return trimmed;
|
|
98
|
+
}
|
|
73
99
|
var cache = new CacheManager();
|
|
74
100
|
var orchestrator = new CollectorOrchestrator(cache, {
|
|
75
101
|
githubToken: process.env.GITHUB_TOKEN
|
|
@@ -290,7 +316,7 @@ async function buildTrustReport(packageName, version, blastRadius = 0) {
|
|
|
290
316
|
};
|
|
291
317
|
}
|
|
292
318
|
async function handleScan(args) {
|
|
293
|
-
const dir =
|
|
319
|
+
const dir = validateDir(args?.dir);
|
|
294
320
|
const tree = await parseProject(dir);
|
|
295
321
|
if (!tree) {
|
|
296
322
|
return errorResponse(
|
|
@@ -321,25 +347,19 @@ async function handleScan(args) {
|
|
|
321
347
|
return successResponse(serialized);
|
|
322
348
|
}
|
|
323
349
|
async function handleTrustScore(args) {
|
|
324
|
-
const packageName = String(args?.package ?? "");
|
|
325
|
-
if (!packageName) {
|
|
326
|
-
return errorResponse('Missing required parameter: "package".');
|
|
327
|
-
}
|
|
350
|
+
const packageName = validatePackageName(String(args?.package ?? ""));
|
|
328
351
|
const version = String(args?.version ?? "latest");
|
|
329
352
|
const report = await buildTrustReport(packageName, version);
|
|
330
353
|
return successResponse(report);
|
|
331
354
|
}
|
|
332
355
|
async function handleBlastRadius(args) {
|
|
333
|
-
const packageName = String(args?.package ?? "");
|
|
334
|
-
|
|
335
|
-
return errorResponse('Missing required parameter: "package".');
|
|
336
|
-
}
|
|
337
|
-
const dir = resolve(String(args?.dir ?? process.cwd()));
|
|
356
|
+
const packageName = validatePackageName(String(args?.package ?? ""));
|
|
357
|
+
const dir = validateDir(args?.dir);
|
|
338
358
|
const result = await blastRadiusCalc.calculate(packageName, dir);
|
|
339
359
|
return successResponse(result);
|
|
340
360
|
}
|
|
341
361
|
async function handleZombies(args) {
|
|
342
|
-
const dir =
|
|
362
|
+
const dir = validateDir(args?.dir);
|
|
343
363
|
const tree = await parseProject(dir);
|
|
344
364
|
if (!tree) {
|
|
345
365
|
return errorResponse(
|
|
@@ -376,10 +396,7 @@ async function handleZombies(args) {
|
|
|
376
396
|
});
|
|
377
397
|
}
|
|
378
398
|
async function handleSuggestMigration(args) {
|
|
379
|
-
const packageName = String(args?.package ?? "");
|
|
380
|
-
if (!packageName) {
|
|
381
|
-
return errorResponse('Missing required parameter: "package".');
|
|
382
|
-
}
|
|
399
|
+
const packageName = validatePackageName(String(args?.package ?? ""));
|
|
383
400
|
const suggestions = migrationAdvisor.suggest(packageName, "manual query");
|
|
384
401
|
if (suggestions.length === 0) {
|
|
385
402
|
return successResponse({
|
|
@@ -394,10 +411,7 @@ async function handleSuggestMigration(args) {
|
|
|
394
411
|
});
|
|
395
412
|
}
|
|
396
413
|
async function handleTyposquatCheck(args) {
|
|
397
|
-
const packageName = String(args?.package ?? "");
|
|
398
|
-
if (!packageName) {
|
|
399
|
-
return errorResponse('Missing required parameter: "package".');
|
|
400
|
-
}
|
|
414
|
+
const packageName = validatePackageName(String(args?.package ?? ""));
|
|
401
415
|
const result = typosquatDetector.check(packageName);
|
|
402
416
|
return successResponse({
|
|
403
417
|
package: packageName,
|
|
@@ -405,11 +419,8 @@ async function handleTyposquatCheck(args) {
|
|
|
405
419
|
});
|
|
406
420
|
}
|
|
407
421
|
async function handleCompare(args) {
|
|
408
|
-
const packageA = String(args?.packageA ?? "");
|
|
409
|
-
const packageB = String(args?.packageB ?? "");
|
|
410
|
-
if (!packageA || !packageB) {
|
|
411
|
-
return errorResponse('Missing required parameters: "packageA" and "packageB".');
|
|
412
|
-
}
|
|
422
|
+
const packageA = validatePackageName(String(args?.packageA ?? ""));
|
|
423
|
+
const packageB = validatePackageName(String(args?.packageB ?? ""));
|
|
413
424
|
const [reportA, reportB] = await Promise.all([
|
|
414
425
|
buildTrustReport(packageA, "latest"),
|
|
415
426
|
buildTrustReport(packageB, "latest")
|
|
@@ -424,8 +435,8 @@ async function handleCompare(args) {
|
|
|
424
435
|
});
|
|
425
436
|
}
|
|
426
437
|
async function handleReport(args) {
|
|
427
|
-
const dir =
|
|
428
|
-
const output = args?.output ?
|
|
438
|
+
const dir = validateDir(args?.dir);
|
|
439
|
+
const output = args?.output ? validateOutputPath(String(args.output), dir) : null;
|
|
429
440
|
const tree = await parseProject(dir);
|
|
430
441
|
if (!tree) {
|
|
431
442
|
return errorResponse(
|
|
@@ -472,7 +483,7 @@ var pkgVersion = (() => {
|
|
|
472
483
|
try {
|
|
473
484
|
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
474
485
|
} catch {
|
|
475
|
-
return "1.
|
|
486
|
+
return "1.2.0";
|
|
476
487
|
}
|
|
477
488
|
})();
|
|
478
489
|
var server = new Server(
|
package/dist/mcp/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/mcp/server.ts","../../src/mcp/tools.ts","../../src/reporters/json.ts"],"sourcesContent":["/**\n * MCP stdio server entry point for dep-oracle.\n *\n * Exposes the full dep-oracle analysis engine as MCP tools so that\n * Claude (and other MCP clients) can scan projects, evaluate trust\n * scores, detect zombies, and more -- all through the standard\n * Model Context Protocol.\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\nimport { registerTools } from './tools.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst pkgPath = join(__dirname, '..', '..', 'package.json');\nconst pkgVersion = (() => {\n try {\n return JSON.parse(readFileSync(pkgPath, 'utf-8')).version as string;\n } catch {\n return '1.1.1';\n }\n})();\n\nconst server = new Server(\n { name: 'dep-oracle', version: pkgVersion },\n { capabilities: { tools: {} } },\n);\n\nregisterTools(server);\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","/**\n * MCP tool definitions for dep-oracle.\n *\n * Registers 8 tools that expose the full analysis pipeline:\n * 1. dep_oracle_scan -- full project scan\n * 2. dep_oracle_trust_score -- single package trust score\n * 3. dep_oracle_blast_radius -- import impact analysis\n * 4. dep_oracle_zombies -- list zombie dependencies\n * 5. dep_oracle_suggest_migration -- migration suggestions\n * 6. dep_oracle_typosquat_check -- typosquat risk check\n * 7. dep_oracle_compare -- side-by-side package comparison\n * 8. dep_oracle_report -- generate HTML report\n *\n * All tools share the same core engine (parsers, collectors, analyzers)\n * as the CLI.\n */\n\nimport { resolve } from 'node:path';\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n ListToolsRequestSchema,\n CallToolRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\n\nimport { CacheManager } from '../cache/store.js';\nimport { NpmParser } from '../parsers/npm.js';\nimport { PythonParser } from '../parsers/python.js';\nimport type { DependencyTree, TrustReport, ScanResult } from '../parsers/schema.js';\nimport { CollectorOrchestrator } from '../collectors/orchestrator.js';\nimport { TrustScoreEngine } from '../analyzers/trust-score.js';\nimport { ZombieDetector } from '../analyzers/zombie-detector.js';\nimport { BlastRadiusCalculator } from '../analyzers/blast-radius.js';\nimport { MigrationAdvisor } from '../analyzers/migration-advisor.js';\nimport { TyposquatDetector } from '../analyzers/typosquat.js';\nimport { buildImportGraph, getBlastRadius } from '../utils/graph.js';\nimport { JsonReporter } from '../reporters/json.js';\n\n// ---------------------------------------------------------------------------\n// Shared instances (created once per MCP server lifetime)\n// ---------------------------------------------------------------------------\n\nconst cache = new CacheManager();\nconst orchestrator = new CollectorOrchestrator(cache, {\n githubToken: process.env.GITHUB_TOKEN,\n});\nconst trustEngine = new TrustScoreEngine();\nconst zombieDetector = new ZombieDetector();\nconst blastRadiusCalc = new BlastRadiusCalculator();\nconst migrationAdvisor = new MigrationAdvisor();\nconst typosquatDetector = new TyposquatDetector();\nconst jsonReporter = new JsonReporter();\nconst parsers = [new NpmParser(), new PythonParser()];\n\n// ---------------------------------------------------------------------------\n// Tool definitions\n// ---------------------------------------------------------------------------\n\nconst TOOLS = [\n {\n name: 'dep_oracle_scan',\n description:\n 'Perform a full dependency security scan on a project directory. ' +\n 'Parses the manifest (package.json, requirements.txt, etc.), collects data ' +\n 'from registries and GitHub, computes trust scores for every dependency, ' +\n 'detects zombies, typosquats, and calculates blast radius. Returns a complete ' +\n 'ScanResult JSON with per-package trust reports and an overall project score.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n dir: {\n type: 'string',\n description:\n 'Absolute path to the project directory to scan. ' +\n 'Defaults to the current working directory if omitted.',\n },\n },\n required: [] as string[],\n },\n },\n {\n name: 'dep_oracle_trust_score',\n description:\n 'Calculate the trust score for a single npm package. Queries the npm registry, ' +\n 'GitHub, OSV vulnerability database, and other sources to produce a weighted ' +\n 'score (0-100) across 6 dimensions: security, maintainer, activity, popularity, ' +\n 'funding, and license. Returns a TrustReport JSON with the overall score, ' +\n 'per-dimension metrics, zombie status, and alternative suggestions.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n package: {\n type: 'string',\n description: 'npm package name (e.g. \"express\", \"@scope/pkg\").',\n },\n version: {\n type: 'string',\n description:\n 'Specific version to analyze (e.g. \"4.18.2\"). If omitted, the latest version is used.',\n },\n },\n required: ['package'],\n },\n },\n {\n name: 'dep_oracle_blast_radius',\n description:\n 'Analyze the blast radius (import impact) of a package within a project. ' +\n 'Scans all JS/TS source files to find how many files import the given package. ' +\n 'Returns the count of affected files, their paths, and the percentage of the ' +\n 'codebase impacted. Useful for understanding the risk if a dependency is ' +\n 'compromised or needs replacement.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n package: {\n type: 'string',\n description: 'Package name to check import usage for.',\n },\n dir: {\n type: 'string',\n description:\n 'Absolute path to the project directory. Defaults to cwd if omitted.',\n },\n },\n required: ['package'],\n },\n },\n {\n name: 'dep_oracle_zombies',\n description:\n 'Detect zombie (abandoned/unmaintained) dependencies in a project. ' +\n 'Parses the manifest, queries registry and GitHub for each dependency, and ' +\n 'flags packages that show signs of abandonment: deprecated, no commits in 12+ months, ' +\n 'no active maintainers, etc. Returns an array of zombie packages with severity ' +\n 'levels and reasons.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n dir: {\n type: 'string',\n description:\n 'Absolute path to the project directory. Defaults to cwd if omitted.',\n },\n },\n required: [] as string[],\n },\n },\n {\n name: 'dep_oracle_suggest_migration',\n description:\n 'Get migration suggestions for a given package. Looks up the package in a ' +\n 'curated knowledge base of common replacements and returns alternatives with ' +\n 'descriptions and difficulty ratings. Useful for replacing deprecated, abandoned, ' +\n 'or low-trust packages (e.g. moment -> dayjs, request -> got, lodash -> es-toolkit).',\n inputSchema: {\n type: 'object' as const,\n properties: {\n package: {\n type: 'string',\n description: 'Package name to find migration alternatives for.',\n },\n },\n required: ['package'],\n },\n },\n {\n name: 'dep_oracle_typosquat_check',\n description:\n 'Check whether a package name is a potential typosquat of a popular package. ' +\n 'Uses Levenshtein distance, homoglyph detection, and pattern analysis to identify ' +\n 'suspicious package names that closely resemble well-known packages. Returns a ' +\n 'risk assessment with similar package names and the edit distance.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n package: {\n type: 'string',\n description: 'Package name to check for typosquatting risk.',\n },\n },\n required: ['package'],\n },\n },\n {\n name: 'dep_oracle_compare',\n description:\n 'Compare two packages side-by-side by computing trust scores for both. ' +\n 'Returns the full trust report for each package including scores, metrics, ' +\n 'zombie status, and trend direction. Useful for evaluating alternatives ' +\n 'or choosing between competing libraries.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n packageA: {\n type: 'string',\n description: 'First package name to compare.',\n },\n packageB: {\n type: 'string',\n description: 'Second package name to compare.',\n },\n },\n required: ['packageA', 'packageB'],\n },\n },\n {\n name: 'dep_oracle_report',\n description:\n 'Generate a JSON report for a project. Runs a full scan and outputs the results ' +\n 'as formatted JSON. Optionally writes the report to a file. Returns the JSON ' +\n 'content or the path to the generated file.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n dir: {\n type: 'string',\n description:\n 'Absolute path to the project directory. Defaults to cwd if omitted.',\n },\n output: {\n type: 'string',\n description:\n 'Absolute path to write the report file. If omitted, the report content is returned directly.',\n },\n },\n required: [] as string[],\n },\n },\n];\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerTools(server: Server): void {\n // List all available tools\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: TOOLS,\n }));\n\n // Handle tool invocations\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n switch (name) {\n case 'dep_oracle_scan':\n return await handleScan(args);\n case 'dep_oracle_trust_score':\n return await handleTrustScore(args);\n case 'dep_oracle_blast_radius':\n return await handleBlastRadius(args);\n case 'dep_oracle_zombies':\n return await handleZombies(args);\n case 'dep_oracle_suggest_migration':\n return await handleSuggestMigration(args);\n case 'dep_oracle_typosquat_check':\n return await handleTyposquatCheck(args);\n case 'dep_oracle_compare':\n return await handleCompare(args);\n case 'dep_oracle_report':\n return await handleReport(args);\n default:\n return errorResponse(`Unknown tool: ${name}`);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return errorResponse(`Tool \"${name}\" failed: ${message}`);\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Helper: build a successful text content response\n// ---------------------------------------------------------------------------\n\nfunction successResponse(data: unknown) {\n const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);\n return {\n content: [{ type: 'text' as const, text }],\n };\n}\n\nfunction errorResponse(message: string) {\n return {\n content: [{ type: 'text' as const, text: message }],\n isError: true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Shared: parse project dependencies\n// ---------------------------------------------------------------------------\n\nasync function parseProject(dir: string): Promise<DependencyTree | null> {\n for (const parser of parsers) {\n if (await parser.detect(dir)) {\n return parser.parse(dir);\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Shared: build a full trust report for a single package\n// ---------------------------------------------------------------------------\n\nasync function buildTrustReport(\n packageName: string,\n version: string,\n blastRadius: number = 0,\n): Promise<TrustReport> {\n const results = await orchestrator.collectAll(packageName, version);\n const trustResult = trustEngine.calculate(results);\n const zombie = zombieDetector.detect(\n results.registry.data,\n results.github.data,\n );\n const typosquat = typosquatDetector.check(packageName);\n const alternatives = migrationAdvisor.suggest(\n packageName,\n zombie.isZombie ? 'zombie dependency' : 'low trust score',\n );\n\n // Determine trend from popularity data\n const trend = results.popularity.data?.trend ?? 'stable';\n\n return {\n package: packageName,\n version,\n trustScore: trustResult.trustScore,\n metrics: trustResult.metrics,\n isZombie: zombie.isZombie,\n blastRadius,\n alternatives: alternatives.map((a) => a.alternative),\n trend,\n typosquatRisk: typosquat.isRisky ? 1.0 : 0.0,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Tool handlers\n// ---------------------------------------------------------------------------\n\nasync function handleScan(args: Record<string, unknown> | undefined) {\n const dir = resolve(String(args?.dir ?? process.cwd()));\n\n const tree = await parseProject(dir);\n if (!tree) {\n return errorResponse(\n `No supported manifest file found in \"${dir}\". ` +\n 'Supported: package.json, requirements.txt, pyproject.toml, Pipfile.',\n );\n }\n\n // Build import graph for blast radius\n const importGraph = await buildImportGraph(dir);\n\n // Collect trust reports for all direct dependencies\n const directNodes = Array.from(tree.nodes.values()).filter((n) => n.isDirect);\n const reports: TrustReport[] = [];\n\n for (const node of directNodes) {\n const blastRadius = getBlastRadius(node.name, importGraph);\n const report = await buildTrustReport(node.name, node.version, blastRadius);\n reports.push(report);\n }\n\n // Overall score: weighted average\n const overallScore =\n reports.length > 0\n ? Math.round(\n reports.reduce((sum, r) => sum + r.trustScore, 0) / reports.length,\n )\n : 0;\n\n // Summary\n const zombieCount = reports.filter((r) => r.isZombie).length;\n const criticalCount = reports.filter((r) => r.trustScore < 50).length;\n const summary =\n `Scanned ${reports.length} direct dependencies. ` +\n `Overall trust score: ${overallScore}/100. ` +\n `${zombieCount} zombie(s) detected. ` +\n `${criticalCount} package(s) below trust threshold.`;\n\n const scanResult: ScanResult = {\n tree,\n reports,\n overallScore,\n summary,\n };\n\n // Serialize (handles Map objects)\n const serialized = jsonReporter.report(scanResult);\n return successResponse(serialized);\n}\n\nasync function handleTrustScore(args: Record<string, unknown> | undefined) {\n const packageName = String(args?.package ?? '');\n if (!packageName) {\n return errorResponse('Missing required parameter: \"package\".');\n }\n\n const version = String(args?.version ?? 'latest');\n const report = await buildTrustReport(packageName, version);\n return successResponse(report);\n}\n\nasync function handleBlastRadius(args: Record<string, unknown> | undefined) {\n const packageName = String(args?.package ?? '');\n if (!packageName) {\n return errorResponse('Missing required parameter: \"package\".');\n }\n\n const dir = resolve(String(args?.dir ?? process.cwd()));\n const result = await blastRadiusCalc.calculate(packageName, dir);\n return successResponse(result);\n}\n\nasync function handleZombies(args: Record<string, unknown> | undefined) {\n const dir = resolve(String(args?.dir ?? process.cwd()));\n\n const tree = await parseProject(dir);\n if (!tree) {\n return errorResponse(\n `No supported manifest file found in \"${dir}\".`,\n );\n }\n\n const directNodes = Array.from(tree.nodes.values()).filter((n) => n.isDirect);\n const zombies: Array<{\n package: string;\n version: string;\n severity: string;\n reason: string;\n lastActivity: string | null;\n }> = [];\n\n for (const node of directNodes) {\n const results = await orchestrator.collectAll(node.name, node.version);\n const zombie = zombieDetector.detect(\n results.registry.data,\n results.github.data,\n );\n\n if (zombie.isZombie) {\n zombies.push({\n package: node.name,\n version: node.version,\n severity: zombie.severity,\n reason: zombie.reason,\n lastActivity: zombie.lastActivity?.toISOString() ?? null,\n });\n }\n }\n\n if (zombies.length === 0) {\n return successResponse({\n message: 'No zombie dependencies detected.',\n zombies: [],\n });\n }\n\n return successResponse({\n message: `Found ${zombies.length} zombie dependency(ies).`,\n zombies,\n });\n}\n\nasync function handleSuggestMigration(args: Record<string, unknown> | undefined) {\n const packageName = String(args?.package ?? '');\n if (!packageName) {\n return errorResponse('Missing required parameter: \"package\".');\n }\n\n const suggestions = migrationAdvisor.suggest(packageName, 'manual query');\n\n if (suggestions.length === 0) {\n return successResponse({\n package: packageName,\n message: `No migration suggestions found for \"${packageName}\". ` +\n 'This package may not have known alternatives in our database.',\n suggestions: [],\n });\n }\n\n return successResponse({\n package: packageName,\n suggestions,\n });\n}\n\nasync function handleTyposquatCheck(args: Record<string, unknown> | undefined) {\n const packageName = String(args?.package ?? '');\n if (!packageName) {\n return errorResponse('Missing required parameter: \"package\".');\n }\n\n const result = typosquatDetector.check(packageName);\n return successResponse({\n package: packageName,\n ...result,\n });\n}\n\nasync function handleCompare(args: Record<string, unknown> | undefined) {\n const packageA = String(args?.packageA ?? '');\n const packageB = String(args?.packageB ?? '');\n\n if (!packageA || !packageB) {\n return errorResponse('Missing required parameters: \"packageA\" and \"packageB\".');\n }\n\n const [reportA, reportB] = await Promise.all([\n buildTrustReport(packageA, 'latest'),\n buildTrustReport(packageB, 'latest'),\n ]);\n\n return successResponse({\n comparison: {\n packageA: reportA,\n packageB: reportB,\n winner:\n reportA.trustScore > reportB.trustScore\n ? packageA\n : reportA.trustScore < reportB.trustScore\n ? packageB\n : 'tie',\n scoreDifference: Math.abs(reportA.trustScore - reportB.trustScore),\n },\n });\n}\n\nasync function handleReport(args: Record<string, unknown> | undefined) {\n const dir = resolve(String(args?.dir ?? process.cwd()));\n const output = args?.output ? resolve(String(args.output)) : null;\n\n const tree = await parseProject(dir);\n if (!tree) {\n return errorResponse(\n `No supported manifest file found in \"${dir}\".`,\n );\n }\n\n // Build import graph for blast radius\n const importGraph = await buildImportGraph(dir);\n\n // Collect trust reports for direct dependencies\n const directNodes = Array.from(tree.nodes.values()).filter((n) => n.isDirect);\n const reports: TrustReport[] = [];\n\n for (const node of directNodes) {\n const blastRadius = getBlastRadius(node.name, importGraph);\n const report = await buildTrustReport(node.name, node.version, blastRadius);\n reports.push(report);\n }\n\n const overallScore =\n reports.length > 0\n ? Math.round(\n reports.reduce((sum, r) => sum + r.trustScore, 0) / reports.length,\n )\n : 0;\n\n const zombieCount = reports.filter((r) => r.isZombie).length;\n const criticalCount = reports.filter((r) => r.trustScore < 50).length;\n const summary =\n `Scanned ${reports.length} direct dependencies. ` +\n `Overall trust score: ${overallScore}/100. ` +\n `${zombieCount} zombie(s) detected. ` +\n `${criticalCount} package(s) below trust threshold.`;\n\n const scanResult: ScanResult = {\n tree,\n reports,\n overallScore,\n summary,\n };\n\n const jsonContent = jsonReporter.report(scanResult);\n\n if (output) {\n const { writeFile } = await import('node:fs/promises');\n await writeFile(output, jsonContent, 'utf-8');\n return successResponse({\n message: `Report written to ${output}`,\n path: output,\n });\n }\n\n return successResponse(jsonContent);\n}\n","import type { ScanResult } from \"../parsers/schema.js\";\n\n// ---------------------------------------------------------------------------\n// JsonReporter\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize a ScanResult as formatted JSON.\n *\n * The output is a self-contained JSON string suitable for piping into jq,\n * saving to a file, or passing to downstream tooling.\n */\nexport class JsonReporter {\n /**\n * Convert the scan result into a pretty-printed JSON string.\n *\n * Map objects (e.g. DependencyTree.nodes) are converted to plain\n * objects so that they survive JSON serialization.\n */\n report(result: ScanResult): string {\n return JSON.stringify(this.toSerializable(result), null, 2);\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n /**\n * Walk the value recursively and convert Map instances into plain objects\n * so JSON.stringify can handle them.\n */\n private toSerializable(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n\n if (value instanceof Map) {\n const obj: Record<string, unknown> = {};\n for (const [k, v] of value.entries()) {\n obj[String(k)] = this.toSerializable(v);\n }\n return obj;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => this.toSerializable(item));\n }\n\n if (typeof value === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = this.toSerializable(v);\n }\n return result;\n }\n\n return value;\n }\n}\n"],"mappings":"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","names":["server","__filename","__dirname"]}
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/server.ts","../../src/mcp/tools.ts","../../src/reporters/json.ts"],"sourcesContent":["/**\n * MCP stdio server entry point for dep-oracle.\n *\n * Exposes the full dep-oracle analysis engine as MCP tools so that\n * Claude (and other MCP clients) can scan projects, evaluate trust\n * scores, detect zombies, and more -- all through the standard\n * Model Context Protocol.\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\nimport { registerTools } from './tools.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst pkgPath = join(__dirname, '..', '..', 'package.json');\nconst pkgVersion = (() => {\n try {\n return JSON.parse(readFileSync(pkgPath, 'utf-8')).version as string;\n } catch {\n return '1.2.0';\n }\n})();\n\nconst server = new Server(\n { name: 'dep-oracle', version: pkgVersion },\n { capabilities: { tools: {} } },\n);\n\nregisterTools(server);\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","/**\n * MCP tool definitions for dep-oracle.\n *\n * Registers 8 tools that expose the full analysis pipeline:\n * 1. dep_oracle_scan -- full project scan\n * 2. dep_oracle_trust_score -- single package trust score\n * 3. dep_oracle_blast_radius -- import impact analysis\n * 4. dep_oracle_zombies -- list zombie dependencies\n * 5. dep_oracle_suggest_migration -- migration suggestions\n * 6. dep_oracle_typosquat_check -- typosquat risk check\n * 7. dep_oracle_compare -- side-by-side package comparison\n * 8. dep_oracle_report -- generate HTML report\n *\n * All tools share the same core engine (parsers, collectors, analyzers)\n * as the CLI.\n */\n\nimport { resolve } from 'node:path';\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n ListToolsRequestSchema,\n CallToolRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\n\nimport { CacheManager } from '../cache/store.js';\nimport { NpmParser } from '../parsers/npm.js';\nimport { PythonParser } from '../parsers/python.js';\nimport type { DependencyTree, TrustReport, ScanResult } from '../parsers/schema.js';\nimport { CollectorOrchestrator } from '../collectors/orchestrator.js';\nimport { TrustScoreEngine } from '../analyzers/trust-score.js';\nimport { ZombieDetector } from '../analyzers/zombie-detector.js';\nimport { BlastRadiusCalculator } from '../analyzers/blast-radius.js';\nimport { MigrationAdvisor } from '../analyzers/migration-advisor.js';\nimport { TyposquatDetector } from '../analyzers/typosquat.js';\nimport { buildImportGraph, getBlastRadius } from '../utils/graph.js';\nimport { JsonReporter } from '../reporters/json.js';\n\n// ---------------------------------------------------------------------------\n// Security: path validation\n// ---------------------------------------------------------------------------\n\nconst VALID_PACKAGE_NAME = /^(@[a-z0-9-~][a-z0-9._-~]*\\/)?[a-z0-9-~][a-z0-9._-~]*$/i;\n\nfunction validateDir(input: string | undefined): string {\n const dir = resolve(String(input ?? process.cwd()));\n // Block obvious path traversal patterns\n const normalized = dir.replace(/\\\\/g, '/');\n if (normalized.includes('/../') || normalized.endsWith('/..')) {\n throw new Error('Path traversal not allowed');\n }\n return dir;\n}\n\nfunction validateOutputPath(output: string, baseDir: string): string {\n const resolved = resolve(output);\n const base = resolve(baseDir);\n if (!resolved.startsWith(base)) {\n throw new Error('Output path must be within the project directory');\n }\n return resolved;\n}\n\nfunction validatePackageName(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) throw new Error('Package name is required');\n if (trimmed.length > 214) throw new Error('Package name too long');\n if (!VALID_PACKAGE_NAME.test(trimmed)) {\n throw new Error(`Invalid package name: \"${trimmed}\"`);\n }\n return trimmed;\n}\n\n// ---------------------------------------------------------------------------\n// Shared instances (created once per MCP server lifetime)\n// ---------------------------------------------------------------------------\n\nconst cache = new CacheManager();\nconst orchestrator = new CollectorOrchestrator(cache, {\n githubToken: process.env.GITHUB_TOKEN,\n});\nconst trustEngine = new TrustScoreEngine();\nconst zombieDetector = new ZombieDetector();\nconst blastRadiusCalc = new BlastRadiusCalculator();\nconst migrationAdvisor = new MigrationAdvisor();\nconst typosquatDetector = new TyposquatDetector();\nconst jsonReporter = new JsonReporter();\nconst parsers = [new NpmParser(), new PythonParser()];\n\n// ---------------------------------------------------------------------------\n// Tool definitions\n// ---------------------------------------------------------------------------\n\nconst TOOLS = [\n {\n name: 'dep_oracle_scan',\n description:\n 'Perform a full dependency security scan on a project directory. ' +\n 'Parses the manifest (package.json, requirements.txt, etc.), collects data ' +\n 'from registries and GitHub, computes trust scores for every dependency, ' +\n 'detects zombies, typosquats, and calculates blast radius. Returns a complete ' +\n 'ScanResult JSON with per-package trust reports and an overall project score.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n dir: {\n type: 'string',\n description:\n 'Absolute path to the project directory to scan. ' +\n 'Defaults to the current working directory if omitted.',\n },\n },\n required: [] as string[],\n },\n },\n {\n name: 'dep_oracle_trust_score',\n description:\n 'Calculate the trust score for a single npm package. Queries the npm registry, ' +\n 'GitHub, OSV vulnerability database, and other sources to produce a weighted ' +\n 'score (0-100) across 6 dimensions: security, maintainer, activity, popularity, ' +\n 'funding, and license. Returns a TrustReport JSON with the overall score, ' +\n 'per-dimension metrics, zombie status, and alternative suggestions.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n package: {\n type: 'string',\n description: 'npm package name (e.g. \"express\", \"@scope/pkg\").',\n },\n version: {\n type: 'string',\n description:\n 'Specific version to analyze (e.g. \"4.18.2\"). If omitted, the latest version is used.',\n },\n },\n required: ['package'],\n },\n },\n {\n name: 'dep_oracle_blast_radius',\n description:\n 'Analyze the blast radius (import impact) of a package within a project. ' +\n 'Scans all JS/TS source files to find how many files import the given package. ' +\n 'Returns the count of affected files, their paths, and the percentage of the ' +\n 'codebase impacted. Useful for understanding the risk if a dependency is ' +\n 'compromised or needs replacement.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n package: {\n type: 'string',\n description: 'Package name to check import usage for.',\n },\n dir: {\n type: 'string',\n description:\n 'Absolute path to the project directory. Defaults to cwd if omitted.',\n },\n },\n required: ['package'],\n },\n },\n {\n name: 'dep_oracle_zombies',\n description:\n 'Detect zombie (abandoned/unmaintained) dependencies in a project. ' +\n 'Parses the manifest, queries registry and GitHub for each dependency, and ' +\n 'flags packages that show signs of abandonment: deprecated, no commits in 12+ months, ' +\n 'no active maintainers, etc. Returns an array of zombie packages with severity ' +\n 'levels and reasons.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n dir: {\n type: 'string',\n description:\n 'Absolute path to the project directory. Defaults to cwd if omitted.',\n },\n },\n required: [] as string[],\n },\n },\n {\n name: 'dep_oracle_suggest_migration',\n description:\n 'Get migration suggestions for a given package. Looks up the package in a ' +\n 'curated knowledge base of common replacements and returns alternatives with ' +\n 'descriptions and difficulty ratings. Useful for replacing deprecated, abandoned, ' +\n 'or low-trust packages (e.g. moment -> dayjs, request -> got, lodash -> es-toolkit).',\n inputSchema: {\n type: 'object' as const,\n properties: {\n package: {\n type: 'string',\n description: 'Package name to find migration alternatives for.',\n },\n },\n required: ['package'],\n },\n },\n {\n name: 'dep_oracle_typosquat_check',\n description:\n 'Check whether a package name is a potential typosquat of a popular package. ' +\n 'Uses Levenshtein distance, homoglyph detection, and pattern analysis to identify ' +\n 'suspicious package names that closely resemble well-known packages. Returns a ' +\n 'risk assessment with similar package names and the edit distance.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n package: {\n type: 'string',\n description: 'Package name to check for typosquatting risk.',\n },\n },\n required: ['package'],\n },\n },\n {\n name: 'dep_oracle_compare',\n description:\n 'Compare two packages side-by-side by computing trust scores for both. ' +\n 'Returns the full trust report for each package including scores, metrics, ' +\n 'zombie status, and trend direction. Useful for evaluating alternatives ' +\n 'or choosing between competing libraries.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n packageA: {\n type: 'string',\n description: 'First package name to compare.',\n },\n packageB: {\n type: 'string',\n description: 'Second package name to compare.',\n },\n },\n required: ['packageA', 'packageB'],\n },\n },\n {\n name: 'dep_oracle_report',\n description:\n 'Generate a JSON report for a project. Runs a full scan and outputs the results ' +\n 'as formatted JSON. Optionally writes the report to a file. Returns the JSON ' +\n 'content or the path to the generated file.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n dir: {\n type: 'string',\n description:\n 'Absolute path to the project directory. Defaults to cwd if omitted.',\n },\n output: {\n type: 'string',\n description:\n 'Absolute path to write the report file. If omitted, the report content is returned directly.',\n },\n },\n required: [] as string[],\n },\n },\n];\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerTools(server: Server): void {\n // List all available tools\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: TOOLS,\n }));\n\n // Handle tool invocations\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n switch (name) {\n case 'dep_oracle_scan':\n return await handleScan(args);\n case 'dep_oracle_trust_score':\n return await handleTrustScore(args);\n case 'dep_oracle_blast_radius':\n return await handleBlastRadius(args);\n case 'dep_oracle_zombies':\n return await handleZombies(args);\n case 'dep_oracle_suggest_migration':\n return await handleSuggestMigration(args);\n case 'dep_oracle_typosquat_check':\n return await handleTyposquatCheck(args);\n case 'dep_oracle_compare':\n return await handleCompare(args);\n case 'dep_oracle_report':\n return await handleReport(args);\n default:\n return errorResponse(`Unknown tool: ${name}`);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return errorResponse(`Tool \"${name}\" failed: ${message}`);\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Helper: build a successful text content response\n// ---------------------------------------------------------------------------\n\nfunction successResponse(data: unknown) {\n const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);\n return {\n content: [{ type: 'text' as const, text }],\n };\n}\n\nfunction errorResponse(message: string) {\n return {\n content: [{ type: 'text' as const, text: message }],\n isError: true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Shared: parse project dependencies\n// ---------------------------------------------------------------------------\n\nasync function parseProject(dir: string): Promise<DependencyTree | null> {\n for (const parser of parsers) {\n if (await parser.detect(dir)) {\n return parser.parse(dir);\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Shared: build a full trust report for a single package\n// ---------------------------------------------------------------------------\n\nasync function buildTrustReport(\n packageName: string,\n version: string,\n blastRadius: number = 0,\n): Promise<TrustReport> {\n const results = await orchestrator.collectAll(packageName, version);\n const trustResult = trustEngine.calculate(results);\n const zombie = zombieDetector.detect(\n results.registry.data,\n results.github.data,\n );\n const typosquat = typosquatDetector.check(packageName);\n const alternatives = migrationAdvisor.suggest(\n packageName,\n zombie.isZombie ? 'zombie dependency' : 'low trust score',\n );\n\n // Determine trend from popularity data\n const trend = results.popularity.data?.trend ?? 'stable';\n\n return {\n package: packageName,\n version,\n trustScore: trustResult.trustScore,\n metrics: trustResult.metrics,\n isZombie: zombie.isZombie,\n blastRadius,\n alternatives: alternatives.map((a) => a.alternative),\n trend,\n typosquatRisk: typosquat.isRisky ? 1.0 : 0.0,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Tool handlers\n// ---------------------------------------------------------------------------\n\nasync function handleScan(args: Record<string, unknown> | undefined) {\n const dir = validateDir(args?.dir as string | undefined);\n\n const tree = await parseProject(dir);\n if (!tree) {\n return errorResponse(\n `No supported manifest file found in \"${dir}\". ` +\n 'Supported: package.json, requirements.txt, pyproject.toml, Pipfile.',\n );\n }\n\n // Build import graph for blast radius\n const importGraph = await buildImportGraph(dir);\n\n // Collect trust reports for all direct dependencies\n const directNodes = Array.from(tree.nodes.values()).filter((n) => n.isDirect);\n const reports: TrustReport[] = [];\n\n for (const node of directNodes) {\n const blastRadius = getBlastRadius(node.name, importGraph);\n const report = await buildTrustReport(node.name, node.version, blastRadius);\n reports.push(report);\n }\n\n // Overall score: weighted average\n const overallScore =\n reports.length > 0\n ? Math.round(\n reports.reduce((sum, r) => sum + r.trustScore, 0) / reports.length,\n )\n : 0;\n\n // Summary\n const zombieCount = reports.filter((r) => r.isZombie).length;\n const criticalCount = reports.filter((r) => r.trustScore < 50).length;\n const summary =\n `Scanned ${reports.length} direct dependencies. ` +\n `Overall trust score: ${overallScore}/100. ` +\n `${zombieCount} zombie(s) detected. ` +\n `${criticalCount} package(s) below trust threshold.`;\n\n const scanResult: ScanResult = {\n tree,\n reports,\n overallScore,\n summary,\n };\n\n // Serialize (handles Map objects)\n const serialized = jsonReporter.report(scanResult);\n return successResponse(serialized);\n}\n\nasync function handleTrustScore(args: Record<string, unknown> | undefined) {\n const packageName = validatePackageName(String(args?.package ?? ''));\n\n const version = String(args?.version ?? 'latest');\n const report = await buildTrustReport(packageName, version);\n return successResponse(report);\n}\n\nasync function handleBlastRadius(args: Record<string, unknown> | undefined) {\n const packageName = validatePackageName(String(args?.package ?? ''));\n const dir = validateDir(args?.dir as string | undefined);\n const result = await blastRadiusCalc.calculate(packageName, dir);\n return successResponse(result);\n}\n\nasync function handleZombies(args: Record<string, unknown> | undefined) {\n const dir = validateDir(args?.dir as string | undefined);\n\n const tree = await parseProject(dir);\n if (!tree) {\n return errorResponse(\n `No supported manifest file found in \"${dir}\".`,\n );\n }\n\n const directNodes = Array.from(tree.nodes.values()).filter((n) => n.isDirect);\n const zombies: Array<{\n package: string;\n version: string;\n severity: string;\n reason: string;\n lastActivity: string | null;\n }> = [];\n\n for (const node of directNodes) {\n const results = await orchestrator.collectAll(node.name, node.version);\n const zombie = zombieDetector.detect(\n results.registry.data,\n results.github.data,\n );\n\n if (zombie.isZombie) {\n zombies.push({\n package: node.name,\n version: node.version,\n severity: zombie.severity,\n reason: zombie.reason,\n lastActivity: zombie.lastActivity?.toISOString() ?? null,\n });\n }\n }\n\n if (zombies.length === 0) {\n return successResponse({\n message: 'No zombie dependencies detected.',\n zombies: [],\n });\n }\n\n return successResponse({\n message: `Found ${zombies.length} zombie dependency(ies).`,\n zombies,\n });\n}\n\nasync function handleSuggestMigration(args: Record<string, unknown> | undefined) {\n const packageName = validatePackageName(String(args?.package ?? ''));\n\n const suggestions = migrationAdvisor.suggest(packageName, 'manual query');\n\n if (suggestions.length === 0) {\n return successResponse({\n package: packageName,\n message: `No migration suggestions found for \"${packageName}\". ` +\n 'This package may not have known alternatives in our database.',\n suggestions: [],\n });\n }\n\n return successResponse({\n package: packageName,\n suggestions,\n });\n}\n\nasync function handleTyposquatCheck(args: Record<string, unknown> | undefined) {\n const packageName = validatePackageName(String(args?.package ?? ''));\n\n const result = typosquatDetector.check(packageName);\n return successResponse({\n package: packageName,\n ...result,\n });\n}\n\nasync function handleCompare(args: Record<string, unknown> | undefined) {\n const packageA = validatePackageName(String(args?.packageA ?? ''));\n const packageB = validatePackageName(String(args?.packageB ?? ''));\n\n const [reportA, reportB] = await Promise.all([\n buildTrustReport(packageA, 'latest'),\n buildTrustReport(packageB, 'latest'),\n ]);\n\n return successResponse({\n comparison: {\n packageA: reportA,\n packageB: reportB,\n winner:\n reportA.trustScore > reportB.trustScore\n ? packageA\n : reportA.trustScore < reportB.trustScore\n ? packageB\n : 'tie',\n scoreDifference: Math.abs(reportA.trustScore - reportB.trustScore),\n },\n });\n}\n\nasync function handleReport(args: Record<string, unknown> | undefined) {\n const dir = validateDir(args?.dir as string | undefined);\n const output = args?.output ? validateOutputPath(String(args.output), dir) : null;\n\n const tree = await parseProject(dir);\n if (!tree) {\n return errorResponse(\n `No supported manifest file found in \"${dir}\".`,\n );\n }\n\n // Build import graph for blast radius\n const importGraph = await buildImportGraph(dir);\n\n // Collect trust reports for direct dependencies\n const directNodes = Array.from(tree.nodes.values()).filter((n) => n.isDirect);\n const reports: TrustReport[] = [];\n\n for (const node of directNodes) {\n const blastRadius = getBlastRadius(node.name, importGraph);\n const report = await buildTrustReport(node.name, node.version, blastRadius);\n reports.push(report);\n }\n\n const overallScore =\n reports.length > 0\n ? Math.round(\n reports.reduce((sum, r) => sum + r.trustScore, 0) / reports.length,\n )\n : 0;\n\n const zombieCount = reports.filter((r) => r.isZombie).length;\n const criticalCount = reports.filter((r) => r.trustScore < 50).length;\n const summary =\n `Scanned ${reports.length} direct dependencies. ` +\n `Overall trust score: ${overallScore}/100. ` +\n `${zombieCount} zombie(s) detected. ` +\n `${criticalCount} package(s) below trust threshold.`;\n\n const scanResult: ScanResult = {\n tree,\n reports,\n overallScore,\n summary,\n };\n\n const jsonContent = jsonReporter.report(scanResult);\n\n if (output) {\n const { writeFile } = await import('node:fs/promises');\n await writeFile(output, jsonContent, 'utf-8');\n return successResponse({\n message: `Report written to ${output}`,\n path: output,\n });\n }\n\n return successResponse(jsonContent);\n}\n","import type { ScanResult } from \"../parsers/schema.js\";\n\n// ---------------------------------------------------------------------------\n// JsonReporter\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize a ScanResult as formatted JSON.\n *\n * The output is a self-contained JSON string suitable for piping into jq,\n * saving to a file, or passing to downstream tooling.\n */\nexport class JsonReporter {\n /**\n * Convert the scan result into a pretty-printed JSON string.\n *\n * Map objects (e.g. DependencyTree.nodes) are converted to plain\n * objects so that they survive JSON serialization.\n */\n report(result: ScanResult): string {\n return JSON.stringify(this.toSerializable(result), null, 2);\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n /**\n * Walk the value recursively and convert Map instances into plain objects\n * so JSON.stringify can handle them.\n */\n private toSerializable(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n\n if (value instanceof Map) {\n const obj: Record<string, unknown> = {};\n for (const [k, v] of value.entries()) {\n obj[String(k)] = this.toSerializable(v);\n }\n return obj;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => this.toSerializable(item));\n }\n\n if (typeof value === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = this.toSerializable(v);\n }\n return result;\n }\n\n return value;\n }\n}\n"],"mappings":"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","names":["server","__filename","__dirname"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dep-oracle",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"mcpName": "io.github.ertugrulakben/dep-oracle",
|
|
5
5
|
"description": "Predictive dependency security engine. Trust scores, zombie detection, blast radius analysis for your supply chain.",
|
|
6
6
|
"type": "module",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
21
|
"action",
|
|
22
|
+
"server.json",
|
|
22
23
|
"README.md",
|
|
23
24
|
"LICENSE"
|
|
24
25
|
],
|