dep-oracle 1.2.0 → 1.3.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 +33 -8
- package/dist/action/index.js +398 -16
- package/dist/badge-5Z3WAD2B.js +89 -0
- package/dist/badge-5Z3WAD2B.js.map +1 -0
- package/dist/chunk-32B3QIPY.js +1505 -0
- package/dist/chunk-32B3QIPY.js.map +1 -0
- package/dist/chunk-7DST6SNA.js +258 -0
- package/dist/chunk-7DST6SNA.js.map +1 -0
- package/dist/{chunk-TXSNFX3N.js → chunk-DLWG22RC.js} +403 -17
- package/dist/chunk-DLWG22RC.js.map +1 -0
- package/dist/chunk-HX6MGNBD.js +271 -0
- package/dist/chunk-HX6MGNBD.js.map +1 -0
- package/dist/chunk-IVXGOPRU.js +145 -0
- package/dist/chunk-IVXGOPRU.js.map +1 -0
- package/dist/chunk-SP3VYPXX.js +218 -0
- package/dist/chunk-SP3VYPXX.js.map +1 -0
- package/dist/chunk-T5EVLWZM.js +4234 -0
- package/dist/chunk-T5EVLWZM.js.map +1 -0
- package/dist/chunk-UMB5MJHL.js +239 -0
- package/dist/chunk-UMB5MJHL.js.map +1 -0
- package/dist/cli/index.js +163 -6499
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +9 -84
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +33 -12
- package/dist/mcp/server.js.map +1 -1
- package/dist/npm-UB54H37N.js +9 -0
- package/dist/npm-UB54H37N.js.map +1 -0
- package/dist/orchestrator-VOOYKDPT.js +8 -0
- package/dist/orchestrator-VOOYKDPT.js.map +1 -0
- package/dist/python-U4G2GK4J.js +9 -0
- package/dist/python-U4G2GK4J.js.map +1 -0
- package/dist/server-WONIBSG4.js +640 -0
- package/dist/server-WONIBSG4.js.map +1 -0
- package/dist/store-Z5UANEBB.js +8 -0
- package/dist/store-Z5UANEBB.js.map +1 -0
- package/dist/trust-score-YXYDFVPZ.js +8 -0
- package/dist/trust-score-YXYDFVPZ.js.map +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/chunk-TXSNFX3N.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
MigrationAdvisor,
|
|
10
10
|
NpmParser,
|
|
11
11
|
PythonParser,
|
|
12
|
+
RateLimiter,
|
|
12
13
|
ScanResultSchema,
|
|
13
14
|
TrustMetricsSchema,
|
|
14
15
|
TrustReportSchema,
|
|
@@ -25,11 +26,14 @@ import {
|
|
|
25
26
|
createLogger,
|
|
26
27
|
getBlastRadius,
|
|
27
28
|
getImportingFiles,
|
|
29
|
+
githubRateLimiter,
|
|
28
30
|
isDebug,
|
|
29
31
|
isVerbose,
|
|
30
32
|
logger,
|
|
33
|
+
npmRateLimiter,
|
|
34
|
+
pypiRateLimiter,
|
|
31
35
|
setVerbose
|
|
32
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-DLWG22RC.js";
|
|
33
37
|
|
|
34
38
|
// src/analyzers/trend-predictor.ts
|
|
35
39
|
var HIGH_CONFIDENCE_THRESHOLD = 3;
|
|
@@ -182,87 +186,6 @@ function monthsBetween(from, to) {
|
|
|
182
186
|
return years * 12 + months + (days < 0 ? -0.5 : 0);
|
|
183
187
|
}
|
|
184
188
|
|
|
185
|
-
// src/utils/rate-limiter.ts
|
|
186
|
-
var RateLimiter = class {
|
|
187
|
-
tokens;
|
|
188
|
-
maxTokens;
|
|
189
|
-
refillIntervalMs;
|
|
190
|
-
lastRefill;
|
|
191
|
-
waitQueue = [];
|
|
192
|
-
/**
|
|
193
|
-
* @param maxRequests Maximum number of requests allowed in the window
|
|
194
|
-
* @param windowMs Window duration in milliseconds
|
|
195
|
-
*/
|
|
196
|
-
constructor(maxRequests, windowMs) {
|
|
197
|
-
this.maxTokens = maxRequests;
|
|
198
|
-
this.tokens = maxRequests;
|
|
199
|
-
this.refillIntervalMs = windowMs;
|
|
200
|
-
this.lastRefill = Date.now();
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Acquire a token. Resolves immediately when tokens are available,
|
|
204
|
-
* otherwise waits until the bucket is refilled.
|
|
205
|
-
*/
|
|
206
|
-
async acquire() {
|
|
207
|
-
this.refill();
|
|
208
|
-
if (this.tokens > 0) {
|
|
209
|
-
this.tokens--;
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
return new Promise((resolve2) => {
|
|
213
|
-
this.waitQueue.push(resolve2);
|
|
214
|
-
this.scheduleRefill();
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Return the number of tokens currently available (without waiting).
|
|
219
|
-
*/
|
|
220
|
-
get remaining() {
|
|
221
|
-
this.refill();
|
|
222
|
-
return this.tokens;
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Return the number of milliseconds until the next refill.
|
|
226
|
-
*/
|
|
227
|
-
get msUntilRefill() {
|
|
228
|
-
const elapsed = Date.now() - this.lastRefill;
|
|
229
|
-
return Math.max(0, this.refillIntervalMs - elapsed);
|
|
230
|
-
}
|
|
231
|
-
// -----------------------------------------------------------------------
|
|
232
|
-
// Internal
|
|
233
|
-
// -----------------------------------------------------------------------
|
|
234
|
-
refill() {
|
|
235
|
-
const now = Date.now();
|
|
236
|
-
const elapsed = now - this.lastRefill;
|
|
237
|
-
if (elapsed >= this.refillIntervalMs) {
|
|
238
|
-
const periods = Math.floor(elapsed / this.refillIntervalMs);
|
|
239
|
-
this.tokens = Math.min(this.maxTokens, this.tokens + periods * this.maxTokens);
|
|
240
|
-
this.lastRefill = now - elapsed % this.refillIntervalMs;
|
|
241
|
-
this.drainWaitQueue();
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
scheduleRefill() {
|
|
245
|
-
const delay = this.msUntilRefill;
|
|
246
|
-
if (delay <= 0) {
|
|
247
|
-
this.refill();
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
setTimeout(() => {
|
|
251
|
-
this.refill();
|
|
252
|
-
}, delay);
|
|
253
|
-
}
|
|
254
|
-
drainWaitQueue() {
|
|
255
|
-
while (this.waitQueue.length > 0 && this.tokens > 0) {
|
|
256
|
-
this.tokens--;
|
|
257
|
-
const resolve2 = this.waitQueue.shift();
|
|
258
|
-
resolve2?.();
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
var githubRateLimiter = new RateLimiter(5e3, 36e5);
|
|
263
|
-
var npmRateLimiter = new RateLimiter(300, 6e4);
|
|
264
|
-
var pypiRateLimiter = new RateLimiter(100, 6e4);
|
|
265
|
-
|
|
266
189
|
// src/cli/config.ts
|
|
267
190
|
import { cosmiconfig } from "cosmiconfig";
|
|
268
191
|
var explorer = cosmiconfig("dep-oracle");
|
|
@@ -319,7 +242,8 @@ async function scan(options) {
|
|
|
319
242
|
const reports = [];
|
|
320
243
|
for (const node of directNodes) {
|
|
321
244
|
const blastRadius = getBlastRadius(node.name, importGraph);
|
|
322
|
-
const
|
|
245
|
+
const nodeEcosystem = options?.ecosystem ?? node.registry;
|
|
246
|
+
const results = await orchestrator.collectAll(node.name, node.version, nodeEcosystem);
|
|
323
247
|
const trustResult = trustEngine.calculate(results);
|
|
324
248
|
const zombie = zombieDetector.detect(results.registry.data, results.github.data);
|
|
325
249
|
const typosquat = typosquatDetector.check(node.name);
|
|
@@ -355,7 +279,8 @@ async function checkPackage(packageName, version, options) {
|
|
|
355
279
|
const zombieDetector = new ZombieDetector();
|
|
356
280
|
const migrationAdvisor = new MigrationAdvisor();
|
|
357
281
|
const typosquatDetector = new TyposquatDetector();
|
|
358
|
-
const
|
|
282
|
+
const ecosystem = options?.ecosystem ?? "npm";
|
|
283
|
+
const results = await orchestrator.collectAll(packageName, version ?? "latest", ecosystem);
|
|
359
284
|
const trustResult = trustEngine.calculate(results);
|
|
360
285
|
const zombie = zombieDetector.detect(results.registry.data, results.github.data);
|
|
361
286
|
const typosquat = typosquatDetector.check(packageName);
|
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","../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"]}
|
|
1
|
+
{"version":3,"sources":["../src/analyzers/trend-predictor.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 * 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 ecosystem?: 'npm' | 'pypi';\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 nodeEcosystem = options?.ecosystem ?? node.registry;\n const results = await orchestrator.collectAll(node.name, node.version, nodeEcosystem);\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; ecosystem?: 'npm' | 'pypi' },\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 ecosystem = options?.ecosystem ?? 'npm';\n const results = await orchestrator.collectAll(packageName, version ?? 'latest', ecosystem);\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":[]}
|
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-DLWG22RC.js";
|
|
14
14
|
|
|
15
15
|
// src/mcp/server.ts
|
|
16
16
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -117,6 +117,10 @@ var TOOLS = [
|
|
|
117
117
|
dir: {
|
|
118
118
|
type: "string",
|
|
119
119
|
description: "Absolute path to the project directory to scan. Defaults to the current working directory if omitted."
|
|
120
|
+
},
|
|
121
|
+
ecosystem: {
|
|
122
|
+
type: "string",
|
|
123
|
+
description: 'Package ecosystem: "npm" or "pypi". Defaults to "npm". When scanning a project, the ecosystem is auto-detected from the manifest.'
|
|
120
124
|
}
|
|
121
125
|
},
|
|
122
126
|
required: []
|
|
@@ -124,17 +128,21 @@ var TOOLS = [
|
|
|
124
128
|
},
|
|
125
129
|
{
|
|
126
130
|
name: "dep_oracle_trust_score",
|
|
127
|
-
description: "Calculate the trust score for a single
|
|
131
|
+
description: "Calculate the trust score for a single package. Queries the registry, GitHub, OSV vulnerability database, and other sources to produce a weighted score (0-100) across 6 dimensions: security, maintainer, activity, popularity, funding, and license. Returns a TrustReport JSON with the overall score, per-dimension metrics, zombie status, and alternative suggestions.",
|
|
128
132
|
inputSchema: {
|
|
129
133
|
type: "object",
|
|
130
134
|
properties: {
|
|
131
135
|
package: {
|
|
132
136
|
type: "string",
|
|
133
|
-
description: '
|
|
137
|
+
description: 'Package name (e.g. "express", "@scope/pkg", "requests").'
|
|
134
138
|
},
|
|
135
139
|
version: {
|
|
136
140
|
type: "string",
|
|
137
141
|
description: 'Specific version to analyze (e.g. "4.18.2"). If omitted, the latest version is used.'
|
|
142
|
+
},
|
|
143
|
+
ecosystem: {
|
|
144
|
+
type: "string",
|
|
145
|
+
description: 'Package ecosystem: "npm" or "pypi". Defaults to "npm".'
|
|
138
146
|
}
|
|
139
147
|
},
|
|
140
148
|
required: ["package"]
|
|
@@ -213,6 +221,10 @@ var TOOLS = [
|
|
|
213
221
|
packageB: {
|
|
214
222
|
type: "string",
|
|
215
223
|
description: "Second package name to compare."
|
|
224
|
+
},
|
|
225
|
+
ecosystem: {
|
|
226
|
+
type: "string",
|
|
227
|
+
description: 'Package ecosystem: "npm" or "pypi". Defaults to "npm". Both packages must be from the same ecosystem.'
|
|
216
228
|
}
|
|
217
229
|
},
|
|
218
230
|
required: ["packageA", "packageB"]
|
|
@@ -231,6 +243,10 @@ var TOOLS = [
|
|
|
231
243
|
output: {
|
|
232
244
|
type: "string",
|
|
233
245
|
description: "Absolute path to write the report file. If omitted, the report content is returned directly."
|
|
246
|
+
},
|
|
247
|
+
ecosystem: {
|
|
248
|
+
type: "string",
|
|
249
|
+
description: 'Package ecosystem: "npm" or "pypi". Defaults to "npm".'
|
|
234
250
|
}
|
|
235
251
|
},
|
|
236
252
|
required: []
|
|
@@ -290,8 +306,8 @@ async function parseProject(dir) {
|
|
|
290
306
|
}
|
|
291
307
|
return null;
|
|
292
308
|
}
|
|
293
|
-
async function buildTrustReport(packageName, version, blastRadius = 0) {
|
|
294
|
-
const results = await orchestrator.collectAll(packageName, version);
|
|
309
|
+
async function buildTrustReport(packageName, version, blastRadius = 0, ecosystem = "npm") {
|
|
310
|
+
const results = await orchestrator.collectAll(packageName, version, ecosystem);
|
|
295
311
|
const trustResult = trustEngine.calculate(results);
|
|
296
312
|
const zombie = zombieDetector.detect(
|
|
297
313
|
results.registry.data,
|
|
@@ -328,7 +344,8 @@ async function handleScan(args) {
|
|
|
328
344
|
const reports = [];
|
|
329
345
|
for (const node of directNodes) {
|
|
330
346
|
const blastRadius = getBlastRadius(node.name, importGraph);
|
|
331
|
-
const
|
|
347
|
+
const nodeEcosystem = node.registry === "pypi" ? "pypi" : "npm";
|
|
348
|
+
const report = await buildTrustReport(node.name, node.version, blastRadius, nodeEcosystem);
|
|
332
349
|
reports.push(report);
|
|
333
350
|
}
|
|
334
351
|
const overallScore = reports.length > 0 ? Math.round(
|
|
@@ -349,7 +366,8 @@ async function handleScan(args) {
|
|
|
349
366
|
async function handleTrustScore(args) {
|
|
350
367
|
const packageName = validatePackageName(String(args?.package ?? ""));
|
|
351
368
|
const version = String(args?.version ?? "latest");
|
|
352
|
-
const
|
|
369
|
+
const ecosystem = args?.ecosystem === "pypi" ? "pypi" : "npm";
|
|
370
|
+
const report = await buildTrustReport(packageName, version, 0, ecosystem);
|
|
353
371
|
return successResponse(report);
|
|
354
372
|
}
|
|
355
373
|
async function handleBlastRadius(args) {
|
|
@@ -369,7 +387,8 @@ async function handleZombies(args) {
|
|
|
369
387
|
const directNodes = Array.from(tree.nodes.values()).filter((n) => n.isDirect);
|
|
370
388
|
const zombies = [];
|
|
371
389
|
for (const node of directNodes) {
|
|
372
|
-
const
|
|
390
|
+
const nodeEcosystem = node.registry === "pypi" ? "pypi" : "npm";
|
|
391
|
+
const results = await orchestrator.collectAll(node.name, node.version, nodeEcosystem);
|
|
373
392
|
const zombie = zombieDetector.detect(
|
|
374
393
|
results.registry.data,
|
|
375
394
|
results.github.data
|
|
@@ -421,9 +440,10 @@ async function handleTyposquatCheck(args) {
|
|
|
421
440
|
async function handleCompare(args) {
|
|
422
441
|
const packageA = validatePackageName(String(args?.packageA ?? ""));
|
|
423
442
|
const packageB = validatePackageName(String(args?.packageB ?? ""));
|
|
443
|
+
const ecosystem = args?.ecosystem === "pypi" ? "pypi" : "npm";
|
|
424
444
|
const [reportA, reportB] = await Promise.all([
|
|
425
|
-
buildTrustReport(packageA, "latest"),
|
|
426
|
-
buildTrustReport(packageB, "latest")
|
|
445
|
+
buildTrustReport(packageA, "latest", 0, ecosystem),
|
|
446
|
+
buildTrustReport(packageB, "latest", 0, ecosystem)
|
|
427
447
|
]);
|
|
428
448
|
return successResponse({
|
|
429
449
|
comparison: {
|
|
@@ -448,7 +468,8 @@ async function handleReport(args) {
|
|
|
448
468
|
const reports = [];
|
|
449
469
|
for (const node of directNodes) {
|
|
450
470
|
const blastRadius = getBlastRadius(node.name, importGraph);
|
|
451
|
-
const
|
|
471
|
+
const nodeEcosystem = node.registry === "pypi" ? "pypi" : "npm";
|
|
472
|
+
const report = await buildTrustReport(node.name, node.version, blastRadius, nodeEcosystem);
|
|
452
473
|
reports.push(report);
|
|
453
474
|
}
|
|
454
475
|
const overallScore = reports.length > 0 ? Math.round(
|
|
@@ -483,7 +504,7 @@ var pkgVersion = (() => {
|
|
|
483
504
|
try {
|
|
484
505
|
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
485
506
|
} catch {
|
|
486
|
-
return "1.
|
|
507
|
+
return "1.3.0";
|
|
487
508
|
}
|
|
488
509
|
})();
|
|
489
510
|
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.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"]}
|
|
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.3.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 ecosystem: {\n type: 'string',\n description:\n 'Package ecosystem: \"npm\" or \"pypi\". Defaults to \"npm\". ' +\n 'When scanning a project, the ecosystem is auto-detected from the manifest.',\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 package. Queries the 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: 'Package name (e.g. \"express\", \"@scope/pkg\", \"requests\").',\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 ecosystem: {\n type: 'string',\n description: 'Package ecosystem: \"npm\" or \"pypi\". Defaults to \"npm\".',\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 ecosystem: {\n type: 'string',\n description: 'Package ecosystem: \"npm\" or \"pypi\". Defaults to \"npm\". Both packages must be from the same ecosystem.',\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 ecosystem: {\n type: 'string',\n description: 'Package ecosystem: \"npm\" or \"pypi\". Defaults to \"npm\".',\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 ecosystem: 'npm' | 'pypi' = 'npm',\n): Promise<TrustReport> {\n const results = await orchestrator.collectAll(packageName, version, ecosystem);\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 nodeEcosystem = node.registry === 'pypi' ? 'pypi' as const : 'npm' as const;\n const report = await buildTrustReport(node.name, node.version, blastRadius, nodeEcosystem);\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 ecosystem = (args?.ecosystem === 'pypi' ? 'pypi' : 'npm') as 'npm' | 'pypi';\n const report = await buildTrustReport(packageName, version, 0, ecosystem);\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 nodeEcosystem = node.registry === 'pypi' ? 'pypi' as const : 'npm' as const;\n const results = await orchestrator.collectAll(node.name, node.version, nodeEcosystem);\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 const ecosystem = (args?.ecosystem === 'pypi' ? 'pypi' : 'npm') as 'npm' | 'pypi';\n\n const [reportA, reportB] = await Promise.all([\n buildTrustReport(packageA, 'latest', 0, ecosystem),\n buildTrustReport(packageB, 'latest', 0, ecosystem),\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 nodeEcosystem = node.registry === 'pypi' ? 'pypi' as const : 'npm' as const;\n const report = await buildTrustReport(node.name, node.version, blastRadius, nodeEcosystem);\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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|